diff --git a/.eslintignore b/.eslintignore index 54f42aa5..63391126 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,3 +8,4 @@ keys/ logs/ static/ templates/ +*/webpack.config.js \ No newline at end of file diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml new file mode 100644 index 00000000..9abe9d54 --- /dev/null +++ b/.github/workflows/github_pages.yml @@ -0,0 +1,43 @@ +name: Github pages + +on: + push: + branches: + - develop + +jobs: + deploy: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + fetch-depth: 0 + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: '0.83.1' + extended: true + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '14.x' + + - name: Install npm packages + working-directory: ./site + run: | + npm ci + + - name: Build docs + run: | + pip install gitpython packaging + python site/build_docs.py + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + force_orphan: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ca8e06c..877a095f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: cache-from: type=local,src=/tmp/cvat_cache_server tags: openvino/cvat_server:latest load: true - - name: Runing unit tests + - name: Running unit tests env: HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" @@ -80,7 +80,7 @@ jobs: strategy: fail-fast: false matrix: - specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', 'actions_objects', 'actions_objects2', 'actions_users', 'actions_projects', 'canvas3d_functionality', 'issues_prs', 'issues_prs2'] + specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', 'actions_objects', 'actions_objects2', 'actions_users', 'actions_projects_models', 'canvas3d_functionality', 'canvas3d_functionality_2', 'issues_prs', 'issues_prs2'] steps: - uses: actions/checkout@v2 - name: Getting SHA from the default branch @@ -151,7 +151,7 @@ jobs: run: | npm ci npm run coverage - docker-compose -f docker-compose.yml -f docker-compose.dev.yml build cvat_ui + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml build cvat_ui - name: Running e2e tests env: DJANGO_SU_NAME: 'admin' @@ -159,16 +159,24 @@ jobs: DJANGO_SU_PASSWORD: '12qwaszx' API_ABOUT_PAGE: "localhost:8080/api/v1/server/about" run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml up -d /bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done' docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" cd ./tests npm ci if [[ ${{ github.ref }} == 'refs/heads/develop' ]]; then - npx cypress run --headless --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then + npx cypress run --browser chrome --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + else + npx cypress run --headless --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + fi mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json else - npx cypress run --headless --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then + npx cypress run --browser chrome --env coverage=false --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + else + npx cypress run --headless --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js' + fi fi - name: Creating a log file from "cvat" container logs if: failure() @@ -199,7 +207,7 @@ jobs: needs: [Unit_testing, E2E_testing] steps: - uses: actions/checkout@v2 - - name: Geting SHA from the default branch + - name: Getting SHA from the default branch id: get-sha run: | URL_get_default_branch="https://api.github.com/repos/${{ github.repository }}" diff --git a/.github/workflows/publish_docker_images.yml b/.github/workflows/publish_docker_images.yml index 4c7353ee..0cf349d4 100644 --- a/.github/workflows/publish_docker_images.yml +++ b/.github/workflows/publish_docker_images.yml @@ -4,36 +4,40 @@ on: types: [published] jobs: - build_and_push_to_registry: + Unit_testing: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 12 - - - name: Build images - run: | - CLAM_AV=yes INSTALL_SOURCES=yes docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml build - - name: Run unit tests env: HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} CONTAINER_COVERAGE_DATA_DIR: '/coverage_data' - DJANGO_SU_NAME: 'admin' - DJANGO_SU_EMAIL: 'admin@localhost.company' - DJANGO_SU_PASSWORD: '12qwaszx' run: | docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli' docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm ci && cd ../cvat-core && npm ci && npm run test' - docker-compose up -d - docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" + E2E_testing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 12 - name: Run end-to-end tests + env: + DJANGO_SU_NAME: 'admin' + DJANGO_SU_EMAIL: 'admin@localhost.company' + DJANGO_SU_PASSWORD: '12qwaszx' + API_ABOUT_PAGE: "localhost:8080/api/v1/server/about" run: | + docker-compose -f docker-compose.yml -f docker-compose.dev.yml build + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml up -d + /bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done' + docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" cd ./tests npm ci npm run cypress:run:chrome + npm run cypress:run:chrome:canvas3d - name: Uploading cypress screenshots as an artifact if: failure() uses: actions/upload-artifact@v2 @@ -41,6 +45,14 @@ jobs: name: cypress_screenshots path: ${{ github.workspace }}/tests/cypress/screenshots + Push_to_registry: + runs-on: ubuntu-latest + needs: [Unit_testing, E2E_testing] + steps: + - uses: actions/checkout@v2 + - name: Build images + run: | + CLAM_AV=yes INSTALL_SOURCES=yes docker-compose -f docker-compose.yml -f docker-compose.dev.yml build - name: Login to Docker Hub uses: docker/login-action@v1 with: diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 61560bcd..13400a23 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -11,31 +11,16 @@ jobs: - name: Run checks run: | - URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" - PR_FILES=$(curl -s -X GET -G $URL | jq -r '.[] | select(.status != "removed") | .filename') - for files in $PR_FILES; do - extension="${files##*.}" - if [[ $extension == 'md' ]]; then - changed_files_remark+=" ${files}" - fi - done + npm ci + mkdir -p remark_report - if [[ ! -z ${changed_files_remark} ]]; then - npm ci - npm install remark-cli@9.0.0 vfile-reporter-json@2.0.2 - mkdir -p remark_report - - echo "Remark version: "`npx remark --version` - echo "The files will be checked: "`echo ${changed_files_remark}` - npx remark --quiet --report json --no-stdout ${changed_files_remark} 2> ./remark_report/remark_report.json - get_report=`cat ./remark_report/remark_report.json | jq -r '.[] | select(.messages | length > 0)'` - if [[ ! -z ${get_report} ]]; then - pip install json2html - python ./tests/json_to_html.py ./remark_report/remark_report.json - exit 1 - fi - else - echo "No files with the \"md\" extension found" + echo "Remark version: "`npx remark --version` + npx remark --quiet --report json --no-stdout . 2> ./remark_report/remark_report.json + get_report=`cat ./remark_report/remark_report.json | jq -r '.[] | select(.messages | length > 0)'` + if [[ ! -z ${get_report} ]]; then + pip install json2html + python ./tests/json_to_html.py ./remark_report/remark_report.json + exit 1 fi - name: Upload artifacts diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index 90f53049..e7d23696 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -18,7 +18,7 @@ jobs: DJANGO_SU_PASSWORD: "12qwaszx" API_ABOUT_PAGE: "localhost:8080/api/v1/server/about" run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml up -d --build + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml -f components/serverless/docker-compose.serverless.yml up -d --build /bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done' docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" - name: End-to-end testing diff --git a/.gitignore b/.gitignore index ab8d42c8..9ff25c63 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,9 @@ yarn-error.log* /helm-chart/values.*.yaml /helm-chart/*.values.yaml /helm-chart/charts/* + +#Ignore website temp files +/site/public/ +/site/resources/ +/site/node_modules/ +/site/tech-doc-hugo \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..ae36fe05 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "site/themes/docsy"] + path = site/themes/docsy + url = https://github.com/google/docsy diff --git a/.nycrc b/.nycrc index 7438d774..8c684990 100644 --- a/.nycrc +++ b/.nycrc @@ -9,7 +9,9 @@ ], "exclude": [ "**/3rdparty/*", - "**/tests/*" + "**/tests/*", + "cvat-ui/src/actions/boundaries-actions.ts", + "cvat-ui/src/utils/platform-checker.ts" ], "parser-plugins": [ "typescript" diff --git a/.remarkrc.js b/.remarkrc.js index f1140194..a986dad3 100644 --- a/.remarkrc.js +++ b/.remarkrc.js @@ -1,6 +1,8 @@ exports.settings = { bullet: '*', paddedTable: false }; exports.plugins = [ + 'remark-frontmatter', + 'remark-gfm', 'remark-preset-lint-recommended', 'remark-preset-lint-consistent', ['remark-lint-list-item-indent', 'space'], diff --git a/.vscode/launch.json b/.vscode/launch.json index 658abd2f..4b65fad0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ { "type": "chrome", "request": "launch", - "preLaunchTask": "ui.js: server", + "preLaunchTask": "npm: start - cvat-ui", "name": "ui.js: debug", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}/cvat-ui", @@ -45,7 +45,7 @@ "python": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "env": { - "CVAT_SERVERLESS": "1", + "CVAT_SERVERLESS": "1" }, "args": [ "runserver", @@ -207,7 +207,6 @@ { "name": "server: debug", "configurations": [ - "server: chrome", "server: django", "server: RQ - default", "server: RQ - low", diff --git a/.vscode/settings.json b/.vscode/settings.json index cb78ca04..ce5a350e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "eslint.enable": true, "eslint.probe": [ "javascript", "typescript", @@ -24,5 +23,14 @@ "python.linting.pycodestyleEnabled": false, "licenser.license": "Custom", "licenser.customHeader": "Copyright (C) @YEAR@ Intel Corporation\n\nSPDX-License-Identifier: MIT", - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "python.pythonPath": ".env/bin/python", + "sqltools.connections": [ + { + "previewLimit": 50, + "driver": "SQLite", + "name": "cvat", + "database": "${workspaceFolder:cvat}/db.sqlite3" + } + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 10c34d90..c8d8c11e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,11 +4,35 @@ "version": "2.0.0", "tasks": [ { - "label": "ui.js: server", "type": "npm", "script": "start", "path": "cvat-ui/", - "problemMatcher": [] + "label": "npm: start - cvat-ui", + "detail": "webpack-dev-server --env.API_URL=http://localhost:7000 --config ./webpack.config.js --mode=development", + "promptOnClose": true, + "isBackground": true, + "problemMatcher": { + "owner": "webpack", + "severity": "error", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "ERROR in (.*)", + "file": 1 + }, + { + "regexp": "\\((\\d+),(\\d+)\\):(.*)", + "line": 1, + "column": 2, + "message": 3 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "webpack-dev-server", + "endsPattern": "Compiled" + } + } } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 349498e3..1e24fe57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.4.0] - 2021-05-18 +## \[1.5.0] - 2021-08-02 + +### Added + +- Support of context images for 2D image tasks () +- Support of cloud storage without copying data into CVAT: server part () +- Filter `is_active` for user list () +- Ability to export/import tasks () +- Add a tutorial for semi-automatic/automatic annotation () +- Explicit "Done" button when drawing any polyshapes () +- Histogram equalization with OpenCV javascript () +- Client-side polyshapes approximation when using semi-automatic interactors & scissors () + +### Changed + +- Updated manifest format, added meta with related images () +- Update of COCO format documentation () +- Updated Webpack Dev Server config to add proxy () +- Update to Django 3.1.12 () +- Updated visibility for removable points in AI tools () +- Updated UI handling for IOG serverless function () +- Changed Nginx proxy to Traefik in `docker-compose.yml` () +- Simplify the process of deploying CVAT with HTTPS () + +### Fixed + +- Project page requests took a long time and did many DB queries () +- Fixed Python 3.6 support () +- Incorrect attribute import in tracks () +- Issue "is not a constructor" when create object, save, undo, save, redo save () +- Fix CLI create an infinite loop if git repository responds with failure () +- Bug with sidebar & fullscreen () +- 504 Gateway Time-out on `data/meta` requests () +- TypeError: Cannot read property 'clientX' of undefined when draw cuboids with hotkeys () +- Duplication of the cuboids when redraw them () +- Some code issues in Deep Extreme Cut handler code () +- UI fails when inactive user is assigned to a task/job () +- Calculate precise progress of decoding a video file () +- Falsely successful `cvat_ui` image build in case of OOM error that leads to the default nginx welcome page + () +- Fixed issue when save filtered object in AAM () +- Context image disappears after undo/redo () +- Using combined data sources (directory and image) when create a task () +- Creating task with labels in project () + +## \[1.4.0] - 2021-05-18 ### Added @@ -13,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hotkeys to switch a label of existing object or to change default label (for objects created with N) () - A script to convert some kinds of DICOM files to regular images () - Helm chart prototype () +- Initial implementation of moving tasks between projects () ### Changed @@ -31,7 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Manifest: migration () - Fixed cropping polygon in some corner cases () -## [1.3.0] - 3/31/2021 +## \[1.3.0] - 3/31/2021 ### Added @@ -56,7 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - [Market-1501](https://www.aitribune.com/dataset/2018051063) format support () - Ability of upload manifest for dataset with images () -- Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418) +- Annotations filters UI using react-awesome-query-builder () - Storing settings in local storage to keep them between browser sessions () - [ICDAR](https://rrc.cvc.uab.es/?ch=2) format support () - Added switcher to maintain polygon crop behavior ( @@ -85,7 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed filters select overflow () - Fixed tasks in project auto annotation () - Cuboids are missed in annotations statistics () -- The list of files attached to the task is not displayed () - A couple of css-related issues (top bar disappear, wrong arrow position on collapse elements) () - Issue with point region doesn't work in Firefox () - Fixed cuboid perspective change () @@ -104,7 +150,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updating label attributes when label contains number attributes () - Crop a polygon if its points are outside the bounds of the image () -## [1.2.0] - 2021-01-08 +## \[1.2.0] - 2021-01-08 ### Fixed @@ -112,7 +158,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Frame preloading () - Project cannot be removed from the project page () -## [1.2.0-beta] - 2020-12-15 +## \[1.2.0-beta] - 2020-12-15 ### Added @@ -129,7 +175,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - PATCH requests from cvat-core submit only changed fields () -- deploy.sh in serverless folder is seperated into deploy_cpu.sh and deploy_gpu.sh () +- deploy.sh in serverless folder is separated into deploy_cpu.sh and deploy_gpu.sh () - Bumped nuclio version to 1.5.8 - Migrated to Antd 4.9 () @@ -152,20 +198,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reset state (reviews, issues) after logout or changing a job () - TypeError: Cannot read property 'id' of undefined when updating a task () -## [1.2.0-alpha] - 2020-11-09 +## \[1.2.0-alpha] - 2020-11-09 ### Added - Ability to login into CVAT-UI with token from api/v1/auth/login () - Added layout grids toggling ('ctrl + alt + Enter') - Added password reset functionality () -- Ability to work with data on the fly (https://github.com/opencv/cvat/pull/2007) +- Ability to work with data on the fly () - Annotation in process outline color wheel () - On the fly annotation using DL detectors () - Displaying automatic annotation progress on a task view () - Automatic tracking of bounding boxes using serverless functions () -- [Datumaro] CLI command for dataset equality comparison () -- [Datumaro] Merging of datasets with different labels () +- \[Datumaro] CLI command for dataset equality comparison () +- \[Datumaro] Merging of datasets with different labels () - Add FBRS interactive segmentation serverless function () - Ability to change default behaviour of previous/next buttons of a player. It supports regular navigation, searching a frame according to annotations @@ -185,7 +231,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Used Ubuntu:20.04 as a base image for CVAT Dockerfile () - Right colors of label tags in label mapping when a user runs automatic detection () - Nuclio became an optional component of CVAT () -- A key to remove a point from a polyshape [Ctrl => Alt] () +- A key to remove a point from a polyshape (Ctrl => Alt) () - Updated `docker-compose` file version from `2.3` to `3.3`() - Added auto inference of url schema from host in CLI, if provided () - Track frames in skips between annotation is presented in MOT and MOTS formats are marked `outside` () @@ -217,15 +263,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 'List of tasks' Kibana visualization () - An error on exporting not `jpg` or `png` images in TF Detection API format () -## [1.1.0] - 2020-08-31 +## \[1.1.0] - 2020-08-31 ### Added - Siammask tracker as DL serverless function () -- [Datumaro] Added model info and source info commands () -- [Datumaro] Dataset statistics () +- \[Datumaro] Added model info and source info commands () +- \[Datumaro] Dataset statistics () - Ability to change label color in tasks and predefined labels () -- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695) +- \[Datumaro] Multi-dataset merge () - Ability to configure email verification for new users () - Link to django admin page from UI () - Notification message when users use wrong browser () @@ -244,7 +290,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Django RQ dashboard view () - Object's details menu settings () -## [1.1.0-beta] - 2020-08-03 +## \[1.1.0-beta] - 2020-08-03 ### Added @@ -252,7 +298,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Source type support for tags, shapes and tracks () - Source type support for CVAT Dumper/Loader () - Intelligent polygon editing () -- Support creating multiple jobs for each task through python cli (https://github.com/opencv/cvat/pull/1950) +- Support creating multiple jobs for each task through python cli () - python cli over https () - Error message when plugins weren't able to initialize instead of infinite loading () - Ability to change user password () @@ -279,11 +325,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cannot read property 'pinned' of undefined because of zOrder related issues () - Do not iterate over hidden objects in aam (which are invisible because of zOrder) () - Cursor position is reset after changing a text field () -- Hidden points and cuboids can be selected to be groupped () +- Hidden points and cuboids can be selected to be grouped () - `outside` annotations should not be in exported images () - `CVAT for video format` import error with interpolation () - `Image compression` definition mismatch () -- Points are dublicated during polygon interpolation sometimes () +- Points are duplicated during polygon interpolation sometimes () - When redraw a shape with activated autobordering, previous points are visible () - No mapping between side object element and context menu in some attributes () - Interpolated shapes exported as `keyframe = True` () @@ -292,7 +338,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Clearing frame cache when close a task () - Increase rate of throttling policy for unauthenticated users () -## [1.1.0-alpha] - 2020-06-30 +## \[1.1.0-alpha] - 2020-06-30 ### Added @@ -307,9 +353,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ClamAV antivirus integration () - Added canvas background color selector () - SCSS files linting with Stylelint tool () -- Supported import and export or single boxes in MOT format (https://github.com/opencv/cvat/pull/1764) -- [Datumaro] Added `stats` command, which shows some dataset statistics - like image mean and std (https://github.com/opencv/cvat/pull/1734) +- Supported import and export or single boxes in MOT format () +- \[Datumaro] Added `stats` command, which shows some dataset statistics + like image mean and std () - Add option to upload annotations upon task creation on CLI - Polygon and polylines interpolation () - Ability to redraw shape from scratch (Shift + N) for an activated shape () @@ -319,9 +365,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new tag annotation workspace () - Appearance block in attribute annotation mode () - Keyframe navigations and some switchers in attribute annotation mode () -- [Datumaro] Added `convert` command to convert datasets directly () -- [Datumaro] Added an option to specify image extension when exporting datasets () -- [Datumaro] Added image copying when exporting datasets, if possible () +- \[Datumaro] Added `convert` command to convert datasets directly () +- \[Datumaro] Added an option to specify image extension when exporting datasets () +- \[Datumaro] Added image copying when exporting datasets, if possible () ### Changed @@ -331,10 +377,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Settings page move to the modal. () - Implemented import and export of annotations with relative image paths () - Using only single click to start editing or remove a point () -- Added support for attributes in VOC XML format (https://github.com/opencv/cvat/pull/1792) -- Added annotation attributes in COCO format (https://github.com/opencv/cvat/pull/1782) +- Added support for attributes in VOC XML format () +- Added annotation attributes in COCO format () - Colorized object items in the side panel () -- [Datumaro] Annotation-less files are not generated anymore in COCO format, unless tasks explicitly requested () +- \[Datumaro] Annotation-less files are not generated anymore in COCO format, unless tasks explicitly requested () ### Fixed @@ -361,7 +407,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SQL injection in Django `CVE-2020-9402` () -## [1.0.0] - 2020-05-29 +## \[1.0.0] - 2020-05-29 ### Added @@ -398,7 +444,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added directory removal on error during `extract` command () - Added debug error message on incorrect XPath () - Exporting frame stepped task - () + (, ) - Fixed broken command line interface for `cvat` export format in Datumaro () - Updated Rest API document, Swagger document serving instruction issue () - Fixed cuboid occluded view () @@ -423,7 +469,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Permission group whitelist check for analytics view () -## [1.0.0-beta.2] - 2020-04-30 +## \[1.0.0-beta.2] - 2020-04-30 ### Added @@ -434,14 +480,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- Annotation convertation utils, currently supported natively via Datumaro framework - (https://github.com/opencv/cvat/pull/1477) +- Annotation conversion utils, currently supported natively via Datumaro framework + () ### Fixed -- Auto annotation, TF annotation and Auto segmentation apps (https://github.com/opencv/cvat/pull/1409) +- Auto annotation, TF annotation and Auto segmentation apps () - Import works with truncated images now: "OSError:broken data stream" on corrupt images - (https://github.com/opencv/cvat/pull/1430) + () - Hide functionality (H) doesn't work () - The highlighted attribute doesn't correspond to the chosen attribute in AAM () - Inconvinient image shaking while drawing a polygon (hold Alt key during drawing/editing/grouping to drag an image) () @@ -449,13 +495,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Block of text information doesn't disappear after deactivating for locked shapes () - Annotation uploading fails in annotation view () - UI freezes after canceling pasting with escape () -- Duplicating keypoints in COCO export (https://github.com/opencv/cvat/pull/1435) +- Duplicating keypoints in COCO export () - CVAT new UI: add arrows on a mouse cursor () - Delete point bug (in new UI) () -- Fix apache startup after PC restart (https://github.com/opencv/cvat/pull/1467) -- Open task button doesn't work (https://github.com/opencv/cvat/pull/1474) +- Fix apache startup after PC restart () +- Open task button doesn't work () -## [1.0.0-beta.1] - 2020-04-15 +## \[1.0.0-beta.1] - 2020-04-15 ### Added @@ -464,12 +510,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to display a bitmap in the new UI - Button to reset colors settings (brightness, saturation, contrast) in the new UI - Option to display shape text always -- Dedicated message with clarifications when share is unmounted (https://github.com/opencv/cvat/pull/1373) -- Ability to create one tracked point (https://github.com/opencv/cvat/pull/1383) +- Dedicated message with clarifications when share is unmounted () +- Ability to create one tracked point () - Ability to draw/edit polygons and polylines with automatic bordering feature - (https://github.com/opencv/cvat/pull/1394) + () - Tutorial: instructions for CVAT over HTTPS -- Deep extreme cut (semi-automatic segmentation) to the new UI (https://github.com/opencv/cvat/pull/1398) +- Deep extreme cut (semi-automatic segmentation) to the new UI () ### Changed @@ -489,38 +535,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hidden points (or outsided) are visible after changing a frame - Merge is allowed for points, but clicks on points conflict with frame dragging logic - Removed objects are visible for search -- Add missed task_id and job_id fields into exception logs for the new UI (https://github.com/opencv/cvat/pull/1372) -- UI fails when annotations saving occurs during drag/resize/edit (https://github.com/opencv/cvat/pull/1383) +- Add missed task_id and job_id fields into exception logs for the new UI () +- UI fails when annotations saving occurs during drag/resize/edit () - Multiple savings when hold Ctrl+S (a lot of the same copies of events were sent with the same working time) - (https://github.com/opencv/cvat/pull/1383) -- UI doesn't have any reaction when git repos synchronization failed (https://github.com/opencv/cvat/pull/1383) -- Bug when annotations cannot be saved after (delete - save - undo - save) (https://github.com/opencv/cvat/pull/1383) -- VOC format exports Upper case labels correctly in lower case (https://github.com/opencv/cvat/pull/1379) -- Fixed polygon exporting bug in COCO dataset (https://github.com/opencv/cvat/issues/1387) -- Task creation from remote files (https://github.com/opencv/cvat/pull/1392) + () +- UI doesn't have any reaction when git repos synchronization failed () +- Bug when annotations cannot be saved after (delete - save - undo - save) () +- VOC format exports Upper case labels correctly in lower case () +- Fixed polygon exporting bug in COCO dataset () +- Task creation from remote files () - Job cannot be opened in some cases when the previous job was failed during opening - (https://github.com/opencv/cvat/issues/1403) -- Deactivated shape is still highlighted on the canvas (https://github.com/opencv/cvat/issues/1403) -- AttributeError: 'tuple' object has no attribute 'read' in ReID algorithm (https://github.com/opencv/cvat/issues/1403) -- Wrong semi-automatic segmentation near edges of an image (https://github.com/opencv/cvat/issues/1403) -- Git repos paths (https://github.com/opencv/cvat/pull/1400) -- Uploading annotations for tasks with multiple jobs (https://github.com/opencv/cvat/pull/1396) + () +- Deactivated shape is still highlighted on the canvas () +- AttributeError: 'tuple' object has no attribute 'read' in ReID algorithm () +- Wrong semi-automatic segmentation near edges of an image () +- Git repos paths () +- Uploading annotations for tasks with multiple jobs () -## [1.0.0-alpha] - 2020-03-31 +## \[1.0.0-alpha] - 2020-03-31 ### Added -- Data streaming using chunks (https://github.com/opencv/cvat/pull/1007) -- New UI: showing file names in UI (https://github.com/opencv/cvat/pull/1311) -- New UI: delete a point from context menu (https://github.com/opencv/cvat/pull/1292) +- Data streaming using chunks () +- New UI: showing file names in UI () +- New UI: delete a point from context menu () ### Fixed -- Git app cannot clone a repository (https://github.com/opencv/cvat/pull/1330) -- New UI: preview position in task details (https://github.com/opencv/cvat/pull/1312) -- AWS deployment (https://github.com/opencv/cvat/pull/1316) +- Git app cannot clone a repository () +- New UI: preview position in task details () +- AWS deployment () -## [0.6.1] - 2020-03-21 +## \[0.6.1] - 2020-03-21 ### Changed @@ -540,7 +586,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui ([#1270](https://github.com/opencv/cvat/pull/1270)) -## [0.6.0] - 2020-03-15 +## \[0.6.0] - 2020-03-15 ### Added @@ -578,19 +624,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Output labels for VOC format can be specified with Datumaro [#942](https://github.com/opencv/cvat/issues/942) - Annotations can be filtered before dumping with Datumaro [#994](https://github.com/opencv/cvat/issues/994) -## [0.5.2] - 2019-12-15 +## \[0.5.2] - 2019-12-15 ### Fixed - Frozen version of scikit-image==0.15 in requirements.txt because next releases don't support Python 3.5 -## [0.5.1] - 2019-10-17 +## \[0.5.1] - 2019-10-17 ### Added - Integration with Zenodo.org (DOI) -## [0.5.0] - 2019-09-12 +## \[0.5.0] - 2019-09-12 ### Added @@ -608,7 +654,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added in a command line model manager tester - Ability to dump/load annotations in several formats from UI (CVAT, Pascal VOC, YOLO, MS COCO, png mask, TFRecord) - Auth for REST API (api/v1/auth/): login, logout, register, ... -- Preview for the new CVAT UI (dashboard only) is available: http://localhost:9080/ +- Preview for the new CVAT UI (dashboard only) is available: - Added command line tool for performing common task operations (/utils/cli/) ### Changed @@ -642,26 +688,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded Django, djangorestframework, and other packages -## [0.4.2] - 2019-06-03 +## \[0.4.2] - 2019-06-03 ### Fixed - Fixed interaction with the server share in the auto annotation plugin -## [0.4.1] - 2019-05-14 +## \[0.4.1] - 2019-05-14 ### Fixed - JavaScript syntax incompatibility with Google Chrome versions less than 72 -## [0.4.0] - 2019-05-04 +## \[0.4.0] - 2019-05-04 ### Added - OpenVINO auto annotation: it is possible to upload a custom model and annotate images automatically. - Ability to rotate images/video in the client part (Ctrl+R, Shift+Ctrl+R shortcuts) (#305) - The ReID application for automatic bounding box merging has been added (#299) -- Keyboard shortcuts to switch next/previous default shape type (box, polygon etc) [Alt + <, Alt + >] (#316) +- Keyboard shortcuts to switch next/previous default shape type (box, polygon etc) (Alt + <, Alt + >) (#316) - Converter for VOC now supports interpolation tracks - REST API (/api/v1/\*, /api/docs) - Semi-automatic semantic segmentation with the [Deep Extreme Cut](http://www.vision.ee.ethz.ch/~cvlsegmentation/dextr/) work @@ -679,10 +725,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Django 2.1.5 (security fix, https://nvd.nist.gov/vuln/detail/CVE-2019-3498) +- Django 2.1.5 (security fix, [CVE-2019-3498](https://nvd.nist.gov/vuln/detail/CVE-2019-3498)) - Several scenarious which cause code 400 after undo/redo/save have been fixed (#315) -## [0.3.0] - 2018-12-29 +## \[0.3.0] - 2018-12-29 ### Added @@ -709,7 +755,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Polyshape editing method has been improved. You can redraw part of shape instead of points cloning. - Unified shortcut (Esc) for close any mode instead of different shortcuts (Alt+N, Alt+G, Alt+M etc.). - Dump file contains information about data source (e.g. video name, archive name, ...) -- Update requests library due to https://nvd.nist.gov/vuln/detail/CVE-2018-18074 +- Update requests library due to [CVE-2018-18074](https://nvd.nist.gov/vuln/detail/CVE-2018-18074) - Per task/job permissions to create/access/change/delete tasks and annotations - Documentation was improved - Timeout for creating tasks was increased (from 1h to 4h) (#136) @@ -728,7 +774,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dashboard loading has been accelerated (#156) - Text drawing outside of a frame in some cases (#202) -## [0.2.0] - 2018-09-28 +## \[0.2.0] - 2018-09-28 ### Added @@ -760,7 +806,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Several memory leaks - Inconsistent extensions between filenames in an annotation file and real filenames -## [0.1.2] - 2018-08-07 +## \[0.1.2] - 2018-08-07 ### Added @@ -778,7 +824,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - More strict verification for labels with attributes -## [0.1.1] - 2018-07-6 +## \[0.1.1] - 2018-07-6 ### Added @@ -789,7 +835,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GitHub documentation -## 0.1.0 - 2018-06-29 +## \[0.1.0] - 2018-06-29 ### Added diff --git a/Dockerfile b/Dockerfile index 917f2422..7e720aa7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -134,7 +134,7 @@ RUN if [ "$INSTALL_SOURCES" = "yes" ]; then \ fi COPY --from=build-image /tmp/openh264/openh264*.tar.gz /tmp/ffmpeg/ffmpeg*.tar.gz ${HOME}/sources/ -# Copy python virtual enviroment and FFmpeg binaries from build-image +# Copy python virtual environment and FFmpeg binaries from build-image COPY --from=build-image /opt/venv /opt/venv ENV PATH="/opt/venv/bin:${PATH}" COPY --from=build-image /opt/ffmpeg /usr diff --git a/Dockerfile.ui b/Dockerfile.ui index 513bc711..3bf5b66b 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -1,4 +1,4 @@ -FROM node:lts-alpine AS cvat-ui +FROM node:lts-buster AS cvat-ui ARG http_proxy ARG https_proxy @@ -15,8 +15,6 @@ ENV TERM=xterm \ LANG='C.UTF-8' \ LC_ALL='C.UTF-8' -RUN apk add python3 g++ make - # Install dependencies COPY cvat-core/package*.json /tmp/cvat-core/ COPY cvat-canvas/package*.json /tmp/cvat-canvas/ diff --git a/README.md b/README.md index c90303f5..697256d1 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,19 @@ annotate million of objects with different properties. Many UI and UX decisions are based on feedbacks from professional data annotation team. Try it online [cvat.org](https://cvat.org). -![CVAT screenshot](cvat/apps/documentation/static/documentation/images/cvat.jpg) +![CVAT screenshot](site/content/en/images/cvat.jpg) ## Documentation -- [Installation guide](cvat/apps/documentation/installation.md) -- [User's guide](cvat/apps/documentation/user_guide.md) -- [Django REST API documentation](#rest-api) +- [Contributing](https://openvinotoolkit.github.io/cvat/docs/contributing/) +- [Installation guide](https://openvinotoolkit.github.io/cvat/docs/administration/basics/installation/) +- [Manual](https://openvinotoolkit.github.io/cvat/docs/manual/) +- [Django REST API documentation](https://openvinotoolkit.github.io/cvat/docs/administration/basics/rest_api_guide/) - [Datumaro dataset framework](https://github.com/openvinotoolkit/datumaro/blob/develop/README.md) -- [Command line interface](utils/cli/) -- [XML annotation format](cvat/apps/documentation/xml_format.md) -- [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md) -- [Frequently asked questions](cvat/apps/documentation/faq.md) +- [Command line interface](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/cli/) +- [XML annotation format](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/) +- [AWS Deployment Guide](https://openvinotoolkit.github.io/cvat/docs/administration/basics/aws-deployment-guide/) +- [Frequently asked questions](https://openvinotoolkit.github.io/cvat/docs/faq/) - [Questions](#questions) ## Screencasts @@ -47,26 +48,30 @@ dataset framework allows additional dataset transformations via its command line tool and Python library. For more information about supported formats look at the -[documentation](cvat/apps/dataset_manager/formats/README.md#formats). - -| Annotation format | Import | Export | -| ----------------------------------------------------------------------------- | ------ | ------ | -| [CVAT for images](cvat/apps/documentation/xml_format.md#annotation) | X | X | -| [CVAT for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X | -| [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X | -| [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | -| Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | -| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | -| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | -| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) | X | X | -| [MOT](https://motchallenge.net/) | X | X | -| [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 | -| [Market-1501](https://www.aitribune.com/dataset/2018051063) | X | X | -| [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) | X | X | +[documentation](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/formats/). + + + +| Annotation format | Import | Export | +| --------------------------------------------------------------------------------------------------------- | ------ | ------ | +| [CVAT for images](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/#annotation) | X | X | +| [CVAT for a video](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/#interpolation) | X | X | +| [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X | +| [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | +| Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | +| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | +| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | +| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) | X | X | +| [MOT](https://motchallenge.net/) | X | X | +| [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 | +| [Market-1501](https://www.aitribune.com/dataset/2018051063) | X | X | +| [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) | X | X | + + ## Deep learning serverless functions for automatic labeling @@ -86,6 +91,7 @@ For more information about supported formats look at the | [Inside-Outside Guidance](/serverless/pytorch/shiyinzhang/iog/nuclio) | interactor | PyTorch | X | | | [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow | X | X | | [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow | X | X | +| [RetinaNet](serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio) | detector | PyTorch | X | X | @@ -97,7 +103,7 @@ are visible to users. Disabled features: -- [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) +- [Analytics: management and monitoring of data annotation team](https://openvinotoolkit.github.io/cvat/docs/administration/advanced/analytics/) Limitations: @@ -111,15 +117,6 @@ Prebuilt docker images for CVAT releases are available on Docker Hub: - [cvat_server](https://hub.docker.com/r/openvino/cvat_server) - [cvat_ui](https://hub.docker.com/r/openvino/cvat_ui) -## REST API - -Automatically generated Swagger documentation for Django REST API is available -on `/api/swagger`(default: `localhost:8080/api/swagger`). - -Swagger documentation is visiable on allowed hostes, Update environement -variable in docker-compose.yml file with cvat hosted machine IP or domain -name. Example - `ALLOWED_HOSTS: 'localhost, 127.0.0.1'`. - ## LICENSE Code released under the [MIT License](https://opensource.org/licenses/MIT). @@ -164,10 +161,10 @@ Other ways to ask questions and get our support: vision AI platform that fully integrates CVAT with scalable data processing and parallelized training pipelines. - [DataIsKey](https://dataiskey.eu/annotation-tool/) uses CVAT as their prime data labeling tool -to offer annotation services for projects of any size. - - - + to offer annotation services for projects of any size. +- [Human Protocol](https://hmt.ai) uses CVAT as a way of adding annotation service to the human protocol. + + [docker-server-pulls-img]: https://img.shields.io/docker/pulls/openvino/cvat_server.svg?style=flat-square&label=server%20pulls [docker-server-image-url]: https://hub.docker.com/r/openvino/cvat_server diff --git a/components/analytics/docker-compose.analytics.yml b/components/analytics/docker-compose.analytics.yml index 23e8720d..58d3874d 100644 --- a/components/analytics/docker-compose.analytics.yml +++ b/components/analytics/docker-compose.analytics.yml @@ -1,12 +1,10 @@ version: '3.3' services: - cvat_elasticsearch: + elasticsearch: container_name: cvat_elasticsearch image: cvat_elasticsearch networks: - default: - aliases: - - elasticsearch + - cvat build: context: ./components/analytics/elasticsearch args: @@ -15,18 +13,16 @@ services: - cvat_events:/usr/share/elasticsearch/data restart: always - cvat_kibana: + kibana: container_name: cvat_kibana image: cvat_kibana networks: - default: - aliases: - - kibana + - cvat build: context: ./components/analytics/kibana args: ELK_VERSION: 6.4.0 - depends_on: ['cvat_elasticsearch'] + depends_on: ['elasticsearch'] restart: always cvat_kibana_setup: @@ -35,6 +31,8 @@ services: volumes: ['./components/analytics/kibana:/home/django/kibana:ro'] depends_on: ['cvat'] working_dir: '/home/django' + networks: + - cvat entrypoint: [ 'bash', @@ -56,13 +54,11 @@ services: environment: no_proxy: elasticsearch,kibana,${no_proxy} - cvat_logstash: + logstash: container_name: cvat_logstash image: cvat_logstash networks: - default: - aliases: - - logstash + - cvat build: context: ./components/analytics/logstash args: @@ -73,7 +69,7 @@ services: LOGSTASH_OUTPUT_HOST: elasticsearch:9200 LOGSTASH_OUTPUT_USER: LOGSTASH_OUTPUT_PASS: - depends_on: ['cvat_elasticsearch'] + depends_on: ['elasticsearch'] restart: always cvat: diff --git a/components/serverless/docker-compose.serverless.yml b/components/serverless/docker-compose.serverless.yml index 8938d5c5..13f97132 100644 --- a/components/serverless/docker-compose.serverless.yml +++ b/components/serverless/docker-compose.serverless.yml @@ -1,13 +1,11 @@ version: '3.3' services: - serverless: + nuclio: container_name: nuclio image: quay.io/nuclio/dashboard:1.5.16-amd64 restart: always networks: - default: - aliases: - - nuclio + - cvat volumes: - /tmp:/tmp - /var/run/docker.sock:/var/run/docker.sock diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 126dc885..ee0a0fb3 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -245,4 +245,4 @@ canvas.draw({ You can call setup() during editing, dragging, and resizing only to update objects, not to change a frame. You can change frame during draw only when you do not redraw an existing object -Other methods do not change state and can be used everytime. +Other methods do not change state and can be used at any time. diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 4419de07..b0ec3bcb 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.4.3", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1995,17 +1995,6 @@ "postcss-value-parser": "^4.0.2" }, "dependencies": { - "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" - } - }, "caniuse-lite": { "version": "1.0.30001016", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz", @@ -2015,40 +2004,25 @@ "electron-to-chromium": { "version": "1.3.322", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", - "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", - "dev": true + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==" }, "node-releases": { "version": "1.1.44", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.44.tgz", "integrity": "sha512-NwbdvJyR7nrcGrXvKAvzc5raj/NkoJudkarh2yIpJ4t0NH4aqjUDz/486P+ynIW5eokKOfzGNRdYoLfBlomruw==", - "dev": true, "requires": { "semver": "^6.3.0" } }, - "postcss": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", - "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -2367,14 +2341,42 @@ } }, "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001228", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", + "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.737", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz", + "integrity": "sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + } } }, "buffer-from": { @@ -2811,12 +2813,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3121,17 +3117,6 @@ "schema-utils": "^2.6.0" }, "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "schema-utils": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", @@ -3145,8 +3130,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -3387,9 +3371,9 @@ "dev": true }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "dev": true, "requires": { "ip": "^1.1.0", @@ -3501,12 +3485,6 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "electron-to-chromium": { - "version": "1.3.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz", - "integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==", - "dev": true - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -6738,15 +6716,6 @@ } } }, - "node-releases": { - "version": "1.1.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.25.tgz", - "integrity": "sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==", - "dev": true, - "requires": { - "semver": "^5.3.0" - } - }, "node-sass": { "version": "4.14.1", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", @@ -6915,9 +6884,9 @@ "dev": true }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, "npm-run-path": { @@ -7604,9 +7573,9 @@ "dev": true }, "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10461,30 +10430,15 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", - "escalade": "^3.1.1", - "node-releases": "^1.1.70" - } - }, "caniuse-lite": { "version": "1.0.30001185", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", - "integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==", - "dev": true + "integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==" }, "electron-to-chromium": { "version": "1.3.654", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.654.tgz", - "integrity": "sha512-Zy2gc/c8KYFg2GkNr7Ruzh5tPEZpFm7EyXqZTFasm1YRDJtpyBRGaOuM0H3t6SuIP53qX4kNmtO9t0WjhBjE9A==", - "dev": true + "integrity": "sha512-Zy2gc/c8KYFg2GkNr7Ruzh5tPEZpFm7EyXqZTFasm1YRDJtpyBRGaOuM0H3t6SuIP53qX4kNmtO9t0WjhBjE9A==" }, "enhanced-resolve": { "version": "5.7.0", @@ -10559,8 +10513,7 @@ "node-releases": { "version": "1.1.70", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", - "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", - "dev": true + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==" }, "schema-utils": { "version": "3.0.0", @@ -10976,9 +10929,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dev": true, "requires": { "async-limiter": "~1.0.0" diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 9612021d..9fa50052 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.4.3", + "version": "2.5.0", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 181fa15c..993745ba 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -155,6 +155,22 @@ polyline.cvat_canvas_shape_splitting { fill: blueviolet; } +.cvat_canvas_interact_intermediate_shape { + @extend .cvat_canvas_shape; +} + +.cvat_canvas_removable_interaction_point { + cursor: + url( + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K' + ) 10 10, + auto; +} + +.cvat_canvas_interact_intermediate_shape_point { + pointer-events: none; +} + .svg_select_boundingRect { opacity: 0; pointer-events: none; diff --git a/cvat-canvas/src/typescript/autoborderHandler.ts b/cvat-canvas/src/typescript/autoborderHandler.ts index 36a8e3d5..caa29693 100644 --- a/cvat-canvas/src/typescript/autoborderHandler.ts +++ b/cvat-canvas/src/typescript/autoborderHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -28,14 +28,14 @@ export class AutoborderHandlerImpl implements AutoborderHandler { private auxiliaryGroupID: number | null; private auxiliaryClicks: number[]; private listeners: Record< - number, - Record< - number, - { - click: (event: MouseEvent) => void; - dblclick: (event: MouseEvent) => void; - } - > + number, + Record< + number, + { + click: (event: MouseEvent) => void; + dblclick: (event: MouseEvent) => void; + } + > >; public constructor(frameContent: SVGSVGElement) { @@ -172,12 +172,11 @@ export class AutoborderHandlerImpl implements AutoborderHandler { } else { // sign defines bypass direction const landmarks = this.auxiliaryClicks; - const sign = - Math.sign(landmarks[2] - landmarks[0]) * - Math.sign(landmarks[1] - landmarks[0]) * - Math.sign(landmarks[2] - landmarks[1]); + const sign = Math.sign(landmarks[2] - landmarks[0]) + * Math.sign(landmarks[1] - landmarks[0]) + * Math.sign(landmarks[2] - landmarks[1]); - // go via a polygon and get vertexes + // go via a polygon and get vertices // the first vertex has been already drawn const way = []; for (let i = landmarks[0] + sign; ; i += sign) { diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 4a9fc3bd..0da3bce4 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -58,6 +58,7 @@ export interface Configuration { showProjections?: boolean; forceDisableEditing?: boolean; intelligentPolygonCrop?: boolean; + forceFrameUpdate?: boolean; } export interface DrawData { @@ -77,10 +78,14 @@ export interface InteractionData { crosshair?: boolean; minPosVertices?: number; minNegVertices?: number; - enableNegVertices?: boolean; + startWithBox?: boolean; enableThreshold?: boolean; enableSliding?: boolean; allowRemoveOnlyLast?: boolean; + intermediateShape?: { + shapeType: string; + points: number[]; + }; } export interface InteractionResult { @@ -388,8 +393,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } } - - if (frameData.number === this.data.imageID) { + if (frameData.number === this.data.imageID && !this.data.configuration.forceFrameUpdate) { this.data.zLayer = zLayer; this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); @@ -421,7 +425,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }) .catch((exception: any): void => { this.data.exception = exception; - this.notify(UpdateReasons.DATA_FAILED); + // don't notify when the frame is no longer needed + if (typeof exception !== 'number' || exception === this.data.imageID) { + this.notify(UpdateReasons.DATA_FAILED); + } throw exception; }); } @@ -548,7 +555,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } - if (interactionData.enabled) { + if (interactionData.enabled && !interactionData.intermediateShape) { if (this.data.interactionData.enabled) { throw new Error('Interaction has been already started'); } else if (!interactionData.shapeType) { @@ -645,6 +652,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.intelligentPolygonCrop = configuration.intelligentPolygonCrop; } + if (typeof configuration.forceFrameUpdate === 'boolean') { + this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate; + } + this.notify(UpdateReasons.CONFIG_UPDATED); } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 95e89b7d..f1a9f466 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -23,6 +23,7 @@ import consts from './consts'; import { translateToSVG, translateFromSVG, + translateToCanvas, pointsToNumberArray, parsePoints, displayShapeSize, @@ -103,7 +104,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private translateToCanvas(points: number[]): number[] { const { offset } = this.controller.geometry; - return points.map((coord: number): number => coord + offset); + return translateToCanvas(offset, points); } private translateFromCanvas(points: number[]): number[] { @@ -1267,9 +1268,11 @@ export class CanvasViewImpl implements CanvasView, Listener { } } else if (reason === UpdateReasons.INTERACT) { const data: InteractionData = this.controller.interactionData; - if (data.enabled && this.mode === Mode.IDLE) { - this.canvas.style.cursor = 'crosshair'; - this.mode = Mode.INTERACT; + if (data.enabled && (this.mode === Mode.IDLE || data.intermediateShape)) { + if (!data.intermediateShape) { + this.canvas.style.cursor = 'crosshair'; + this.mode = Mode.INTERACT; + } this.interactionHandler.interact(data); } else { this.canvas.style.cursor = ''; diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index ded91714..435c6d78 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -352,7 +352,7 @@ function setupCuboidPoints(points: Point[]): any[] { ? Math.abs(points[1].y - points[0].y) : Math.abs(points[1].y - points[2].y); - // seperate into left and right point + // separate into left and right point // we pick the first and third point because we know assume they will be on // opposite corners if (points[0].x < points[2].x) { diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index f3e08e07..8bf38eee 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -311,8 +311,9 @@ export class DrawHandlerImpl implements DrawHandler { // We check if it is activated with remember function if (this.drawInstance.remember('_paintHandler')) { if ( - this.drawData.shapeType !== 'rectangle' - && this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC + ['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) + || (this.drawData.shapeType === 'cuboid' + && this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS) ) { // Check for unsaved drawn shapes this.drawInstance.draw('done'); @@ -462,11 +463,11 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.draw('point', e); } else { this.drawInstance.draw('update', e); - const deltaTreshold = 15; + const deltaThreshold = 15; const dx = (e.clientX - lastDrawnPoint.x) ** 2; const dy = (e.clientY - lastDrawnPoint.y) ** 2; const delta = Math.sqrt(dx + dy); - if (delta > deltaTreshold) { + if (delta > deltaThreshold) { this.drawInstance.draw('point', e); } } @@ -574,7 +575,7 @@ export class DrawHandlerImpl implements DrawHandler { .on('drawstop', (e: Event): void => { const bbox = (e.target as SVGRectElement).getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); - const { shapeType } = this.drawData; + const { shapeType, redraw: clientID } = this.drawData; this.release(); if (this.canceled) return; @@ -584,6 +585,7 @@ export class DrawHandlerImpl implements DrawHandler { { shapeType, points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]), + clientID, }, Date.now() - this.startTimestamp, ); diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index 3c97c776..aad596e4 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -93,11 +93,11 @@ export class EditHandlerImpl implements EditHandler { if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) { (this.editLine as any).draw('point', e); } else { - const deltaTreshold = 15; + const deltaThreshold = 15; const dxsqr = (e.clientX - lastDrawnPoint.x) ** 2; const dysqr = (e.clientY - lastDrawnPoint.y) ** 2; const delta = Math.sqrt(dxsqr + dysqr); - if (delta > deltaTreshold) { + if (delta > deltaThreshold) { (this.editLine as any).draw('point', e); } } diff --git a/cvat-canvas/src/typescript/interactionHandler.ts b/cvat-canvas/src/typescript/interactionHandler.ts index a0c18a82..dcb8101e 100644 --- a/cvat-canvas/src/typescript/interactionHandler.ts +++ b/cvat-canvas/src/typescript/interactionHandler.ts @@ -5,7 +5,9 @@ import * as SVG from 'svg.js'; import consts from './consts'; import Crosshair from './crosshair'; -import { translateToSVG } from './shared'; +import { + translateToSVG, PropType, stringifyPoints, translateToCanvas, +} from './shared'; import { InteractionData, InteractionResult, Geometry } from './canvasModel'; export interface InteractionHandler { @@ -26,6 +28,8 @@ export class InteractionHandlerImpl implements InteractionHandler { private crosshair: Crosshair; private threshold: SVG.Rect | null; private thresholdRectSize: number; + private intermediateShape: PropType; + private drawnIntermediateShape: SVG.Shape; private prepareResult(): InteractionResult[] { return this.interactionShapes.map( @@ -65,8 +69,10 @@ export class InteractionHandlerImpl implements InteractionHandler { return enabled && !ctrlKey && !!interactionShapes.length; } - const minPosVerticesAchieved = typeof minPosVertices === 'undefined' || minPosVertices <= positiveShapes.length; - const minNegVerticesAchieved = typeof minNegVertices === 'undefined' || minPosVertices <= negativeShapes.length; + const minPosVerticesDefined = Number.isInteger(minPosVertices); + const minNegVerticesDefined = Number.isInteger(minNegVertices) && minNegVertices >= 0; + const minPosVerticesAchieved = !minPosVerticesDefined || minPosVertices <= positiveShapes.length; + const minNegVerticesAchieved = !minNegVerticesDefined || minNegVertices <= negativeShapes.length; const minimumVerticesAchieved = minPosVerticesAchieved && minNegVerticesAchieved; return enabled && !ctrlKey && minimumVerticesAchieved && shapesWereUpdated; } @@ -91,10 +97,10 @@ export class InteractionHandlerImpl implements InteractionHandler { private interactPoints(): void { const eventListener = (e: MouseEvent): void => { - if ((e.button === 0 || (e.button === 2 && this.interactionData.enableNegVertices)) && !e.altKey) { + if ((e.button === 0 || (e.button === 2 && this.interactionData.minNegVertices >= 0)) && !e.altKey) { e.preventDefault(); const [cx, cy] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]); - if (!this.isWithingFrame(cx, cy)) return; + if (!this.isWithinFrame(cx, cy)) return; if (!this.isWithinThreshold(cx, cy)) return; this.currentInteractionShape = this.canvas @@ -121,8 +127,10 @@ export class InteractionHandlerImpl implements InteractionHandler { } } + self.addClass('cvat_canvas_removable_interaction_point'); self.attr({ 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale, + r: (consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale, }); self.on('mousedown', (_e: MouseEvent): void => { @@ -132,6 +140,9 @@ export class InteractionHandlerImpl implements InteractionHandler { this.interactionShapes = this.interactionShapes.filter( (shape: SVG.Shape): boolean => shape !== self, ); + if (this.interactionData.startWithBox && this.interactionShapes.length === 1) { + this.interactionShapes[0].style({ visibility: '' }); + } this.shapesWereUpdated = true; if (this.shouldRaiseEvent(_e.ctrlKey)) { this.onInteraction(this.prepareResult(), true, false); @@ -140,8 +151,10 @@ export class InteractionHandlerImpl implements InteractionHandler { }); self.on('mouseleave', (): void => { + self.removeClass('cvat_canvas_removable_interaction_point'); self.attr({ 'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale, + r: consts.BASE_POINT_SIZE / this.geometry.scale, }); self.off('mousedown'); @@ -149,11 +162,11 @@ export class InteractionHandlerImpl implements InteractionHandler { } }; - // clear this listener in relese() + // clear this listener in release() this.canvas.on('mousedown.interaction', eventListener); } - private interactRectangle(): void { + private interactRectangle(shouldFinish: boolean, onContinue?: () => void): void { let initialized = false; const eventListener = (e: MouseEvent): void => { if (e.button === 0 && !e.altKey) { @@ -170,11 +183,15 @@ export class InteractionHandlerImpl implements InteractionHandler { this.canvas.on('mousedown.interaction', eventListener); this.currentInteractionShape .on('drawstop', (): void => { + this.canvas.off('mousedown.interaction', eventListener); this.interactionShapes.push(this.currentInteractionShape); this.shapesWereUpdated = true; - this.canvas.off('mousedown.interaction', eventListener); - this.interact({ enabled: false }); + if (shouldFinish) { + this.interact({ enabled: false }); + } else if (onContinue) { + onContinue(); + } }) .addClass('cvat_canvas_shape_drawing') .attr({ @@ -194,15 +211,25 @@ export class InteractionHandlerImpl implements InteractionHandler { private startInteraction(): void { if (this.interactionData.shapeType === 'rectangle') { - this.interactRectangle(); + this.interactRectangle(true); } else if (this.interactionData.shapeType === 'points') { - this.interactPoints(); + if (this.interactionData.startWithBox) { + this.interactRectangle(false, (): void => this.interactPoints()); + } else { + this.interactPoints(); + } } else { throw new Error('Interactor implementation supports only rectangle and points'); } } private release(): void { + if (this.drawnIntermediateShape) { + this.selectize(false, this.drawnIntermediateShape); + this.drawnIntermediateShape.remove(); + this.drawnIntermediateShape = null; + } + if (this.crosshair) { this.removeCrosshair(); } @@ -234,13 +261,75 @@ export class InteractionHandlerImpl implements InteractionHandler { return xDiff < this.thresholdRectSize / 2 && yDiff < this.thresholdRectSize / 2; } - private isWithingFrame(x: number, y: number): boolean { + private isWithinFrame(x: number, y: number): boolean { const { offset, image } = this.geometry; const { width, height } = image; const [imageX, imageY] = [Math.round(x - offset), Math.round(y - offset)]; return imageX >= 0 && imageX < width && imageY >= 0 && imageY < height; } + private updateIntermediateShape(): void { + const { intermediateShape, geometry } = this; + if (this.drawnIntermediateShape) { + this.selectize(false, this.drawnIntermediateShape); + this.drawnIntermediateShape.remove(); + } + + if (!intermediateShape) return; + const { shapeType, points } = intermediateShape; + if (shapeType === 'polygon') { + const erroredShape = shapeType === 'polygon' && points.length < 3 * 2; + this.drawnIntermediateShape = this.canvas + .polygon(stringifyPoints(translateToCanvas(geometry.offset, points))) + .attr({ + 'color-rendering': 'optimizeQuality', + 'shape-rendering': 'geometricprecision', + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + stroke: erroredShape ? 'red' : 'black', + fill: 'none', + }) + .addClass('cvat_canvas_interact_intermediate_shape'); + this.selectize(true, this.drawnIntermediateShape, erroredShape); + } else { + throw new Error( + `Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`, + ); + } + } + + private selectize(value: boolean, shape: SVG.Element, erroredShape = false): void { + const self = this; + + if (value) { + (shape as any).selectize(value, { + deepSelect: true, + pointSize: consts.BASE_POINT_SIZE / self.geometry.scale, + rotationPoint: false, + classPoints: 'cvat_canvas_interact_intermediate_shape_point', + pointType(cx: number, cy: number): SVG.Circle { + return this.nested + .circle(this.options.pointSize) + .stroke(erroredShape ? 'red' : 'black') + .fill('black') + .center(cx, cy) + .attr({ + 'fill-opacity': 1, + 'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale, + }); + }, + }); + } else { + (shape as any).selectize(false, { + deepSelect: true, + }); + } + + const handler = shape.remember('_selectHandler'); + if (handler && handler.nested) { + handler.nested.fill(shape.attr('fill')); + } + } + public constructor( onInteraction: ( shapes: InteractionResult[] | null, @@ -264,6 +353,8 @@ export class InteractionHandlerImpl implements InteractionHandler { this.crosshair = new Crosshair(); this.threshold = null; this.thresholdRectSize = 300; + this.intermediateShape = null; + this.drawnIntermediateShape = null; this.cursorPosition = { x: 0, y: 0, @@ -280,7 +371,7 @@ export class InteractionHandlerImpl implements InteractionHandler { } if (this.interactionData.enableSliding && this.interactionShapes.length) { - if (this.isWithingFrame(x, y)) { + if (this.isWithinFrame(x, y)) { if (this.interactionData.enableThreshold && !this.isWithinThreshold(x, y)) return; this.onInteraction( [ @@ -334,16 +425,36 @@ export class InteractionHandlerImpl implements InteractionHandler { : [...this.interactionShapes]; for (const shape of shapesToBeScaled) { if (shape.type === 'circle') { - (shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale); - shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale); + if (shape.hasClass('cvat_canvas_removable_interaction_point')) { + (shape as SVG.Circle).radius((consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale); + shape.attr('stroke-width', consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale); + } else { + (shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale); + shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale); + } } else { shape.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale); } } + + for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) { + element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / (2 * this.geometry.scale)}`); + element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`); + } + + if (this.drawnIntermediateShape) { + this.drawnIntermediateShape.stroke({ width: consts.BASE_STROKE_WIDTH / this.geometry.scale }); + } } public interact(interactionData: InteractionData): void { - if (interactionData.enabled) { + if (interactionData.intermediateShape) { + this.intermediateShape = interactionData.intermediateShape; + this.updateIntermediateShape(); + if (this.interactionData.startWithBox) { + this.interactionShapes[0].style({ visibility: 'hidden' }); + } + } else if (interactionData.enabled) { this.interactionData = interactionData; this.initInteraction(); this.startInteraction(); diff --git a/cvat-canvas/src/typescript/master.ts b/cvat-canvas/src/typescript/master.ts index 9380e041..2bdf4cf2 100644 --- a/cvat-canvas/src/typescript/master.ts +++ b/cvat-canvas/src/typescript/master.ts @@ -1,11 +1,9 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2021 Intel Corporation // // SPDX-License-Identifier: MIT export interface Master { subscribe(listener: Listener): void; - unsubscribe(listener: Listener): void; - unsubscribeAll(): void; notify(reason: string): void; } @@ -24,18 +22,6 @@ export class MasterImpl implements Master { this.listeners.push(listener); } - public unsubscribe(listener: Listener): void { - for (let i = 0; i < this.listeners.length; i++) { - if (this.listeners[i] === listener) { - this.listeners.splice(i, 1); - } - } - } - - public unsubscribeAll(): void { - this.listeners = []; - } - public notify(reason: string): void { for (const listener of this.listeners) { listener.notify(this, reason); diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 9ffa080e..452982a9 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -181,3 +181,9 @@ export function vectorLength(vector: Vector2D): number { const sqrJ = vector.j ** 2; return Math.sqrt(sqrI + sqrJ); } + +export function translateToCanvas(offset: number, points: number[]): number[] { + return points.map((coord: number): number => coord + offset); +} + +export type PropType = T[Prop]; diff --git a/cvat-canvas/src/typescript/splitHandler.ts b/cvat-canvas/src/typescript/splitHandler.ts index b8224d33..69b602ab 100644 --- a/cvat-canvas/src/typescript/splitHandler.ts +++ b/cvat-canvas/src/typescript/splitHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -43,7 +43,7 @@ export class SplitHandlerImpl implements SplitHandler { } private closeSplitting(): void { - // Split done is true if an object was splitted + // Split done is true if an object was split // Split also can be called with { enabled: false } without splitting an object if (!this.splitDone) { this.onSplitDone(null); diff --git a/cvat-canvas3d/README.md b/cvat-canvas3d/README.md index 7ce318f7..17566e61 100644 --- a/cvat-canvas3d/README.md +++ b/cvat-canvas3d/README.md @@ -26,11 +26,20 @@ npm run build -- --mode=development # without a minification ```ts interface Canvas3d { - html(): HTMLDivElement; - setup(frameData: any): void; - mode(): Mode; + html(): ViewsDOM; + setup(frameData: any, objectStates: any[]): void; isAbleToChangeFrame(): boolean; + mode(): Mode; render(): void; + keyControls(keys: KeyboardEvent): void; + draw(drawData: DrawData): void; + cancel(): void; + dragCanvas(enable: boolean): void; + activate(clientID: number | null, attributeID?: number): void; + configureShapes(shapeProperties: ShapeProperties): void; + fitCanvas(): void; + fit(): void; + group(groupData: GroupData): void; } ``` @@ -44,5 +53,9 @@ console.log('Version ', window.canvas.CanvasVersion); console.log('Current mode is ', window.canvas.mode()); // Put canvas to a html container -htmlContainer.appendChild(canvas.html()); +const views = canvas.html(); +htmlContainer.appendChild(views.perspective); +htmlContainer.appendChild(views.top); +htmlContainer.appendChild(views.side); +htmlContainer.appendChild(views.front); ``` diff --git a/cvat-canvas3d/src/typescript/canvas3d.ts b/cvat-canvas3d/src/typescript/canvas3d.ts index 139321fe..02a375cd 100644 --- a/cvat-canvas3d/src/typescript/canvas3d.ts +++ b/cvat-canvas3d/src/typescript/canvas3d.ts @@ -5,10 +5,17 @@ import pjson from '../../package.json'; import { Canvas3dController, Canvas3dControllerImpl } from './canvas3dController'; import { - Canvas3dModel, Canvas3dModelImpl, Mode, DrawData, ViewType, MouseInteraction, + Canvas3dModel, + Canvas3dModelImpl, + Mode, + DrawData, + ViewType, + MouseInteraction, + ShapeProperties, + GroupData, } from './canvas3dModel'; import { - Canvas3dView, Canvas3dViewImpl, ViewsDOM, CAMERA_ACTION, + Canvas3dView, Canvas3dViewImpl, ViewsDOM, CameraAction, } from './canvas3dView'; import { Master } from './master'; @@ -16,19 +23,24 @@ const Canvas3dVersion = pjson.version; interface Canvas3d { html(): ViewsDOM; - setup(frameData: any): void; + setup(frameData: any, objectStates: any[]): void; isAbleToChangeFrame(): boolean; mode(): Mode; render(): void; keyControls(keys: KeyboardEvent): void; - mouseControls(type: string, event: MouseEvent): void; draw(drawData: DrawData): void; cancel(): void; + dragCanvas(enable: boolean): void; + activate(clientID: number | null, attributeID?: number): void; + configureShapes(shapeProperties: ShapeProperties): void; + fitCanvas(): void; + fit(): void; + group(groupData: GroupData): void; } class Canvas3dImpl implements Canvas3d { - private model: Canvas3dModel & Master; - private controller: Canvas3dController; + private readonly model: Canvas3dModel & Master; + private readonly controller: Canvas3dController; private view: Canvas3dView; public constructor() { @@ -45,10 +57,6 @@ class Canvas3dImpl implements Canvas3d { this.view.keyControls(keys); } - public mouseControls(type: MouseInteraction, event: MouseEvent): void { - this.view.mouseControls(type, event); - } - public render(): void { this.view.render(); } @@ -57,14 +65,18 @@ class Canvas3dImpl implements Canvas3d { this.model.draw(drawData); } - public setup(frameData: any): void { - this.model.setup(frameData); + public setup(frameData: any, objectStates: any[]): void { + this.model.setup(frameData, objectStates); } public mode(): Mode { return this.model.mode; } + public group(groupData: GroupData): void { + this.model.group(groupData); + } + public isAbleToChangeFrame(): boolean { return this.model.isAbleToChangeFrame(); } @@ -72,8 +84,28 @@ class Canvas3dImpl implements Canvas3d { public cancel(): void { this.model.cancel(); } + + public dragCanvas(enable: boolean): void { + this.model.dragCanvas(enable); + } + + public configureShapes(shapeProperties: ShapeProperties): void { + this.model.configureShapes(shapeProperties); + } + + public activate(clientID: number | null, attributeID: number | null = null): void { + this.model.activate(String(clientID), attributeID); + } + + public fit(): void { + this.model.fit(); + } + + public fitCanvas(): void { + this.model.fit(); + } } export { - Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CAMERA_ACTION, + Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CameraAction, ViewsDOM, }; diff --git a/cvat-canvas3d/src/typescript/canvas3dController.ts b/cvat-canvas3d/src/typescript/canvas3dController.ts index 5320fc03..00b08f6c 100644 --- a/cvat-canvas3d/src/typescript/canvas3dController.ts +++ b/cvat-canvas3d/src/typescript/canvas3dController.ts @@ -2,11 +2,18 @@ // // SPDX-License-Identifier: MIT -import { Canvas3dModel, Mode, DrawData } from './canvas3dModel'; +import { + Canvas3dModel, Mode, DrawData, ActiveElement, FocusData, GroupData, +} from './canvas3dModel'; export interface Canvas3dController { readonly drawData: DrawData; + readonly activeElement: ActiveElement; + readonly selected: any; + readonly focused: FocusData; + readonly groupData: GroupData; mode: Mode; + group(groupData: GroupData): void; } export class Canvas3dControllerImpl implements Canvas3dController { @@ -27,4 +34,24 @@ export class Canvas3dControllerImpl implements Canvas3dController { public get drawData(): DrawData { return this.model.data.drawData; } + + public get activeElement(): ActiveElement { + return this.model.data.activeElement; + } + + public get selected(): any { + return this.model.data.selected; + } + + public get focused(): any { + return this.model.data.focusData; + } + + public get groupData(): GroupData { + return this.model.groupData; + } + + public group(groupData: GroupData): void { + this.model.group(groupData); + } } diff --git a/cvat-canvas3d/src/typescript/canvas3dModel.ts b/cvat-canvas3d/src/typescript/canvas3dModel.ts index 0b0aef59..41c566cf 100644 --- a/cvat-canvas3d/src/typescript/canvas3dModel.ts +++ b/cvat-canvas3d/src/typescript/canvas3dModel.ts @@ -9,6 +9,16 @@ export interface Size { height: number; } +export interface ActiveElement { + clientID: string | null; + attributeID: number | null; +} + +export interface GroupData { + enabled: boolean; + grouped?: []; +} + export interface Image { renderWidth: number; renderHeight: number; @@ -19,6 +29,7 @@ export interface DrawData { enabled: boolean; initialState?: any; redraw?: number; + shapeType?: string; } export enum FrameZoom { @@ -26,6 +37,13 @@ export enum FrameZoom { MAX = 10, } +export enum Planes { + TOP = 'topPlane', + SIDE = 'sidePlane', + FRONT = 'frontPlane', + PERSPECTIVE = 'perspectivePlane', +} + export enum ViewType { PERSPECTIVE = 'perspective', TOP = 'top', @@ -39,14 +57,29 @@ export enum MouseInteraction { HOVER = 'hover', } +export interface FocusData { + clientID: string | null; +} + +export interface ShapeProperties { + opacity: number; + outlined: boolean; + outlineColor: string; + selectedOpacity: number; + colorBy: string; +} + export enum UpdateReasons { IMAGE_CHANGED = 'image_changed', OBJECTS_UPDATED = 'objects_updated', - FITTED_CANVAS = 'fitted_canvas', DRAW = 'draw', SELECT = 'select', CANCEL = 'cancel', DATA_FAILED = 'data_failed', + DRAG_CANVAS = 'drag_canvas', + SHAPE_ACTIVATED = 'shape_activated', + GROUP = 'group', + FITTED_CANVAS = 'fitted_canvas', } export enum Mode { @@ -56,9 +89,13 @@ export enum Mode { DRAW = 'draw', EDIT = 'edit', INTERACT = 'interact', + DRAG_CANVAS = 'drag_canvas', + GROUP = 'group', + BUSY = 'busy', } export interface Canvas3dDataModel { + activeElement: ActiveElement; canvasSize: Size; image: Image | null; imageID: number | null; @@ -66,16 +103,29 @@ export interface Canvas3dDataModel { imageSize: Size; drawData: DrawData; mode: Mode; + objectUpdating: boolean; exception: Error | null; + objects: any[]; + groupedObjects: any[]; + focusData: FocusData; + selected: any; + shapeProperties: ShapeProperties; + groupData: GroupData; } export interface Canvas3dModel { mode: Mode; data: Canvas3dDataModel; - setup(frameData: any): void; + readonly groupData: GroupData; + setup(frameData: any, objectStates: any[]): void; isAbleToChangeFrame(): boolean; draw(drawData: DrawData): void; cancel(): void; + dragCanvas(enable: boolean): void; + activate(clientID: string | null, attributeID: number | null): void; + configureShapes(shapeProperties: any): void; + fit(): void; + group(groupData: GroupData): void; } export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { @@ -84,10 +134,17 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { public constructor() { super(); this.data = { + activeElement: { + clientID: null, + attributeID: null, + }, canvasSize: { height: 0, width: 0, }, + objectUpdating: false, + objects: [], + groupedObjects: [], image: null, imageID: null, imageOffset: 0, @@ -101,37 +158,71 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { }, mode: Mode.IDLE, exception: null, + focusData: { + clientID: null, + }, + groupData: { + enabled: false, + grouped: [], + }, + selected: null, + shapeProperties: { + opacity: 40, + outlined: false, + outlineColor: '#000000', + selectedOpacity: 60, + colorBy: 'Label', + }, }; } - public setup(frameData: any): void { + public setup(frameData: any, objectStates: any[]): void { if (this.data.imageID !== frameData.number) { - this.data.imageID = frameData.number; - frameData - .data((): void => { - this.data.image = null; - this.notify(UpdateReasons.IMAGE_CHANGED); - }) - .then((data: Image): void => { - if (frameData.number !== this.data.imageID) { - // already another image - return; - } - - this.data.imageSize = { - height: frameData.height as number, - width: frameData.width as number, - }; - - this.data.image = data; - this.notify(UpdateReasons.IMAGE_CHANGED); - }) - .catch((exception: any): void => { - this.data.exception = exception; - this.notify(UpdateReasons.DATA_FAILED); - throw exception; - }); + if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + } + if ([Mode.EDIT, Mode.BUSY].includes(this.data.mode)) { + return; + } + + if (frameData.number === this.data.imageID) { + if (this.data.objectUpdating) { + return; + } + this.data.objects = objectStates; + this.data.objectUpdating = true; + this.notify(UpdateReasons.OBJECTS_UPDATED); + return; } + + this.data.imageID = frameData.number; + frameData + .data((): void => { + this.data.image = null; + this.notify(UpdateReasons.IMAGE_CHANGED); + }) + .then((data: Image): void => { + if (frameData.number !== this.data.imageID) { + // already another image + return; + } + + this.data.imageSize = { + height: frameData.height as number, + width: frameData.width as number, + }; + + this.data.image = data; + this.notify(UpdateReasons.IMAGE_CHANGED); + this.data.objects = objectStates; + this.notify(UpdateReasons.OBJECTS_UPDATED); + }) + .catch((exception: any): void => { + this.data.exception = exception; + this.notify(UpdateReasons.DATA_FAILED); + throw exception; + }); } public set mode(value: Mode) { @@ -143,23 +234,110 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { } public isAbleToChangeFrame(): boolean { - const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode) + const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT, Mode.BUSY].includes(this.data.mode) || (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); - return !isUnable; } public draw(drawData: DrawData): void { - if (drawData.enabled && this.data.drawData.enabled) { + if (drawData.enabled && this.data.drawData.enabled && !drawData.initialState) { throw new Error('Drawing has been already started'); } + if ([Mode.DRAW, Mode.EDIT].includes(this.data.mode) && !drawData.initialState) { + return; + } this.data.drawData.enabled = drawData.enabled; this.data.mode = Mode.DRAW; + if (typeof drawData.redraw === 'number') { + const clientID = drawData.redraw; + const [state] = this.data.objects.filter((_state: any): boolean => _state.clientID === clientID); + + if (state) { + this.data.drawData = { ...drawData }; + this.data.drawData.initialState = { ...this.data.drawData.initialState, label: state.label }; + this.data.drawData.shapeType = state.shapeType; + } else { + return; + } + } else { + this.data.drawData = { ...drawData }; + if (this.data.drawData.initialState) { + this.data.drawData.shapeType = this.data.drawData.initialState.shapeType; + } + } this.notify(UpdateReasons.DRAW); } public cancel(): void { this.notify(UpdateReasons.CANCEL); } + + public dragCanvas(enable: boolean): void { + if (enable && this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (!enable && this.data.mode !== Mode.DRAG_CANVAS) { + throw Error(`Canvas is not in the drag mode. Action: ${this.data.mode}`); + } + + this.data.mode = enable ? Mode.DRAG_CANVAS : Mode.IDLE; + this.notify(UpdateReasons.DRAG_CANVAS); + } + + public activate(clientID: string, attributeID: number | null): void { + if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) { + return; + } + if (this.data.mode !== Mode.IDLE) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + if (typeof clientID === 'number') { + const [state] = this.data.objects.filter((_state: any): boolean => _state.clientID === clientID); + if (!state || state.objectType === 'tag') { + return; + } + } + this.data.activeElement = { + clientID, + attributeID, + }; + this.notify(UpdateReasons.SHAPE_ACTIVATED); + } + + public group(groupData: GroupData): void { + if (![Mode.IDLE, Mode.GROUP].includes(this.data.mode)) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (this.data.groupData.enabled && groupData.enabled) { + return; + } + + if (!this.data.groupData.enabled && !groupData.enabled) { + return; + } + this.data.mode = groupData.enabled ? Mode.GROUP : Mode.IDLE; + this.data.groupData = { ...this.data.groupData, ...groupData }; + this.notify(UpdateReasons.GROUP); + } + + public configureShapes(shapeProperties: ShapeProperties): void { + this.data.drawData.enabled = false; + this.data.mode = Mode.IDLE; + this.cancel(); + this.data.shapeProperties = { + ...shapeProperties, + }; + this.notify(UpdateReasons.OBJECTS_UPDATED); + } + + public fit(): void { + this.notify(UpdateReasons.FITTED_CANVAS); + } + + public get groupData(): GroupData { + return { ...this.data.groupData }; + } } diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index 866e7cc2..5f450ac5 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -9,18 +9,19 @@ import { Canvas3dController } from './canvas3dController'; import { Listener, Master } from './master'; import CONST from './consts'; import { - Canvas3dModel, UpdateReasons, Mode, DrawData, ViewType, MouseInteraction, + Canvas3dModel, DrawData, Mode, Planes, UpdateReasons, ViewType, } from './canvas3dModel'; -import { CuboidModel } from './cuboid'; +import { + createRotationHelper, CuboidModel, setEdges, setTranslationHelper, +} from './cuboid'; export interface Canvas3dView { html(): ViewsDOM; render(): void; keyControls(keys: KeyboardEvent): void; - mouseControls(type: MouseInteraction, event: MouseEvent): void; } -export enum CAMERA_ACTION { +export enum CameraAction { ZOOM_IN = 'KeyI', MOVE_UP = 'KeyU', MOVE_DOWN = 'KeyO', @@ -75,6 +76,9 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { private cube: CuboidModel; private highlighted: boolean; private selected: CubeObject; + private model: Canvas3dModel & Master; + private action: any; + private globalHelpers: any; private set mode(value: Mode) { this.controller.mode = value; @@ -88,9 +92,76 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.controller = controller; this.clock = new THREE.Clock(); this.speed = CONST.MOVEMENT_FACTOR; - this.cube = new CuboidModel(); + this.cube = new CuboidModel('line', '#ffffff'); this.highlighted = false; this.selected = this.cube; + this.model = model; + this.globalHelpers = { + top: { + resize: [], + rotate: [], + }, + side: { + resize: [], + rotate: [], + }, + front: { + resize: [], + rotate: [], + }, + }; + this.action = { + loading: false, + oldState: '', + scan: null, + selectable: true, + frameCoordinates: { + x: 0, + y: 0, + z: 0, + }, + detected: false, + initialMouseVector: new THREE.Vector2(), + detachCam: false, + detachCamRef: 'null', + translation: { + status: false, + helper: null, + coordinates: null, + offset: new THREE.Vector3(), + inverseMatrix: new THREE.Matrix4(), + }, + rotation: { + status: false, + helper: null, + recentMouseVector: new THREE.Vector2(0, 0), + screenInit: { + x: 0, + y: 0, + }, + screenMove: { + x: 0, + y: 0, + }, + }, + resize: { + status: false, + helper: null, + recentMouseVector: new THREE.Vector2(0, 0), + initScales: { + x: 1, + y: 1, + z: 1, + }, + memScales: { + x: 1, + y: 1, + z: 1, + }, + resizeVector: new THREE.Vector3(0, 0, 0), + frontBool: false, + }, + }; this.views = { perspective: { @@ -104,18 +175,211 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { top: { renderer: new THREE.WebGLRenderer({ antialias: true }), scene: new THREE.Scene(), + rayCaster: { + renderer: new THREE.Raycaster(), + mouseVector: new THREE.Vector2(), + }, }, side: { renderer: new THREE.WebGLRenderer({ antialias: true }), scene: new THREE.Scene(), + rayCaster: { + renderer: new THREE.Raycaster(), + mouseVector: new THREE.Vector2(), + }, }, front: { renderer: new THREE.WebGLRenderer({ antialias: true }), scene: new THREE.Scene(), + rayCaster: { + renderer: new THREE.Raycaster(), + mouseVector: new THREE.Vector2(), + }, }, }; CameraControls.install({ THREE }); + const canvasPerspectiveView = this.views.perspective.renderer.domElement; + const canvasTopView = this.views.top.renderer.domElement; + const canvasSideView = this.views.side.renderer.domElement; + const canvasFrontView = this.views.front.renderer.domElement; + + canvasPerspectiveView.addEventListener('contextmenu', (e: MouseEvent): void => { + if (this.controller.focused.clientID !== null) { + this.dispatchEvent( + new CustomEvent('canvas.contextmenu', { + bubbles: false, + cancelable: true, + detail: { + clientID: Number(this.controller.focused.clientID), + clientX: e.clientX, + clientY: e.clientY, + }, + }), + ); + } + if (this.model.mode === Mode.DRAW && e.ctrlKey && this.model.data.drawData.initialState) { + const { x, y, z } = this.cube.perspective.position; + const { x: width, y: height, z: depth } = this.cube.perspective.scale; + const { x: rotationX, y: rotationY, z: rotationZ } = this.cube.perspective.rotation; + const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; + const initState = this.model.data.drawData.initialState; + let label; + if (initState) { + ({ label } = initState); + } + this.dispatchEvent( + new CustomEvent('canvas.drawn', { + bubbles: false, + cancelable: true, + detail: { + state: { + ...initState, + shapeType: 'cuboid', + frame: this.model.data.imageID, + points, + label, + }, + continue: true, + duration: 0, + }, + }), + ); + this.action.oldState = Mode.DRAW; + } + }); + + canvasTopView.addEventListener('mousedown', this.startAction.bind(this, 'top')); + canvasSideView.addEventListener('mousedown', this.startAction.bind(this, 'side')); + canvasFrontView.addEventListener('mousedown', this.startAction.bind(this, 'front')); + + canvasTopView.addEventListener('mousemove', this.moveAction.bind(this, 'top')); + canvasSideView.addEventListener('mousemove', this.moveAction.bind(this, 'side')); + canvasFrontView.addEventListener('mousemove', this.moveAction.bind(this, 'front')); + + canvasTopView.addEventListener('mouseup', this.completeActions.bind(this)); + canvasTopView.addEventListener('mouseleave', this.completeActions.bind(this)); + canvasSideView.addEventListener('mouseup', this.completeActions.bind(this)); + canvasSideView.addEventListener('mouseleave', this.completeActions.bind(this)); + canvasFrontView.addEventListener('mouseup', this.completeActions.bind(this)); + canvasFrontView.addEventListener('mouseleave', this.completeActions.bind(this)); + + canvasPerspectiveView.addEventListener('mousemove', (event: MouseEvent): void => { + event.preventDefault(); + if (this.mode === Mode.DRAG_CANVAS) return; + const canvas = this.views.perspective.renderer.domElement; + const rect = canvas.getBoundingClientRect(); + const { mouseVector } = this.views.perspective.rayCaster as { mouseVector: THREE.Vector2 }; + mouseVector.x = ((event.clientX - (canvas.offsetLeft + rect.left)) / canvas.clientWidth) * 2 - 1; + mouseVector.y = -((event.clientY - (canvas.offsetTop + rect.top)) / canvas.clientHeight) * 2 + 1; + }); + + canvasPerspectiveView.addEventListener('click', (e: MouseEvent): void => { + e.preventDefault(); + if (e.detail !== 1) return; + if (![Mode.GROUP, Mode.IDLE].includes(this.mode) || !this.views.perspective.rayCaster) return; + const intersects = this.views.perspective.rayCaster.renderer.intersectObjects( + this.views.perspective.scene.children[0].children, + false, + ); + if (intersects.length !== 0 && this.mode === Mode.GROUP && this.model.data.groupData.grouped) { + const item = this.model.data.groupData.grouped.filter( + (_state: any): boolean => _state.clientID === Number(intersects[0].object.name), + ); + if (item.length !== 0) { + // @ts-ignore + this.model.data.groupData.grouped = this.model.data.groupData.grouped.filter( + (_state: any): boolean => _state.clientID !== Number(intersects[0].object.name), + ); + intersects[0].object.material.color.set(intersects[0].object.originalColor); + } else { + const [state] = this.model.data.objects.filter( + (_state: any): boolean => _state.clientID === Number(intersects[0].object.name), + ); + this.model.data.groupData.grouped.push(state); + intersects[0].object.material.color.set('#ffffff'); + } + } else if (this.mode === Mode.IDLE) { + if (intersects.length === 0) { + this.setHelperVisibility(false); + } + this.dispatchEvent( + new CustomEvent('canvas.selected', { + bubbles: false, + cancelable: true, + detail: { + clientID: intersects.length !== 0 ? Number(intersects[0].object.name) : null, + }, + }), + ); + } + }); + + canvasPerspectiveView.addEventListener('dblclick', (e: MouseEvent): void => { + e.preventDefault(); + if (this.mode !== Mode.DRAW) { + const { perspective: viewType } = this.views; + viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera); + const intersects = viewType.rayCaster.renderer.intersectObjects( + this.views.perspective.scene.children[0].children, + false, + ); + if (intersects.length !== 0 || this.controller.focused.clientID !== null) { + this.setDefaultZoom(); + } else { + const { x, y, z } = this.action.frameCoordinates; + this.positionAllViews(x, y, z, true); + } + return; + } + this.controller.drawData.enabled = false; + this.mode = Mode.IDLE; + const { x, y, z } = this.cube.perspective.position; + const { x: width, y: height, z: depth } = this.cube.perspective.scale; + const { x: rotationX, y: rotationY, z: rotationZ } = this.cube.perspective.rotation; + const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; + const initState = this.model.data.drawData.initialState; + let label; + if (initState) { + ({ label } = initState); + } + + if (typeof this.model.data.drawData.redraw === 'number') { + const [state] = this.model.data.objects.filter( + (_state: any): boolean => _state.clientID === Number(this.model.data.selected.perspective.name), + ); + this.dispatchEvent( + new CustomEvent('canvas.edited', { + bubbles: false, + cancelable: true, + detail: { + state, + points, + }, + }), + ); + } else { + this.dispatchEvent( + new CustomEvent('canvas.drawn', { + bubbles: false, + cancelable: true, + detail: { + state: { + ...initState, + shapeType: 'cuboid', + frame: this.model.data.imageID, + points, + label, + }, + continue: undefined, + duration: 0, + }, + }), + ); + } + this.dispatchEvent(new CustomEvent('canvas.canceled')); + }); + this.mode = Mode.IDLE; Object.keys(this.views).forEach((view: string): void => { @@ -132,84 +396,469 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.perspective.camera.position.set(-15, 0, 4); this.views.perspective.camera.up.set(0, 0, 1); this.views.perspective.camera.lookAt(10, 0, 0); + this.views.perspective.camera.name = 'cameraPerspective'; this.views.top.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2 - 2, (aspectRatio * viewSize) / 2 + 2, viewSize / 2 + 2, -viewSize / 2 - 2, - -10, - 10, + -50, + 50, ); + this.views.top.camera.position.set(0, 0, 5); + this.views.top.camera.lookAt(0, 0, 0); + this.views.top.camera.up.set(0, 0, 1); + this.views.top.camera.name = 'cameraTop'; + this.views.side.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2, (aspectRatio * viewSize) / 2, viewSize / 2, -viewSize / 2, - -10, - 10, + -50, + 50, ); this.views.side.camera.position.set(0, 5, 0); this.views.side.camera.lookAt(0, 0, 0); this.views.side.camera.up.set(0, 0, 1); + this.views.side.camera.name = 'cameraSide'; this.views.front.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2, (aspectRatio * viewSize) / 2, viewSize / 2, -viewSize / 2, - -10, - 10, + -50, + 50, ); - this.views.front.camera.position.set(-7, 0, 0); + this.views.front.camera.position.set(3, 0, 0); this.views.front.camera.up.set(0, 0, 1); this.views.front.camera.lookAt(0, 0, 0); + this.views.front.camera.name = 'cameraFront'; Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; - viewType.renderer.setSize(width, height); - if (view !== ViewType.PERSPECTIVE) { - viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement); - viewType.controls.mouseButtons.left = CameraControls.ACTION.NONE; - viewType.controls.mouseButtons.right = CameraControls.ACTION.NONE; - } else { - viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement); + if (viewType.camera) { + viewType.renderer.setSize(width, height); + if (view !== ViewType.PERSPECTIVE) { + viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement); + viewType.controls.mouseButtons.left = CameraControls.ACTION.NONE; + viewType.controls.mouseButtons.right = CameraControls.ACTION.NONE; + } else { + viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement); + viewType.controls.mouseButtons.left = CameraControls.ACTION.NONE; + viewType.controls.mouseButtons.right = CameraControls.ACTION.NONE; + viewType.controls.mouseButtons.wheel = CameraControls.ACTION.NONE; + viewType.controls.touches.one = CameraControls.ACTION.NONE; + viewType.controls.touches.two = CameraControls.ACTION.NONE; + viewType.controls.touches.three = CameraControls.ACTION.NONE; + } + viewType.controls.minDistance = CONST.MIN_DISTANCE; + viewType.controls.maxDistance = CONST.MAX_DISTANCE; } - viewType.controls.minDistance = CONST.MIN_DISTANCE; - viewType.controls.maxDistance = CONST.MAX_DISTANCE; + }); + this.views.top.controls.enabled = false; + this.views.side.controls.enabled = false; + this.views.front.controls.enabled = false; + + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + this.views[view].renderer.domElement.addEventListener( + 'wheel', + (event: WheelEvent): void => { + event.preventDefault(); + const { camera } = this.views[view]; + if (event.deltaY < CONST.FOV_MIN && camera.zoom < CONST.FOV_MAX) { + camera.zoom += CONST.FOV_INC; + } else if (event.deltaY > CONST.FOV_MIN && camera.zoom > CONST.FOV_MIN + 0.1) { + camera.zoom -= CONST.FOV_INC; + } + this.setHelperSize(view); + }, + { passive: false }, + ); }); model.subscribe(this); } + private setDefaultZoom(): void { + if (this.model.data.activeElement === 'null') { + Object.keys(this.views).forEach((view: string): void => { + const viewType = this.views[view as keyof Views]; + if (view !== ViewType.PERSPECTIVE) { + viewType.camera.zoom = CONST.FOV_DEFAULT; + viewType.camera.updateProjectionMatrix(); + } + }); + } else { + const canvasTop = this.views.top.renderer.domElement; + const bboxtop = new THREE.Box3().setFromObject(this.model.data.selected.top); + const x1 = Math.min( + canvasTop.offsetWidth / (bboxtop.max.x - bboxtop.min.x), + canvasTop.offsetHeight / (bboxtop.max.y - bboxtop.min.y), + ) * 0.4; + this.views.top.camera.zoom = x1 / 100; + this.views.top.camera.updateProjectionMatrix(); + this.views.top.camera.updateMatrix(); + this.setHelperSize(ViewType.TOP); + + const canvasFront = this.views.top.renderer.domElement; + const bboxfront = new THREE.Box3().setFromObject(this.model.data.selected.front); + const x2 = Math.min( + canvasFront.offsetWidth / (bboxfront.max.y - bboxfront.min.y), + canvasFront.offsetHeight / (bboxfront.max.z - bboxfront.min.z), + ) * 0.4; + this.views.front.camera.zoom = x2 / 100; + this.views.front.camera.updateProjectionMatrix(); + this.views.front.camera.updateMatrix(); + this.setHelperSize(ViewType.FRONT); + + const canvasSide = this.views.side.renderer.domElement; + const bboxside = new THREE.Box3().setFromObject(this.model.data.selected.side); + const x3 = Math.min( + canvasSide.offsetWidth / (bboxside.max.x - bboxside.min.x), + canvasSide.offsetHeight / (bboxside.max.z - bboxside.min.z), + ) * 0.4; + this.views.side.camera.zoom = x3 / 100; + this.views.side.camera.updateProjectionMatrix(); + this.views.side.camera.updateMatrix(); + this.setHelperSize(ViewType.SIDE); + } + } + + private startAction(view: any, event: MouseEvent): void { + if (event.detail !== 1) return; + if (this.model.mode === Mode.DRAG_CANVAS) return; + const { clientID } = this.model.data.activeElement; + if (clientID === 'null') return; + const canvas = this.views[view as keyof Views].renderer.domElement; + const rect = canvas.getBoundingClientRect(); + const { mouseVector } = this.views[view as keyof Views].rayCaster as { mouseVector: THREE.Vector2 }; + const diffX = event.clientX - rect.left; + const diffY = event.clientY - rect.top; + mouseVector.x = (diffX / canvas.clientWidth) * 2 - 1; + mouseVector.y = -(diffY / canvas.clientHeight) * 2 + 1; + this.action.rotation.screenInit = { x: diffX, y: diffY }; + this.action.rotation.screenMove = { x: diffX, y: diffY }; + if ( + this.model.data.selected + && !this.model.data.selected.perspective.userData.lock + && !this.model.data.selected.perspective.userData.hidden + ) { + this.action.scan = view; + this.model.mode = Mode.EDIT; + this.action.selectable = false; + } + } + + private moveAction(view: any, event: MouseEvent): void { + event.preventDefault(); + if (this.model.mode === Mode.DRAG_CANVAS) return; + const { clientID } = this.model.data.activeElement; + if (clientID === 'null') return; + const canvas = this.views[view as keyof Views].renderer.domElement; + const rect = canvas.getBoundingClientRect(); + const { mouseVector } = this.views[view as keyof Views].rayCaster as { mouseVector: THREE.Vector2 }; + const diffX = event.clientX - rect.left; + const diffY = event.clientY - rect.top; + mouseVector.x = (diffX / canvas.clientWidth) * 2 - 1; + mouseVector.y = -(diffY / canvas.clientHeight) * 2 + 1; + this.action.rotation.screenMove = { x: diffX, y: diffY }; + } + + private translateReferencePlane(coordinates: any): void { + const topPlane = this.views.top.scene.getObjectByName(Planes.TOP); + if (topPlane) { + topPlane.position.x = coordinates.x; + topPlane.position.y = coordinates.y; + topPlane.position.z = coordinates.z; + } + const sidePlane = this.views.side.scene.getObjectByName(Planes.SIDE); + if (sidePlane) { + sidePlane.position.x = coordinates.x; + sidePlane.position.y = coordinates.y; + sidePlane.position.z = coordinates.z; + } + const frontPlane = this.views.front.scene.getObjectByName(Planes.FRONT); + if (frontPlane) { + frontPlane.position.x = coordinates.x; + frontPlane.position.y = coordinates.y; + frontPlane.position.z = coordinates.z; + } + } + + private resetActions(): void { + this.action = { + ...this.action, + scan: null, + detected: false, + translation: { + status: false, + helper: null, + }, + rotation: { + status: false, + helper: null, + recentMouseVector: new THREE.Vector2(0, 0), + }, + resize: { + ...this.action.resize, + status: false, + helper: null, + recentMouseVector: new THREE.Vector2(0, 0), + }, + }; + this.model.mode = Mode.IDLE; + this.action.selectable = true; + } + + private completeActions(): void { + const { scan, detected } = this.action; + if (this.model.mode === Mode.DRAG_CANVAS) return; + if (!detected) { + this.resetActions(); + return; + } + + const { x, y, z } = this.model.data.selected[scan].position; + const { x: width, y: height, z: depth } = this.model.data.selected[scan].scale; + const { x: rotationX, y: rotationY, z: rotationZ } = this.model.data.selected[scan].rotation; + const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; + const [state] = this.model.data.objects.filter( + (_state: any): boolean => _state.clientID === Number(this.model.data.selected[scan].name), + ); + this.dispatchEvent( + new CustomEvent('canvas.edited', { + bubbles: false, + cancelable: true, + detail: { + state, + points, + }, + }), + ); + if (this.action.rotation.status) { + this.detachCamera(scan); + } + + this.adjustPerspectiveCameras(); + this.translateReferencePlane(new THREE.Vector3(x, y, z)); + this.resetActions(); + } + + private onGroupDone(objects?: any[]): void { + if (objects && objects.length !== 0) { + this.dispatchEvent( + new CustomEvent('canvas.groupped', { + bubbles: false, + cancelable: true, + detail: { + states: objects, + }, + }), + ); + } else { + this.dispatchEvent( + new CustomEvent('canvas.canceled', { + bubbles: false, + cancelable: true, + }), + ); + } + + this.controller.group({ + enabled: false, + grouped: [], + }); + + this.mode = Mode.IDLE; + } + + private setupObject(object: any, addToScene: boolean): CuboidModel { + const { + opacity, outlined, outlineColor, selectedOpacity, colorBy, + } = this.model.data.shapeProperties; + const clientID = String(object.clientID); + const cuboid = new CuboidModel(object.occluded ? 'dashed' : 'line', outlined ? outlineColor : '#ffffff'); + + cuboid.setName(clientID); + cuboid.perspective.userData = object; + let color = ''; + if (colorBy === 'Label') { + ({ color } = object.label); + } else if (colorBy === 'Instance') { + ({ color } = object); + } else { + ({ color } = object.group); + } + cuboid.setOriginalColor(color); + cuboid.setColor(color); + cuboid.setOpacity(opacity); + + if ( + this.model.data.activeElement.clientID === clientID + && ![Mode.DRAG_CANVAS, Mode.GROUP].includes(this.mode) + ) { + cuboid.setOpacity(selectedOpacity); + if (!object.lock) { + createRotationHelper(cuboid.top, ViewType.TOP); + createRotationHelper(cuboid.side, ViewType.SIDE); + createRotationHelper(cuboid.front, ViewType.FRONT); + setTranslationHelper(cuboid.top); + setTranslationHelper(cuboid.side); + setTranslationHelper(cuboid.front); + } + setEdges(cuboid.top); + setEdges(cuboid.side); + setEdges(cuboid.front); + this.translateReferencePlane(new THREE.Vector3(object.points[0], object.points[1], object.points[2])); + this.model.data.selected = cuboid; + if (object.hidden) { + this.setHelperVisibility(false); + return cuboid; + } + } else { + cuboid.top.visible = false; + cuboid.side.visible = false; + cuboid.front.visible = false; + } + if (object.hidden) { + return cuboid; + } + cuboid.setPosition(object.points[0], object.points[1], object.points[2]); + cuboid.setScale(object.points[6], object.points[7], object.points[8]); + cuboid.setRotation(object.points[3], object.points[4], object.points[5]); + if (addToScene) { + this.addSceneChildren(cuboid); + } + if (this.model.data.activeElement.clientID === clientID) { + cuboid.attachCameraReference(); + this.rotatePlane(null, null); + this.action.detachCam = true; + this.action.detachCamRef = this.model.data.activeElement.clientID; + if (!object.lock) { + this.setSelectedChildScale(1 / cuboid.top.scale.x, 1 / cuboid.top.scale.y, 1 / cuboid.top.scale.z); + this.setHelperVisibility(true); + this.updateRotationHelperPos(); + this.updateResizeHelperPos(); + } else { + this.setHelperVisibility(false); + } + } + return cuboid; + } + + private setupObjects(): void { + if (this.views.perspective.scene.children[0]) { + this.clearSceneObjects(); + this.setHelperVisibility(false); + for (let i = 0; i < this.model.data.objects.length; i++) { + const object = this.model.data.objects[i]; + this.setupObject(object, true); + } + this.action.loading = false; + } + } + + private addSceneChildren(shapeObject: CuboidModel): void { + this.views.perspective.scene.children[0].add(shapeObject.perspective); + this.views.top.scene.children[0].add(shapeObject.top); + this.views.side.scene.children[0].add(shapeObject.side); + this.views.front.scene.children[0].add(shapeObject.front); + } + + private dispatchEvent(event: CustomEvent): void { + this.views.perspective.renderer.domElement.dispatchEvent(event); + } + public notify(model: Canvas3dModel & Master, reason: UpdateReasons): void { if (reason === UpdateReasons.IMAGE_CHANGED) { + if (!model.data.image) return; + this.dispatchEvent(new CustomEvent('canvas.canceled')); + if (this.model.mode === Mode.DRAW) { + this.model.data.drawData.enabled = false; + } + this.views.perspective.renderer.dispose(); + this.model.mode = Mode.BUSY; + this.action.loading = true; const loader = new PCDLoader(); - this.clearScene(); const objectURL = URL.createObjectURL(model.data.image.imageData); + this.clearScene(); loader.load(objectURL, this.addScene.bind(this)); URL.revokeObjectURL(objectURL); - const event: CustomEvent = new CustomEvent('canvas.setup'); - this.views.perspective.renderer.domElement.dispatchEvent(event); + this.dispatchEvent(new CustomEvent('canvas.setup')); + } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { + const { clientID } = this.model.data.activeElement; + this.setupObjects(); + if (clientID !== 'null') { + this.setDefaultZoom(); + } } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; - if (data.enabled && this.mode === Mode.IDLE) { - this.mode = Mode.DRAW; - this.cube = new CuboidModel(); - } else if (this.mode !== Mode.IDLE) { - this.cube = new CuboidModel(); + this.cube = new CuboidModel('line', '#ffffff'); + if (data.redraw) { + const object = this.views.perspective.scene.getObjectByName(String(data.redraw)); + if (object) { + this.cube.perspective = object.clone() as THREE.Mesh; + object.visible = false; + } + } else if (data.initialState) { + this.model.data.activeElement.clientID = 'null'; + this.setupObjects(); + this.cube = this.setupObject(data.initialState, false); + } + this.setHelperVisibility(false); + } else if (reason === UpdateReasons.OBJECTS_UPDATED) { + this.setupObjects(); + } else if (reason === UpdateReasons.DRAG_CANVAS) { + this.dispatchEvent( + new CustomEvent(this.model.mode === Mode.DRAG_CANVAS ? 'canvas.dragstart' : 'canvas.dragstop', { + bubbles: false, + cancelable: true, + }), + ); + this.model.data.activeElement.clientID = 'null'; + if (this.model.mode === Mode.DRAG_CANVAS) { + const { controls } = this.views.perspective; + controls.mouseButtons.left = CameraControls.ACTION.ROTATE; + controls.mouseButtons.right = CameraControls.ACTION.TRUCK; + controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; + controls.touches.one = CameraControls.ACTION.TOUCH_ROTATE; + controls.touches.two = CameraControls.ACTION.TOUCH_DOLLY_TRUCK; + controls.touches.three = CameraControls.ACTION.TOUCH_TRUCK; } + this.setupObjects(); } else if (reason === UpdateReasons.CANCEL) { if (this.mode === Mode.DRAW) { this.controller.drawData.enabled = false; + this.controller.drawData.redraw = undefined; Object.keys(this.views).forEach((view: string): void => { this.views[view as keyof Views].scene.children[0].remove(this.cube[view as keyof Views]); }); } - this.mode = Mode.IDLE; - const event: CustomEvent = new CustomEvent('canvas.canceled'); - this.views.perspective.renderer.domElement.dispatchEvent(event); + this.model.data.groupData.grouped = []; + this.setHelperVisibility(false); + this.model.mode = Mode.IDLE; + const { controls } = this.views.perspective; + controls.mouseButtons.left = CameraControls.ACTION.NONE; + controls.mouseButtons.right = CameraControls.ACTION.NONE; + controls.mouseButtons.wheel = CameraControls.ACTION.NONE; + controls.touches.one = CameraControls.ACTION.NONE; + controls.touches.two = CameraControls.ACTION.NONE; + controls.touches.three = CameraControls.ACTION.NONE; + this.dispatchEvent(new CustomEvent('canvas.canceled')); + } else if (reason === UpdateReasons.FITTED_CANVAS) { + this.dispatchEvent(new CustomEvent('canvas.fit')); + } else if (reason === UpdateReasons.GROUP) { + if (!this.model.groupData.enabled) { + this.onGroupDone(this.model.data.groupData.grouped); + } else { + this.model.data.groupData.grouped = []; + this.model.data.activeElement.clientID = 'null'; + this.setupObjects(); + } } } @@ -219,13 +868,102 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { }); } + private clearSceneObjects(): void { + Object.keys(this.views).forEach((view: string): void => { + this.views[view as keyof Views].scene.children[0].children = []; + }); + } + + private setHelperVisibility(visibility: boolean): void { + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((viewType: ViewType): void => { + const globalRotationObject = this.views[viewType].scene.getObjectByName('globalRotationHelper'); + if (globalRotationObject) { + globalRotationObject.visible = visibility; + } + for (let i = 0; i < 8; i++) { + const resizeObject = this.views[viewType].scene.getObjectByName(`globalResizeHelper${i}`); + if (resizeObject) { + resizeObject.visible = visibility; + } + } + }); + } + + private static setupRotationHelper(): THREE.Mesh { + const sphereGeometry = new THREE.SphereGeometry(0.15); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1, visible: true }); + const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); + rotationHelper.name = 'globalRotationHelper'; + return rotationHelper; + } + + private updateRotationHelperPos(): void { + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + const point = new THREE.Vector3(0, 0, 0); + this.model.data.selected[view].getObjectByName('rotationHelper').getWorldPosition(point); + const globalRotationObject = this.views[view].scene.getObjectByName('globalRotationHelper'); + if (globalRotationObject) { + globalRotationObject.position.set(point.x, point.y, point.z); + } + }); + } + + private setHelperSize(viewType: ViewType): void { + if ([ViewType.TOP, ViewType.SIDE, ViewType.FRONT].includes(viewType)) { + const { camera } = this.views[viewType]; + if (!camera || camera instanceof THREE.PerspectiveCamera) return; + const factor = (camera.top - camera.bottom) / camera.zoom; + const rotationObject = this.views[viewType].scene.getObjectByName('globalRotationHelper'); + if (rotationObject) { + rotationObject.scale.set(1, 1, 1).multiplyScalar(factor / 10); + } + for (let i = 0; i < 8; i++) { + const resizeObject = this.views[viewType].scene.getObjectByName(`globalResizeHelper${i}`); + if (resizeObject) { + resizeObject.scale.set(1, 1, 1).multiplyScalar(factor / 10); + } + } + } + } + + private setupResizeHelper(viewType: ViewType): void { + const sphereGeometry = new THREE.SphereGeometry(0.15); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1, visible: true }); + const helpers = []; + for (let i = 0; i < 8; i++) { + helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); + helpers[i].name = `globalResizeHelper${i}`; + this.globalHelpers[viewType].resize.push(helpers[i]); + this.views[viewType].scene.add(helpers[i]); + } + } + + private updateResizeHelperPos(): void { + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + let i = 0; + this.model.data.selected[view].children.forEach((element: any): void => { + if (element.name === 'resizeHelper') { + const p = new THREE.Vector3(0, 0, 0); + element.getWorldPosition(p); + const name = `globalResizeHelper${i}`; + const object = this.views[view].scene.getObjectByName(name); + if (object) { + object.position.set(p.x, p.y, p.z); + } + i++; + } + }); + }); + } + private addScene(points: any): void { // eslint-disable-next-line no-param-reassign - points.material.size = 0.08; - // eslint-disable-next-line no-param-reassign - points.material.color = new THREE.Color(0x0000ff); + points.material.size = 0.05; + points.material.color.set(new THREE.Color(0xffffff)); + const material = points.material.clone(); const sphereCenter = points.geometry.boundingSphere.center; const { radius } = points.geometry.boundingSphere; + if (!this.views.perspective.camera) return; const xRange = -radius / 2 < this.views.perspective.camera.position.x - sphereCenter.x && radius / 2 > this.views.perspective.camera.position.x - sphereCenter.x; const yRange = -radius / 2 < this.views.perspective.camera.position.y - sphereCenter.y @@ -245,32 +983,138 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { newZ = sphereCenter.z; } if (newX || newY || newZ) { - this.positionAllViews(newX, newY, newZ); + this.action.frameCoordinates = { x: newX, y: newY, z: newZ }; + this.positionAllViews(newX, newY, newZ, false); } - this.views.perspective.scene.add(points); + + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + this.globalHelpers[view].resize = []; + this.globalHelpers[view].rotation = []; + }); + + this.views.perspective.scene.add(points.clone()); + // Setup TopView + const canvasTopView = this.views.top.renderer.domElement; + const topScenePlane = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasTopView.offsetHeight, + canvasTopView.offsetWidth, + canvasTopView.offsetHeight, + canvasTopView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0xffffff, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0, + }), + ); + topScenePlane.position.set(0, 0, 0); + topScenePlane.name = Planes.TOP; + (topScenePlane.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + (topScenePlane as any).verticesNeedUpdate = true; + // eslint-disable-next-line no-param-reassign + points.material = material; + material.size = 0.5; this.views.top.scene.add(points.clone()); + this.views.top.scene.add(topScenePlane); + const topRotationHelper = Canvas3dViewImpl.setupRotationHelper(); + this.globalHelpers.top.rotation.push(topRotationHelper); + this.views.top.scene.add(topRotationHelper); + this.setupResizeHelper(ViewType.TOP); + // Setup Side View + const canvasSideView = this.views.side.renderer.domElement; + const sideScenePlane = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasSideView.offsetHeight, + canvasSideView.offsetWidth, + canvasSideView.offsetHeight, + canvasSideView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0xffffff, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0, + }), + ); + sideScenePlane.position.set(0, 0, 0); + sideScenePlane.rotation.set(-Math.PI / 2, Math.PI / 2000, Math.PI); + sideScenePlane.name = Planes.SIDE; + (sideScenePlane.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + (sideScenePlane as any).verticesNeedUpdate = true; this.views.side.scene.add(points.clone()); + this.views.side.scene.add(sideScenePlane); + const sideRotationHelper = Canvas3dViewImpl.setupRotationHelper(); + this.globalHelpers.side.rotation.push(sideRotationHelper); + this.views.side.scene.add(sideRotationHelper); + this.setupResizeHelper(ViewType.SIDE); + // Setup front View + const canvasFrontView = this.views.front.renderer.domElement; + const frontScenePlane = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasFrontView.offsetHeight, + canvasFrontView.offsetWidth, + canvasFrontView.offsetHeight, + canvasFrontView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0xffffff, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0, + }), + ); + frontScenePlane.position.set(0, 0, 0); + frontScenePlane.rotation.set(0, Math.PI / 2, 0); + frontScenePlane.name = Planes.FRONT; + (frontScenePlane.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + (frontScenePlane as any).verticesNeedUpdate = true; this.views.front.scene.add(points.clone()); + this.views.front.scene.add(frontScenePlane); + const frontRotationHelper = Canvas3dViewImpl.setupRotationHelper(); + this.globalHelpers.front.rotation.push(frontRotationHelper); + this.views.front.scene.add(frontRotationHelper); + this.setupResizeHelper(ViewType.FRONT); + this.setHelperVisibility(false); + this.setupObjects(); } - private positionAllViews(x: number, y: number, z: number): void { - this.views.perspective.controls.setLookAt(x - 8, y - 8, z + 3, x, y, z, false); - this.views.top.controls.setLookAt(x, y, z + 8, x, y, z, false); - this.views.side.controls.setLookAt(x, y + 8, z, x, y, z, false); - this.views.front.controls.setLookAt(x + 8, y, z, x, y, z, false); + private positionAllViews(x: number, y: number, z: number, animation: boolean): void { + if ( + this.views.perspective.controls + && this.views.top.controls + && this.views.side.controls + && this.views.front.controls + ) { + this.views.perspective.controls.setLookAt(x - 8, y - 8, z + 3, x, y, z, animation); + this.views.top.camera.position.set(x, y, z + 8); + this.views.top.camera.lookAt(x, y, z); + this.views.top.camera.zoom = CONST.FOV_DEFAULT; + this.views.side.camera.position.set(x, y + 8, z); + this.views.side.camera.lookAt(x, y, z); + this.views.side.camera.zoom = CONST.FOV_DEFAULT; + this.views.front.camera.position.set(x + 8, y, z); + this.views.front.camera.lookAt(x, y, z); + this.views.front.camera.zoom = CONST.FOV_DEFAULT; + } } private static resizeRendererToDisplaySize(viewName: string, view: RenderView): void { const { camera, renderer } = view; const canvas = renderer.domElement; + if (!canvas.parentElement) return; const width = canvas.parentElement.clientWidth; const height = canvas.parentElement.clientHeight; const needResize = canvas.clientWidth !== width || canvas.clientHeight !== height; - if (needResize) { + if (needResize && camera && view.camera) { if (camera instanceof THREE.PerspectiveCamera) { camera.aspect = width / height; } else { - const topViewFactor = 0; // viewName === ViewType.TOP ? 2 : 0; + const topViewFactor = 0; const viewSize = CONST.ZOOM_FACTOR; const aspectRatio = width / height; if (!(camera instanceof THREE.PerspectiveCamera)) { @@ -279,8 +1123,8 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { camera.top = viewSize / 2 + topViewFactor; camera.bottom = -viewSize / 2 - topViewFactor; } - camera.near = -10; - camera.far = 10; + camera.near = -50; + camera.far = 50; } view.renderer.setSize(width, height); view.camera.updateProjectionMatrix(); @@ -298,131 +1142,728 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.perspective.scene.children[0].add(this.cube.perspective); const newPoints = intersects[0].point; this.cube.perspective.position.copy(newPoints); + this.views.perspective.renderer.domElement.style.cursor = 'default'; } } else if (this.mode === Mode.IDLE) { - const intersects = this.views.perspective.rayCaster.renderer.intersectObjects( - this.views.perspective.scene.children[0].children, - false, - ); + const { children } = this.views.perspective.scene.children[0]; + const { renderer } = this.views.perspective.rayCaster; + const intersects = renderer.intersectObjects(children, false); if (intersects.length !== 0) { - this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => { - if (this.selected.perspective !== sceneItem) { - // eslint-disable-next-line no-param-reassign - sceneItem.material.color = new THREE.Color(0xff0000); - } - }); - const selectedObject = intersects[0].object as THREE.Mesh; - if (this.selected.perspective !== selectedObject) { - selectedObject.material.color = new THREE.Color(0xffff00); - this.highlighted = true; - } - } else { - if (this.highlighted) { - this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => { - if (this.selected.perspective !== sceneItem) { - // eslint-disable-next-line no-param-reassign - sceneItem.material.color = new THREE.Color(0xff0000); - } - }); + const clientID = intersects[0].object.name; + if (clientID === undefined || clientID === '' || this.model.data.focusData.clientID === clientID) { + return; } - this.highlighted = false; + if (!this.action.selectable) return; + this.resetColor(); + const object = this.views.perspective.scene.getObjectByName(clientID); + if (object === undefined) return; + this.model.data.focusData.clientID = clientID; + this.dispatchEvent( + new CustomEvent('canvas.selected', { + bubbles: false, + cancelable: true, + detail: { + clientID: Number(intersects[0].object.name), + }, + }), + ); + } else if (this.model.data.focusData.clientID !== null) { + this.resetColor(); + this.model.data.focusData.clientID = null; } } }; + private resetColor(): void { + this.model.data.objects.forEach((object: any): void => { + const { clientID } = object; + const target = this.views.perspective.scene.getObjectByName(String(clientID)); + if (target) { + ((target as THREE.Mesh).material as THREE.MeshBasicMaterial).color.set((target as any).originalColor); + } + }); + } + public render(): void { Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; + if (!(viewType.controls && viewType.camera && viewType.rayCaster)) return; Canvas3dViewImpl.resizeRendererToDisplaySize(view, viewType); - viewType.controls.update(this.clock.getDelta()); + if (viewType.controls.enabled) { + viewType.controls.update(this.clock.getDelta()); + } else { + viewType.camera.updateProjectionMatrix(); + } viewType.renderer.render(viewType.scene, viewType.camera); if (view === ViewType.PERSPECTIVE && viewType.scene.children.length !== 0) { this.renderRayCaster(viewType); } + const { clientID } = this.model.data.activeElement; + if (clientID !== 'null' && view !== ViewType.PERSPECTIVE) { + viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera); + // First Scan + if (this.action.scan === view) { + if (!(this.action.translation.status || this.action.resize.status || this.action.rotation.status)) { + this.initiateAction(view, viewType); + } + // Action Operations + if (this.action.detected) { + if (this.action.translation.status) { + this.renderTranslateAction(view as ViewType, viewType); + } else if (this.action.resize.status) { + this.renderResizeAction(view as ViewType, viewType); + } else { + this.renderRotateAction(view as ViewType, viewType); + } + this.updateRotationHelperPos(); + this.updateResizeHelperPos(); + } else { + this.resetActions(); + } + } + } }); + if (this.action.detachCam && this.action.detachCamRef === this.model.data.activeElement.clientID) { + try { + this.detachCamera(null); + // eslint-disable-next-line no-empty + } catch (e) { + } finally { + this.action.detachCam = false; + } + } + if (this.model.mode === Mode.BUSY && !this.action.loading) { + if (this.action.oldState !== '') { + this.model.mode = this.action.oldState; + this.action.oldState = ''; + } else { + this.model.mode = Mode.IDLE; + } + } else if (this.model.data.objectUpdating && !this.action.loading) { + this.model.data.objectUpdating = false; + } } - public keyControls(key: any): void { - const { controls } = this.views.perspective; - switch (key.code) { - case CAMERA_ACTION.ROTATE_RIGHT: - controls.rotate(0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true); + private adjustPerspectiveCameras(): void { + const coordinatesTop = this.model.data.selected.getReferenceCoordinates(ViewType.TOP); + const sphericalTop = new THREE.Spherical(); + sphericalTop.setFromVector3(coordinatesTop); + this.views.top.camera.position.setFromSpherical(sphericalTop); + this.views.top.camera.updateProjectionMatrix(); + + const coordinatesSide = this.model.data.selected.getReferenceCoordinates(ViewType.SIDE); + const sphericalSide = new THREE.Spherical(); + sphericalSide.setFromVector3(coordinatesSide); + this.views.side.camera.position.setFromSpherical(sphericalSide); + this.views.side.camera.updateProjectionMatrix(); + + const coordinatesFront = this.model.data.selected.getReferenceCoordinates(ViewType.FRONT); + const sphericalFront = new THREE.Spherical(); + sphericalFront.setFromVector3(coordinatesFront); + this.views.front.camera.position.setFromSpherical(sphericalFront); + this.views.front.camera.updateProjectionMatrix(); + } + + private renderTranslateAction(view: ViewType, viewType: any): void { + if ( + this.action.translation.helper.x === this.views[view].rayCaster.mouseVector.x + && this.action.translation.helper.y === this.views[view].rayCaster.mouseVector.y + ) { + return; + } + const intersects = viewType.rayCaster.renderer.intersectObjects( + [viewType.scene.getObjectByName(`${view}Plane`)], + true, + ); + + if (intersects.length !== 0 && intersects[0].point) { + const coordinates = intersects[0].point; + this.action.translation.coordinates = coordinates; + this.moveObject(coordinates); + } + } + + private moveObject(coordinates: THREE.Vector3): void { + const { + perspective, top, side, front, + } = this.model.data.selected; + let localCoordinates = coordinates; + if (this.action.translation.status) { + localCoordinates = coordinates + .clone() + .sub(this.action.translation.offset) + .applyMatrix4(this.action.translation.inverseMatrix); + } + perspective.position.copy(localCoordinates.clone()); + top.position.copy(localCoordinates.clone()); + side.position.copy(localCoordinates.clone()); + front.position.copy(localCoordinates.clone()); + } + + private setSelectedChildScale(x: number, y: number, z: number): void { + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + this.model.data.selected[view].children.forEach((element: any): void => { + if (element.name !== CONST.CUBOID_EDGE_NAME) { + element.scale.set( + x == null ? element.scale.x : x, + y == null ? element.scale.y : y, + z == null ? element.scale.z : z, + ); + } + }); + }); + } + + private renderResizeAction(view: ViewType, viewType: any): void { + const intersects = viewType.rayCaster.renderer.intersectObjects( + [viewType.scene.getObjectByName(`${view}Plane`)], + true, + ); + // Return if no intersection with the reference plane + if (intersects.length === 0) return; + const { x: scaleInitX, y: scaleInitY, z: scaleInitZ } = this.action.resize.initScales; + const { x: scaleMemX, y: scaleMemY, z: scaleMemZ } = this.action.resize.memScales; + const { x: initPosX, y: initPosY } = this.action.resize.helper; + const { x: currentPosX, y: currentPosY } = viewType.rayCaster.mouseVector; + const { resizeVector } = this.action.resize; + + if (this.action.resize.helper.x === currentPosX && this.action.resize.helper.y === currentPosY) { + return; + } + + if ( + this.action.resize.recentMouseVector.x === currentPosX + && this.action.resize.recentMouseVector.y === currentPosY + ) { + return; + } + this.action.resize.recentMouseVector = viewType.rayCaster.mouseVector.clone(); + switch (view) { + case ViewType.TOP: { + let y = scaleInitX * (currentPosX / initPosX); + let x = scaleInitY * (currentPosY / initPosY); + if (x < 0) x = 0.2; + if (y < 0) y = 0.2; + this.model.data.selected.setScale(y, x, this.model.data.selected.top.scale.z); + this.setSelectedChildScale(1 / y, 1 / x, null); + const differenceX = y / 2 - scaleMemX / 2; + const differenceY = x / 2 - scaleMemY / 2; + + if (currentPosX > 0 && currentPosY < 0) { + resizeVector.x += differenceX; + resizeVector.y -= differenceY; + } else if (currentPosX > 0 && currentPosY > 0) { + resizeVector.x += differenceX; + resizeVector.y += differenceY; + } else if (currentPosX < 0 && currentPosY < 0) { + resizeVector.x -= differenceX; + resizeVector.y -= differenceY; + } else if (currentPosX < 0 && currentPosY > 0) { + resizeVector.x -= differenceX; + resizeVector.y += differenceY; + } + + this.action.resize.memScales.x = y; + this.action.resize.memScales.y = x; + break; + } + case ViewType.SIDE: { + let x = scaleInitX * (currentPosX / initPosX); + let z = scaleInitZ * (currentPosY / initPosY); + if (x < 0) x = 0.2; + if (z < 0) z = 0.2; + this.model.data.selected.setScale(x, this.model.data.selected.top.scale.y, z); + this.setSelectedChildScale(1 / x, null, 1 / z); + const differenceX = x / 2 - scaleMemX / 2; + const differenceY = z / 2 - scaleMemZ / 2; + + if (currentPosX > 0 && currentPosY < 0) { + resizeVector.x += differenceX; + resizeVector.y -= differenceY; + } else if (currentPosX > 0 && currentPosY > 0) { + resizeVector.x += differenceX; + resizeVector.y += differenceY; + } else if (currentPosX < 0 && currentPosY < 0) { + resizeVector.x -= differenceX; + resizeVector.y -= differenceY; + } else if (currentPosX < 0 && currentPosY > 0) { + resizeVector.x -= differenceX; + resizeVector.y += differenceY; + } + + this.action.resize.memScales = { ...this.action.resize.memScales, x, z }; + break; + } + case ViewType.FRONT: { + let y = scaleInitY * (currentPosX / initPosX); + let z = scaleInitZ * (currentPosY / initPosY); + if (y < 0) y = 0.2; + if (z < 0) z = 0.2; + this.model.data.selected.setScale(this.model.data.selected.top.scale.x, y, z); + this.setSelectedChildScale(null, 1 / y, 1 / z); + let differenceX; + let differenceY; + + if (!this.action.resize.frontBool) { + differenceX = z / 2 - scaleMemZ / 2; + differenceY = y / 2 - scaleMemY / 2; + this.action.resize.frontBool = true; + } else { + differenceX = z / 2 - scaleMemY / 2; + differenceY = y / 2 - scaleMemZ / 2; + } + if (currentPosX > 0 && currentPosY < 0) { + resizeVector.x += differenceX; + resizeVector.y += differenceY; + } else if (currentPosX > 0 && currentPosY > 0) { + resizeVector.x -= differenceX; + resizeVector.y += differenceY; + } else if (currentPosX < 0 && currentPosY < 0) { + resizeVector.x += differenceX; + resizeVector.y -= differenceY; + } else if (currentPosX < 0 && currentPosY > 0) { + resizeVector.x -= differenceX; + resizeVector.y -= differenceY; + } + + this.action.resize.memScales.y = z; + this.action.resize.memScales.z = y; break; - case CAMERA_ACTION.ROTATE_LEFT: - controls.rotate(-0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true); + } + default: + } + const coordinates = resizeVector.clone(); + intersects[0].object.localToWorld(coordinates); + this.moveObject(coordinates); + this.adjustPerspectiveCameras(); + } + + private static isLeft(a: any, b: any, c: any): boolean { + // For reference + // A + // |\ // A = Rotation Center + // | \ // B = Previous Frame Position + // | C // C = Current Frame Position + // B + return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0; + } + + private rotateCube(instance: CuboidModel, direction: number, view: ViewType): void { + switch (view) { + case ViewType.TOP: + instance.perspective.rotateZ(direction); + instance.top.rotateZ(direction); + instance.side.rotateZ(direction); + instance.front.rotateZ(direction); + this.rotateCamera(direction, view); break; - case CAMERA_ACTION.TILT_UP: - controls.rotate(0, -0.05 * THREE.MathUtils.DEG2RAD * this.speed, true); + case ViewType.FRONT: + instance.perspective.rotateX(direction); + instance.top.rotateX(direction); + instance.side.rotateX(direction); + instance.front.rotateX(direction); + this.rotateCamera(direction, view); break; - case CAMERA_ACTION.TILT_DOWN: - controls.rotate(0, 0.05 * THREE.MathUtils.DEG2RAD * this.speed, true); + case ViewType.SIDE: + instance.perspective.rotateY(direction); + instance.top.rotateY(direction); + instance.side.rotateY(direction); + instance.front.rotateY(direction); + this.rotateCamera(direction, view); break; default: + } + } + + private rotateCamera(direction: any, view: ViewType): void { + switch (view) { + case ViewType.TOP: + this.views.top.camera.rotateZ(direction); break; + case ViewType.FRONT: + this.views.front.camera.rotateZ(direction); + break; + case ViewType.SIDE: + this.views.side.camera.rotateZ(direction); + break; + default: } - if (key.altKey === true) { + } + + private attachCamera(view: ViewType): void { + switch (view) { + case ViewType.TOP: + this.model.data.selected.side.attach(this.views.side.camera); + this.model.data.selected.front.attach(this.views.front.camera); + break; + case ViewType.SIDE: + this.model.data.selected.front.attach(this.views.front.camera); + this.model.data.selected.top.attach(this.views.top.camera); + break; + case ViewType.FRONT: + this.model.data.selected.side.attach(this.views.side.camera); + this.model.data.selected.top.attach(this.views.top.camera); + break; + default: + } + } + + private detachCamera(view: ViewType): void { + const coordTop = this.model.data.selected.getReferenceCoordinates(ViewType.TOP); + const sphericaltop = new THREE.Spherical(); + sphericaltop.setFromVector3(coordTop); + + const coordSide = this.model.data.selected.getReferenceCoordinates(ViewType.SIDE); + const sphericalside = new THREE.Spherical(); + sphericalside.setFromVector3(coordSide); + + const coordFront = this.model.data.selected.getReferenceCoordinates(ViewType.FRONT); + const sphericalfront = new THREE.Spherical(); + sphericalfront.setFromVector3(coordFront); + + const { side: objectSideView, front: objectFrontView, top: objectTopView } = this.model.data.selected; + const { camera: sideCamera } = this.views.side; + const { camera: frontCamera } = this.views.front; + const { camera: topCamera } = this.views.top; + + switch (view) { + case ViewType.TOP: { + const camRotationSide = objectSideView + .getObjectByName('cameraSide') + .getWorldQuaternion(new THREE.Quaternion()); + objectSideView.remove(sideCamera); + sideCamera.position.setFromSpherical(sphericalside); + sideCamera.lookAt(objectSideView.position.x, objectSideView.position.y, objectSideView.position.z); + sideCamera.setRotationFromQuaternion(camRotationSide); + sideCamera.scale.set(1, 1, 1); + + const camRotationFront = objectFrontView + .getObjectByName('cameraFront') + .getWorldQuaternion(new THREE.Quaternion()); + objectFrontView.remove(frontCamera); + frontCamera.position.setFromSpherical(sphericalfront); + frontCamera.lookAt(objectFrontView.position.x, objectFrontView.position.y, objectFrontView.position.z); + frontCamera.setRotationFromQuaternion(camRotationFront); + frontCamera.scale.set(1, 1, 1); + break; + } + case ViewType.SIDE: { + const camRotationFront = objectFrontView + .getObjectByName('cameraFront') + .getWorldQuaternion(new THREE.Quaternion()); + objectFrontView.remove(frontCamera); + frontCamera.position.setFromSpherical(sphericalfront); + frontCamera.lookAt(objectFrontView.position.x, objectFrontView.position.y, objectFrontView.position.z); + frontCamera.setRotationFromQuaternion(camRotationFront); + frontCamera.scale.set(1, 1, 1); + + objectTopView.remove(topCamera); + topCamera.position.setFromSpherical(sphericaltop); + topCamera.lookAt(objectTopView.position.x, objectTopView.position.y, objectTopView.position.z); + topCamera.setRotationFromEuler(objectTopView.rotation); + topCamera.scale.set(1, 1, 1); + break; + } + case ViewType.FRONT: { + const camRotationSide = objectSideView + .getObjectByName('cameraSide') + .getWorldQuaternion(new THREE.Quaternion()); + objectSideView.remove(sideCamera); + sideCamera.position.setFromSpherical(sphericalside); + sideCamera.lookAt(objectSideView.position.x, objectSideView.position.y, objectSideView.position.z); + sideCamera.setRotationFromQuaternion(camRotationSide); + sideCamera.scale.set(1, 1, 1); + + objectTopView.remove(topCamera); + topCamera.position.setFromSpherical(sphericaltop); + topCamera.lookAt(objectTopView.position.x, objectTopView.position.y, objectTopView.position.z); + topCamera.setRotationFromEuler(objectTopView.rotation); + topCamera.scale.set(1, 1, 1); + break; + } + default: { + sideCamera.position.setFromSpherical(sphericalside); + sideCamera.lookAt(objectSideView.position.x, objectSideView.position.y, objectSideView.position.z); + sideCamera.rotation.z = this.views.side.scene.getObjectByName(Planes.SIDE).rotation.z; + sideCamera.scale.set(1, 1, 1); + + topCamera.position.setFromSpherical(sphericaltop); + topCamera.lookAt(objectTopView.position.x, objectTopView.position.y, objectTopView.position.z); + topCamera.setRotationFromEuler(objectTopView.rotation); + topCamera.scale.set(1, 1, 1); + + const camFrontRotate = objectFrontView + .getObjectByName('camRefRot') + .getWorldQuaternion(new THREE.Quaternion()); + frontCamera.position.setFromSpherical(sphericalfront); + frontCamera.lookAt(objectFrontView.position.x, objectFrontView.position.y, objectFrontView.position.z); + frontCamera.setRotationFromQuaternion(camFrontRotate); + frontCamera.scale.set(1, 1, 1); + } + } + } + + private rotatePlane(direction: number, view: ViewType): void { + const sceneTopPlane = this.views.top.scene.getObjectByName(Planes.TOP); + const sceneSidePlane = this.views.side.scene.getObjectByName(Planes.SIDE); + const sceneFrontPlane = this.views.front.scene.getObjectByName(Planes.FRONT); + switch (view) { + case ViewType.TOP: + sceneTopPlane.rotateZ(direction); + sceneSidePlane.rotateY(direction); + sceneFrontPlane.rotateX(-direction); + break; + case ViewType.SIDE: + sceneTopPlane.rotateY(direction); + sceneSidePlane.rotateZ(direction); + sceneFrontPlane.rotateY(direction); + break; + case ViewType.FRONT: + sceneTopPlane.rotateX(direction); + sceneSidePlane.rotateX(-direction); + sceneFrontPlane.rotateZ(direction); + break; + default: { + const { top: objectTopView, side: objectSideView, front: objectFrontView } = this.model.data.selected; + objectTopView.add(sceneTopPlane); + objectSideView.add(sceneSidePlane); + objectFrontView.add(sceneFrontPlane); + objectTopView.getObjectByName(Planes.TOP).rotation.set(0, 0, 0); + objectSideView.getObjectByName(Planes.SIDE).rotation.set(-Math.PI / 2, Math.PI / 2000, Math.PI); + objectFrontView.getObjectByName(Planes.FRONT).rotation.set(0, Math.PI / 2, 0); + + const quaternionSide = new THREE.Quaternion(); + objectSideView.getObjectByName(Planes.SIDE).getWorldQuaternion(quaternionSide); + const rotationSide = new THREE.Euler(); + rotationSide.setFromQuaternion(quaternionSide); + + const quaternionFront = new THREE.Quaternion(); + objectFrontView.getObjectByName(Planes.FRONT).getWorldQuaternion(quaternionFront); + const rotationFront = new THREE.Euler(); + rotationFront.setFromQuaternion(quaternionFront); + + const quaternionTop = new THREE.Quaternion(); + objectTopView.getObjectByName(Planes.TOP).getWorldQuaternion(quaternionTop); + const rotationTop = new THREE.Euler(); + rotationTop.setFromQuaternion(quaternionTop); + + objectTopView.remove(sceneTopPlane); + objectSideView.remove(sceneSidePlane); + objectFrontView.remove(sceneFrontPlane); + + const canvasTopView = this.views.top.renderer.domElement; + const planeTop = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasTopView.offsetHeight, + canvasTopView.offsetWidth, + canvasTopView.offsetHeight, + canvasTopView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0xff0000, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0.1, + }), + ); + planeTop.name = Planes.TOP; + (planeTop.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + + const canvasSideView = this.views.side.renderer.domElement; + const planeSide = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasSideView.offsetHeight, + canvasSideView.offsetWidth, + canvasSideView.offsetHeight, + canvasSideView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0x00ff00, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0.1, + }), + ); + planeSide.name = Planes.SIDE; + (planeSide.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + + const canvasFrontView = this.views.front.renderer.domElement; + const planeFront = new THREE.Mesh( + new THREE.PlaneBufferGeometry( + canvasFrontView.offsetHeight, + canvasFrontView.offsetWidth, + canvasFrontView.offsetHeight, + canvasFrontView.offsetWidth, + ), + new THREE.MeshBasicMaterial({ + color: 0x0000ff, + alphaTest: 0, + visible: false, + transparent: true, + opacity: 0.5, + }), + ); + planeFront.name = Planes.FRONT; + (planeFront.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + + const coordinates = { + x: objectTopView.position.x, + y: objectTopView.position.y, + z: objectTopView.position.z, + }; + + planeTop.rotation.set(rotationTop.x, rotationTop.y, rotationTop.z); + planeSide.rotation.set(rotationSide.x, rotationSide.y, rotationSide.z); + planeFront.rotation.set(rotationFront.x, rotationFront.y, rotationFront.z); + this.views.top.scene.add(planeTop); + this.views.side.scene.add(planeSide); + this.views.front.scene.add(planeFront); + + this.translateReferencePlane(coordinates); + } + } + } + + private renderRotateAction(view: ViewType, viewType: any): void { + const rotationSpeed = Math.PI / CONST.ROTATION_SPEED; + const { renderer } = viewType; + const canvas = renderer.domElement; + if (!canvas) return; + const canvasCentre = { + x: canvas.offsetLeft + canvas.offsetWidth / 2, + y: canvas.offsetTop + canvas.offsetHeight / 2, + }; + if ( + this.action.rotation.screenInit.x === this.action.rotation.screenMove.x + && this.action.rotation.screenInit.y === this.action.rotation.screenMove.y + ) { + return; + } + + if ( + this.action.rotation.recentMouseVector.x === this.views[view].rayCaster.mouseVector.x + && this.action.rotation.recentMouseVector.y === this.views[view].rayCaster.mouseVector.y + ) { + return; + } + this.action.rotation.recentMouseVector = this.views[view].rayCaster.mouseVector.clone(); + if (Canvas3dViewImpl.isLeft(canvasCentre, this.action.rotation.screenInit, this.action.rotation.screenMove)) { + this.rotateCube(this.model.data.selected, -rotationSpeed, view); + this.rotatePlane(-rotationSpeed, view); + } else { + this.rotateCube(this.model.data.selected, rotationSpeed, view); + this.rotatePlane(rotationSpeed, view); + } + this.action.rotation.screenInit.x = this.action.rotation.screenMove.x; + this.action.rotation.screenInit.y = this.action.rotation.screenMove.y; + } + + private initiateAction(view: string, viewType: any): void { + const intersectsHelperResize = viewType.rayCaster.renderer.intersectObjects( + this.globalHelpers[view].resize, + false, + ); + const [state] = this.model.data.objects.filter( + (_state: any): boolean => _state.clientID === Number(this.model.data.selected[view].name), + ); + if (state.lock) return; + + if (intersectsHelperResize.length !== 0) { + this.action.resize.helper = viewType.rayCaster.mouseVector.clone(); + this.action.resize.status = true; + this.action.detected = true; + this.views.top.controls.enabled = false; + this.views.side.controls.enabled = false; + this.views.front.controls.enabled = false; + const { x, y, z } = this.model.data.selected[view].scale; + this.action.resize.initScales = { x, y, z }; + this.action.resize.memScales = { x, y, z }; + this.action.resize.frontBool = false; + this.action.resize.resizeVector = new THREE.Vector3(0, 0, 0); + return; + } + const intersectsHelperRotation = viewType.rayCaster.renderer.intersectObjects( + this.globalHelpers[view].rotation, + false, + ); + if (intersectsHelperRotation.length !== 0) { + this.action.rotation.helper = viewType.rayCaster.mouseVector.clone(); + this.action.rotation.status = true; + this.action.detected = true; + this.views.top.controls.enabled = false; + this.views.side.controls.enabled = false; + this.views.front.controls.enabled = false; + this.attachCamera(view as ViewType); + return; + } + + const intersectsBox = viewType.rayCaster.renderer.intersectObjects([this.model.data.selected[view]], false); + const intersectsPointCloud = viewType.rayCaster.renderer.intersectObjects( + [viewType.scene.getObjectByName(`${view}Plane`)], + true, + ); + if (intersectsBox.length !== 0) { + if (state.pinned) return; + this.action.translation.helper = viewType.rayCaster.mouseVector.clone(); + this.action.translation.inverseMatrix = intersectsBox[0].object.parent.matrixWorld.invert(); + this.action.translation.offset = intersectsPointCloud[0].point.sub( + new THREE.Vector3().setFromMatrixPosition(intersectsBox[0].object.matrixWorld), + ); + this.action.translation.status = true; + this.action.detected = true; + this.views.top.controls.enabled = false; + this.views.side.controls.enabled = false; + this.views.front.controls.enabled = false; + } + } + + public keyControls(key: any): void { + const { controls } = this.views.perspective; + if (!controls) return; + if (key.shiftKey) { + switch (key.code) { + case CameraAction.ROTATE_RIGHT: + controls.rotate(0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true); + break; + case CameraAction.ROTATE_LEFT: + controls.rotate(-0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true); + break; + case CameraAction.TILT_UP: + controls.rotate(0, -0.05 * THREE.MathUtils.DEG2RAD * this.speed, true); + break; + case CameraAction.TILT_DOWN: + controls.rotate(0, 0.05 * THREE.MathUtils.DEG2RAD * this.speed, true); + break; + default: + break; + } + } else if (key.altKey === true) { switch (key.code) { - case CAMERA_ACTION.ZOOM_IN: + case CameraAction.ZOOM_IN: controls.dolly(CONST.DOLLY_FACTOR, true); break; - case CAMERA_ACTION.ZOOM_OUT: + case CameraAction.ZOOM_OUT: controls.dolly(-CONST.DOLLY_FACTOR, true); break; - case CAMERA_ACTION.MOVE_LEFT: + case CameraAction.MOVE_LEFT: controls.truck(-0.01 * this.speed, 0, true); break; - case CAMERA_ACTION.MOVE_RIGHT: + case CameraAction.MOVE_RIGHT: controls.truck(0.01 * this.speed, 0, true); break; - case CAMERA_ACTION.MOVE_DOWN: + case CameraAction.MOVE_DOWN: controls.truck(0, -0.01 * this.speed, true); break; - case CAMERA_ACTION.MOVE_UP: + case CameraAction.MOVE_UP: controls.truck(0, 0.01 * this.speed, true); break; default: break; } - } - } - - public mouseControls(type: MouseInteraction, event: MouseEvent): void { - event.preventDefault(); - if (type === MouseInteraction.DOUBLE_CLICK && this.mode === Mode.DRAW) { - this.controller.drawData.enabled = false; - this.mode = Mode.IDLE; - const cancelEvent: CustomEvent = new CustomEvent('canvas.canceled'); - this.views.perspective.renderer.domElement.dispatchEvent(cancelEvent); - } else { - const canvas = this.views.perspective.renderer.domElement; - const rect = canvas.getBoundingClientRect(); - const { mouseVector } = this.views.perspective.rayCaster; - mouseVector.x = ((event.clientX - (canvas.offsetLeft + rect.left)) / canvas.clientWidth) * 2 - 1; - mouseVector.y = -((event.clientY - (canvas.offsetTop + rect.top)) / canvas.clientHeight) * 2 + 1; - - if (type === MouseInteraction.CLICK && this.mode === Mode.IDLE) { - const intersects = this.views.perspective.rayCaster.renderer.intersectObjects( - this.views.perspective.scene.children[0].children, - false, - ); - if (intersects.length !== 0) { - this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => { - // eslint-disable-next-line no-param-reassign - sceneItem.material.color = new THREE.Color(0xff0000); - }); - const selectedObject = intersects[0].object; - selectedObject.material.color = new THREE.Color(0x00ffff); - Object.keys(this.views).forEach((view: string): void => { - if (view !== ViewType.PERSPECTIVE) { - this.views[view as keyof Views].scene.children[0].children = [selectedObject.clone()]; - this.views[view as keyof Views].controls.fitToBox(selectedObject, false); - this.views[view as keyof Views].controls.zoom(view === ViewType.TOP ? -5 : -5, false); - } - this.views[view as keyof Views].scene.background = new THREE.Color(0x000000); - }); - this.selected.perspective = selectedObject as THREE.Mesh; - } - } + } else if (key.code === 'ControlLeft') { + this.action.selectable = !key.ctrlKey; } } diff --git a/cvat-canvas3d/src/typescript/consts.ts b/cvat-canvas3d/src/typescript/consts.ts index ea0b97c7..f4bda8fc 100644 --- a/cvat-canvas3d/src/typescript/consts.ts +++ b/cvat-canvas3d/src/typescript/consts.ts @@ -6,8 +6,17 @@ const BASE_GRID_WIDTH = 2; const MOVEMENT_FACTOR = 200; const DOLLY_FACTOR = 5; const MAX_DISTANCE = 100; -const MIN_DISTANCE = 0; +const MIN_DISTANCE = 0.3; const ZOOM_FACTOR = 7; +const ROTATION_HELPER_OFFSET = 0.1; +const CAMERA_REFERENCE = 'camRef'; +const CUBOID_EDGE_NAME = 'edges'; +const ROTATION_HELPER = 'rotationHelper'; +const ROTATION_SPEED = 80; +const FOV_DEFAULT = 1; +const FOV_MAX = 2; +const FOV_MIN = 0; +const FOV_INC = 0.08; export default { BASE_GRID_WIDTH, @@ -16,4 +25,13 @@ export default { MAX_DISTANCE, MIN_DISTANCE, ZOOM_FACTOR, + ROTATION_HELPER_OFFSET, + CAMERA_REFERENCE, + CUBOID_EDGE_NAME, + ROTATION_HELPER, + ROTATION_SPEED, + FOV_DEFAULT, + FOV_MAX, + FOV_MIN, + FOV_INC, }; diff --git a/cvat-canvas3d/src/typescript/cuboid.ts b/cvat-canvas3d/src/typescript/cuboid.ts index 47a37e33..d293e40e 100644 --- a/cvat-canvas3d/src/typescript/cuboid.ts +++ b/cvat-canvas3d/src/typescript/cuboid.ts @@ -2,6 +2,13 @@ // // SPDX-License-Identifier: MIT import * as THREE from 'three'; +import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils'; +import { ViewType } from './canvas3dModel'; +import constants from './consts'; + +export interface Indexable { + [key: string]: any; +} export class CuboidModel { public perspective: THREE.Mesh; @@ -9,12 +16,174 @@ export class CuboidModel { public side: THREE.Mesh; public front: THREE.Mesh; - public constructor() { + public constructor(outline: string, outlineColor: string) { const geometry = new THREE.BoxGeometry(1, 1, 1); - const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }); + const material = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + wireframe: false, + transparent: true, + opacity: 0.4, + }); this.perspective = new THREE.Mesh(geometry, material); + const geo = new THREE.EdgesGeometry(this.perspective.geometry); + const wireframe = new THREE.LineSegments( + geo, + outline === 'line' + ? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) + : new THREE.LineDashedMaterial({ + color: outlineColor, + dashSize: 0.05, + gapSize: 0.05, + }), + ); + wireframe.computeLineDistances(); + wireframe.renderOrder = 1; + this.perspective.add(wireframe); + this.top = new THREE.Mesh(geometry, material); this.side = new THREE.Mesh(geometry, material); this.front = new THREE.Mesh(geometry, material); + + const camRotateHelper = new THREE.Object3D(); + camRotateHelper.translateX(-2); + camRotateHelper.name = 'camRefRot'; + camRotateHelper.up = new THREE.Vector3(0, 0, 1); + camRotateHelper.lookAt(new THREE.Vector3(0, 0, 0)); + this.front.add(camRotateHelper.clone()); + } + + public setPosition(x: number, y: number, z: number): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + (this as Indexable)[view].position.set(x, y, z); + }); + } + + public setScale(x: number, y: number, z: number): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + (this as Indexable)[view].scale.set(x, y, z); + }); + } + + public setRotation(x: number, y: number, z: number): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + (this as Indexable)[view].rotation.set(x, y, z); + }); + } + + public attachCameraReference(): void { + // Attach Cam Reference + const topCameraReference = new THREE.Object3D(); + topCameraReference.translateZ(2); + topCameraReference.name = constants.CAMERA_REFERENCE; + this.top.add(topCameraReference); + this.top.userData = { ...this.top.userData, camReference: topCameraReference }; + + const sideCameraReference = new THREE.Object3D(); + sideCameraReference.translateY(2); + sideCameraReference.name = constants.CAMERA_REFERENCE; + this.side.add(sideCameraReference); + this.side.userData = { ...this.side.userData, camReference: sideCameraReference }; + + const frontCameraReference = new THREE.Object3D(); + frontCameraReference.translateX(2); + frontCameraReference.name = constants.CAMERA_REFERENCE; + this.front.add(frontCameraReference); + this.front.userData = { ...this.front.userData, camReference: frontCameraReference }; + } + + public getReferenceCoordinates(viewType: string): THREE.Vector3 { + const { elements } = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE).matrixWorld; + return new THREE.Vector3(elements[12], elements[13], elements[14]); + } + + public setName(clientId: any): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + (this as Indexable)[view].name = clientId; + }); + } + + public setOriginalColor(color: string): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + ((this as Indexable)[view] as any).originalColor = color; + }); + } + + public setColor(color: string): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + ((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color); + }); + } + + public setOpacity(opacity: number): void { + [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { + ((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100; + }); + } +} + +export function setEdges(instance: THREE.Mesh): THREE.LineSegments { + const edges = new THREE.EdgesGeometry(instance.geometry); + const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 })); + line.name = constants.CUBOID_EDGE_NAME; + instance.add(line); + return line; +} + +export function setTranslationHelper(instance: THREE.Mesh): void { + const sphereGeometry = new THREE.SphereGeometry(0.1); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); + instance.geometry.deleteAttribute('normal'); + instance.geometry.deleteAttribute('uv'); + // eslint-disable-next-line no-param-reassign + instance.geometry = BufferGeometryUtils.mergeVertices(instance.geometry); + const vertices = []; + const positionAttribute = instance.geometry.getAttribute('position'); + for (let i = 0; i < positionAttribute.count; i++) { + const vertex = new THREE.Vector3(); + vertex.fromBufferAttribute(positionAttribute, i); + vertices.push(vertex); + } + const helpers = []; + for (let i = 0; i < vertices.length; i++) { + helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); + helpers[i].position.set(vertices[i].x, vertices[i].y, vertices[i].z); + helpers[i].up.set(0, 0, 1); + helpers[i].name = 'resizeHelper'; + instance.add(helpers[i]); + helpers[i].scale.set(1 / instance.scale.x, 1 / instance.scale.y, 1 / instance.scale.z); + } + // eslint-disable-next-line no-param-reassign + instance.userData = { ...instance.userData, resizeHelpers: helpers }; +} + +export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void { + const sphereGeometry = new THREE.SphereGeometry(0.1); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); + const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); + rotationHelper.name = constants.ROTATION_HELPER; + switch (viewType) { + case ViewType.TOP: + rotationHelper.position.set( + (instance.geometry as THREE.BoxGeometry).parameters.height / 2 + constants.ROTATION_HELPER_OFFSET, + instance.position.y, + instance.position.z, + ); + instance.add(rotationHelper.clone()); + // eslint-disable-next-line no-param-reassign + instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; + break; + case ViewType.SIDE: + case ViewType.FRONT: + rotationHelper.position.set( + instance.position.x, + instance.position.y, + (instance.geometry as THREE.BoxGeometry).parameters.depth / 2 + constants.ROTATION_HELPER_OFFSET, + ); + instance.add(rotationHelper.clone()); + // eslint-disable-next-line no-param-reassign + instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; + break; + default: + break; } } diff --git a/cvat-core/README.md b/cvat-core/README.md index 22d8faa9..e1deebe9 100644 --- a/cvat-core/README.md +++ b/cvat-core/README.md @@ -2,7 +2,7 @@ ## Description -This CVAT module is a client-side JavaScipt library to management of objects, frames, logs, etc. +This CVAT module is a client-side JavaScript library for management of objects, frames, logs, etc. It contains the core logic of the Computer Vision Annotation Tool. ## Versioning diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index 5fecc130..6daac702 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.12.1", + "version": "3.13.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-core/package.json b/cvat-core/package.json index a04727d5..e71098c0 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.12.1", + "version": "3.13.3", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "babel.config.js", "scripts": { diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index a28f9508..26486bbe 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -788,6 +788,7 @@ () => { importedArray.forEach((object) => { object.removed = false; + object.serverID = undefined; }); }, importedArray.map((object) => object.clientID), @@ -868,7 +869,7 @@ const deepSearch = (deepSearchFrom, deepSearchTo) => { // deepSearchFrom is expected to be a frame that doesn't satisfy a filter - // deepSearchTo is expected to be a frame that satifies a filter + // deepSearchTo is expected to be a frame that satisfies a filter let [prev, next] = [deepSearchFrom, deepSearchTo]; // half division method instead of linear search diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 7f5910e4..2bd2d61e 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -50,7 +50,7 @@ throw new DataError(`Points must have exact 8 points, but got ${points.length / 2}`); } } else { - throw new ArgumentError(`Unknown value of shapeType has been recieved ${shapeType}`); + throw new ArgumentError(`Unknown value of shapeType has been received ${shapeType}`); } } @@ -248,6 +248,18 @@ this.label = label; this.attributes = {}; this.appendDefaultAttributes(label); + + // Try to keep old attributes if name matches and old value is still valid + for (const attribute of redoLabel.attributes) { + for (const oldAttribute of undoLabel.attributes) { + if ( + attribute.name === oldAttribute.name + && validateAttributeValue(undoAttributes[oldAttribute.id], attribute) + ) { + this.attributes[attribute.id] = undoAttributes[oldAttribute.id]; + } + } + } const redoAttributes = { ...this.attributes }; this.history.do( @@ -324,11 +336,16 @@ checkObjectType('points', data.points, null, Array); checkNumberOfPoints(this.shapeType, data.points); // cut points - const { width, height } = this.frameMeta[frame]; + const { width, height, filename } = this.frameMeta[frame]; fittedPoints = fitPoints(this.shapeType, data.points, width, height); - - if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) { - fittedPoints = []; + let check = true; + if (filename && filename.slice(filename.length - 3) === 'pcd') { + check = false; + } + if (check) { + if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) { + fittedPoints = []; + } } } @@ -1447,7 +1464,7 @@ / Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), ); } else { - // The link below works for lines (which have infinit length) + // The link below works for lines (which have infinite length) // There is a case when perpendicular doesn't cross the edge // In this case we don't use the computed distance // Instead we use just distance to the nearest point diff --git a/cvat-core/src/annotations-saver.js b/cvat-core/src/annotations-saver.js index 92c01500..3d5e69cc 100644 --- a/cvat-core/src/annotations-saver.js +++ b/cvat-core/src/annotations-saver.js @@ -1,11 +1,11 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2021 Intel Corporation // // SPDX-License-Identifier: MIT (() => { const serverProxy = require('./server-proxy'); const { Task } = require('./session'); - const { ScriptingError } = './exceptions'; + const { ScriptingError } = require('./exceptions'); class AnnotationsSaver { constructor(version, collection, session) { diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 5e9ff610..787c4303 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -115,6 +115,7 @@ cvat.users.get.implementation = async (filter) => { checkFilter(filter, { id: isInteger, + is_active: isBoolean, self: isBoolean, search: isString, limit: isInteger, @@ -227,6 +228,14 @@ checkExclusiveFields(filter, ['id', 'search'], ['page', 'withoutTasks']); + if (typeof filter.withoutTasks === 'undefined') { + if (typeof filter.id === 'undefined') { + filter.withoutTasks = true; + } else { + filter.withoutTasks = false; + } + } + const searchParams = new URLSearchParams(); for (const field of ['name', 'assignee', 'owner', 'search', 'status', 'id', 'page', 'withoutTasks']) { if (Object.prototype.hasOwnProperty.call(filter, field)) { @@ -238,7 +247,10 @@ // prettier-ignore const projects = projectsData.map((project) => { if (filter.withoutTasks) { + project.task_ids = project.tasks; project.tasks = []; + } else { + project.task_ids = project.tasks.map((task) => task.id); } return project; }).map((project) => new Project(project)); diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index a5d36b6c..7067d560 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -20,6 +20,7 @@ function build() { const { Project } = require('./project'); const { Attribute, Label } = require('./labels'); const MLModel = require('./ml-model'); + const { FrameData } = require('./frames'); const enums = require('./enums'); @@ -765,6 +766,7 @@ function build() { Comment, Issue, Review, + FrameData, }, }; diff --git a/cvat-core/src/common.js b/cvat-core/src/common.js index 80134d06..7ac8660b 100644 --- a/cvat-core/src/common.js +++ b/cvat-core/src/common.js @@ -34,7 +34,7 @@ for (const prop in filter) { if (Object.prototype.hasOwnProperty.call(filter, prop)) { if (!(prop in fields)) { - throw new ArgumentError(`Unsupported filter property has been recieved: "${prop}"`); + throw new ArgumentError(`Unsupported filter property has been received: "${prop}"`); } else if (!fields[prop](filter[prop])) { throw new ArgumentError(`Received filter property "${prop}" is not satisfied for checker`); } @@ -104,6 +104,37 @@ } negativeIDGenerator.start = -1; + class FieldUpdateTrigger { + constructor(initialFields) { + const data = { ...initialFields }; + + Object.defineProperties( + this, + Object.freeze({ + ...Object.assign( + {}, + ...Array.from(Object.keys(data), (key) => ({ + [key]: { + get: () => data[key], + set: (value) => { + data[key] = value; + }, + enumerable: true, + }, + })), + ), + reset: { + value: () => { + Object.keys(data).forEach((key) => { + data[key] = false; + }); + }, + }, + }), + ); + } + } + module.exports = { isBoolean, isInteger, @@ -114,5 +145,6 @@ negativeIDGenerator, checkExclusiveFields, camelToSnake, + FieldUpdateTrigger, }; })(); diff --git a/cvat-core/src/config.js b/cvat-core/src/config.js index 93d95779..3dcddc2b 100644 --- a/cvat-core/src/config.js +++ b/cvat-core/src/config.js @@ -1,8 +1,8 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2021 Intel Corporation // // SPDX-License-Identifier: MIT module.exports = { - backendAPI: 'http://localhost:7000/api/v1', + backendAPI: '/api/v1', proxy: false, }; diff --git a/cvat-core/src/frames.js b/cvat-core/src/frames.js index bd1009ef..fb4c4650 100644 --- a/cvat-core/src/frames.js +++ b/cvat-core/src/frames.js @@ -19,7 +19,15 @@ */ class FrameData { constructor({ - width, height, name, taskID, frameNumber, startFrame, stopFrame, decodeForward, + width, + height, + name, + taskID, + frameNumber, + startFrame, + stopFrame, + decodeForward, + has_related_context: hasRelatedContext, }) { Object.defineProperties( this, @@ -72,6 +80,18 @@ value: frameNumber, writable: false, }, + /** + * True if some context images are associated with this frame + * @name hasRelatedContext + * @type {boolean} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + hasRelatedContext: { + value: hasRelatedContext, + writable: false, + }, startFrame: { value: startFrame, writable: false, @@ -104,6 +124,14 @@ const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); return result; } + + get imageData() { + return this._data.imageData; + } + + set imageData(imageData) { + this._data.imageData = imageData; + } } FrameData.prototype.data.implementation = async function (onServerRequest) { diff --git a/cvat-core/src/ml-model.js b/cvat-core/src/ml-model.js index 950f7004..e16cf24e 100644 --- a/cvat-core/src/ml-model.js +++ b/cvat-core/src/ml-model.js @@ -17,7 +17,8 @@ class MLModel { this._params = { canvas: { minPosVertices: data.min_pos_points, - enableNegVertices: true, + minNegVertices: data.min_neg_points, + startWithBox: data.startswith_box, }, }; } diff --git a/cvat-core/src/project.js b/cvat-core/src/project.js index 0e2b5478..b66eab49 100644 --- a/cvat-core/src/project.js +++ b/cvat-core/src/project.js @@ -8,6 +8,7 @@ const { ArgumentError } = require('./exceptions'); const { Task } = require('./session'); const { Label } = require('./labels'); + const { getPreview } = require('./frames'); const User = require('./user'); /** @@ -17,7 +18,7 @@ class Project { /** * In a fact you need use the constructor only if you want to create a project - * @param {object} initialData - Object which is used for initalization + * @param {object} initialData - Object which is used for initialization *
It can contain keys: *
  • name *
  • labels @@ -34,6 +35,7 @@ updated_date: undefined, task_subsets: undefined, training_project: undefined, + task_ids: undefined, }; for (const property in data) { @@ -58,9 +60,9 @@ data.tasks.push(taskInstance); } } - if (!data.task_subsets && data.tasks.length) { + if (!data.task_subsets) { const subsetsSet = new Set(); - for (const task in data.tasks) { + for (const task of data.tasks) { if (task.subset) subsetsSet.add(task.subset); } data.task_subsets = Array.from(subsetsSet); @@ -254,6 +256,22 @@ ); } + /** + * Get the first frame of the first task of a project for preview + * @method preview + * @memberof Project + * @returns {string} - jpeg encoded image + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async preview() { + const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.preview); + return result; + } + /** * Method updates data of a created project or creates new project from scratch * @method save @@ -331,4 +349,12 @@ const result = await serverProxy.projects.delete(this.id); return result; }; + + Project.prototype.preview.implementation = async function () { + if (!this._internalData.task_ids.length) { + return ''; + } + const frameData = await getPreview(this._internalData.task_ids[0]); + return frameData; + }; })(); diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index f3627d4e..524ceeaf 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -490,6 +490,59 @@ }); } + async function exportTask(id) { + const { backendAPI } = config; + const url = `${backendAPI}/tasks/${id}`; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios.get(`${url}?action=export`, { + proxy: config.proxy, + }); + if (response.status === 202) { + setTimeout(request, 3000); + } else { + resolve(`${url}?action=download`); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); + } + + async function importTask(file) { + const { backendAPI } = config; + + let taskData = new FormData(); + taskData.append('task_file', file); + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios.post(`${backendAPI}/tasks?action=import`, taskData, { + proxy: config.proxy, + }); + if (response.status === 202) { + taskData = new FormData(); + taskData.append('rq_id', response.data.rq_id); + setTimeout(request, 3000); + } else { + const importedTask = await getTasks(`?id=${response.data.id}`); + resolve(importedTask[0]); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); + } + async function createTask(taskSpec, taskDataSpec, onUpdate) { const { backendAPI } = config; @@ -756,11 +809,7 @@ }, ); } catch (errorData) { - const code = errorData.response ? errorData.response.status : errorData.code; - throw new ServerError( - `Could not get Image Context of the frame for the task ${tid} from the server`, - code, - ); + throw generateError(errorData); } return response.data; @@ -1161,6 +1210,8 @@ createTask, deleteTask, exportDataset, + exportTask, + importTask, }), writable: false, }, diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index daaee04a..a1136030 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -16,8 +16,9 @@ const User = require('./user'); const Issue = require('./issue'); const Review = require('./review'); + const { FieldUpdateTrigger } = require('./common'); - function buildDublicatedAPI(prototype) { + function buildDuplicatedAPI(prototype) { Object.defineProperties(prototype, { annotations: Object.freeze({ value: { @@ -575,7 +576,7 @@ * Create a log and add it to a log collection
    * Durable logs will be added after "close" method is called for them
    * The fields "task_id" and "job_id" automatically added when add logs - * throught a task or a job
    + * through a task or a job
    * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
    * Payload of ignored logs are shallowly combined to previous logs of the same type * @method log @@ -734,11 +735,11 @@ task: undefined, }; - let updatedFields = { + const updatedFields = new FieldUpdateTrigger({ assignee: false, reviewer: false, status: false, - }; + }); for (const property in data) { if (Object.prototype.hasOwnProperty.call(data, property)) { @@ -865,9 +866,6 @@ }, __updatedFields: { get: () => updatedFields, - set: (fields) => { - updatedFields = fields; - }, }, }), ); @@ -1001,7 +999,7 @@ class Task extends Session { /** * In a fact you need use the constructor only if you want to create a task - * @param {object} initialData - Object which is used for initalization + * @param {object} initialData - Object which is used for initialization *
    It can contain keys: *
  • name *
  • assignee @@ -1040,13 +1038,14 @@ dimension: undefined, }; - let updatedFields = { + const updatedFields = new FieldUpdateTrigger({ name: false, assignee: false, bug_tracker: false, subset: false, labels: false, - }; + project_id: false, + }); for (const property in data) { if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { @@ -1126,11 +1125,18 @@ * @name projectId * @type {integer|null} * @memberof module:API.cvat.classes.Task - * @readonly * @instance */ projectId: { get: () => data.project_id, + set: (projectId) => { + if (!Number.isInteger(projectId) || projectId <= 0) { + throw new ArgumentError('Value must be a positive integer'); + } + + updatedFields.project_id = true; + data.project_id = projectId; + }, }, /** * @name status @@ -1558,9 +1564,6 @@ }, __updatedFields: { get: () => updatedFields, - set: (fields) => { - updatedFields = fields; - }, }, }), ); @@ -1661,6 +1664,36 @@ const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete); return result; } + + /** + * Method makes a backup of a task + * @method export + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async export() { + const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.export); + return result; + } + + /** + * Method imports a task from a backup + * @method import + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + static async import(file) { + const result = await PluginRegistry.apiWrapper.call(this, Task.import, file); + return result; + } } module.exports = { @@ -1694,8 +1727,8 @@ closeSession, } = require('./annotations'); - buildDublicatedAPI(Job.prototype); - buildDublicatedAPI(Task.prototype); + buildDuplicatedAPI(Job.prototype); + buildDuplicatedAPI(Task.prototype); Job.prototype.save.implementation = async function () { if (this.id) { @@ -1721,11 +1754,7 @@ await serverProxy.jobs.save(this.id, jobData); - this.__updatedFields = { - status: false, - assignee: false, - reviewer: false, - }; + this.__updatedFields.reset(); return this; } @@ -2000,6 +2029,9 @@ case 'subset': taskData.subset = this.subset; break; + case 'project_id': + taskData.project_id = this.projectId; + break; case 'labels': taskData.labels = [...this._internalData.labels.map((el) => el.toJSON())]; break; @@ -2011,13 +2043,7 @@ await serverProxy.tasks.saveTask(this.id, taskData); - this.updatedFields = { - assignee: false, - name: false, - bugTracker: false, - subset: false, - labels: false, - }; + this.__updatedFields.reset(); return this; } @@ -2077,6 +2103,16 @@ return result; }; + Task.prototype.export.implementation = async function () { + const result = await serverProxy.tasks.exportTask(this.id); + return result; + }; + + Task.import.implementation = async function (file) { + const result = await serverProxy.tasks.importTask(file); + return result; + }; + Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) { if (!Number.isInteger(frame) || frame < 0) { throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index b7a44a43..4377db5a 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -211,7 +211,7 @@ describe('Feature: put annotations', () => { expect(task.annotations.put([state1])).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); - test('put shape without points and with invalud points to a task', async () => { + test('put shape without points and with invalid points to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; await task.annotations.clear(true); const state = new window.cvat.classes.ObjectState({ @@ -311,6 +311,27 @@ describe('Feature: check unsaved changes', () => { }); describe('Feature: save annotations', () => { + test('create, save, undo, save, redo save', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.get(0); + const state = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + zOrder: 0, + }); + + await task.annotations.put([state]); + await task.annotations.save(); + await task.actions.undo(); + await task.annotations.save(); + await task.actions.redo(); + await task.annotations.save(); + }); + test('create & save annotations for a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; let annotations = await task.annotations.get(0); diff --git a/cvat-core/tests/api/projects.js b/cvat-core/tests/api/projects.js index 5a01b7d1..8a0fe3b0 100644 --- a/cvat-core/tests/api/projects.js +++ b/cvat-core/tests/api/projects.js @@ -16,7 +16,7 @@ const { Project } = require('../../src/project'); describe('Feature: get projects', () => { test('get all projects', async () => { - const result = await window.cvat.projects.get(); + const result = await window.cvat.projects.get({ withoutTasks: false }); expect(Array.isArray(result)).toBeTruthy(); expect(result).toHaveLength(2); for (const el of result) { diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index eb7374d6..17438499 100644 --- a/cvat-core/tests/api/tasks.js +++ b/cvat-core/tests/api/tasks.js @@ -95,6 +95,7 @@ describe('Feature: save a task', () => { result[0].bugTracker = 'newBugTracker'; result[0].name = 'New Task Name'; + result[0].projectId = 6; result[0].save(); @@ -104,6 +105,7 @@ describe('Feature: save a task', () => { expect(result[0].bugTracker).toBe('newBugTracker'); expect(result[0].name).toBe('New Task Name'); + expect(result[0].projectId).toBe(6); }); test('save some new labels in a task', async () => { diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index 2984aeae..17e886ca 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -984,6 +984,7 @@ const tasksDummyData = { }, assignee: null, bug_tracker: '', + project_id: 2, created_date: '2019-05-15T11:40:19.487999+03:00', updated_date: '2019-05-15T16:58:27.992785+03:00', overlap: 5, diff --git a/cvat-core/tests/mocks/server-proxy.mock.js b/cvat-core/tests/mocks/server-proxy.mock.js index 4357e176..a5111756 100644 --- a/cvat-core/tests/mocks/server-proxy.mock.js +++ b/cvat-core/tests/mocks/server-proxy.mock.js @@ -14,16 +14,18 @@ const { frameMetaDummyData, } = require('./dummy-data.mock'); -function QueryStringToJSON(query) { +function QueryStringToJSON(query, ignoreList = []) { const pairs = [...new URLSearchParams(query).entries()]; const result = {}; for (const pair of pairs) { const [key, value] = pair; - if (['id'].includes(key)) { - result[key] = +value; - } else { - result[key] = value; + if (!ignoreList.includes(key)) { + if (['id'].includes(key)) { + result[key] = +value; + } else { + result[key] = value; + } } } @@ -73,7 +75,7 @@ class ServerProxy { } async function getProjects(filter = '') { - const queries = QueryStringToJSON(filter); + const queries = QueryStringToJSON(filter, ['without_tasks']); const result = projectsDummyData.results.filter((x) => { for (const key in queries) { if (Object.prototype.hasOwnProperty.call(queries, key)) { diff --git a/cvat-data/package-lock.json b/cvat-data/package-lock.json index 5bbfb275..1f432adb 100644 --- a/cvat-data/package-lock.json +++ b/cvat-data/package-lock.json @@ -3432,28 +3432,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -3464,14 +3464,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -3482,42 +3482,42 @@ }, "chownr": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -3527,28 +3527,28 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -3558,14 +3558,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -3582,7 +3582,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -3597,14 +3597,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -3614,7 +3614,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -3624,7 +3624,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -3635,7 +3635,7 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true @@ -3649,7 +3649,7 @@ }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -3659,14 +3659,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -3676,7 +3676,7 @@ }, "minipass": { "version": "2.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -3687,7 +3687,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -3707,14 +3707,14 @@ }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -3726,7 +3726,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -3745,7 +3745,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -3756,14 +3756,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -3774,7 +3774,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -3787,21 +3787,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -3811,21 +3811,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -3836,21 +3836,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "resolved": "", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -3872,7 +3872,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -3888,7 +3888,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -3898,49 +3898,49 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -3952,7 +3952,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -3962,7 +3962,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -3972,14 +3972,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "resolved": "", + "resolved": false, "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -3995,14 +3995,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -4012,14 +4012,14 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true @@ -4832,9 +4832,9 @@ "dev": true }, "jszip": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", - "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.0.tgz", + "integrity": "sha512-Y2OlFIzrDOPWUnpU0LORIcDn2xN7rC9yKffFM/7pGhQuhO+SUhfm2trkJ/S5amjFvem0Y+1EALz/MEPkvHXVNw==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", diff --git a/cvat-data/package.json b/cvat-data/package.json index bead5acf..57244c52 100644 --- a/cvat-data/package.json +++ b/cvat-data/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "async-mutex": "^0.3.1", - "jszip": "3.6.0" + "jszip": "3.7.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-data/src/js/3rdparty/README.md b/cvat-data/src/js/3rdparty/README.md index d6518b90..622e187f 100644 --- a/cvat-data/src/js/3rdparty/README.md +++ b/cvat-data/src/js/3rdparty/README.md @@ -17,52 +17,52 @@ So, we have solved to write patch file for this library. It modifies source code a little to support our scenario of using. ### How to build awc.wasm and Decoder.js -1. Clone Emscripten SDK, install and activate the latest fastcomp SDK: - ```sh - git clone https://github.com/emscripten-core/emsdk.git && cd emsdk - ``` - ```sh - ./emsdk install latest-fastcomp - ``` - ```sh - ./emsdk activate latest-fastcomp - ``` +1. Clone Emscripten SDK, install and activate the latest fastcomp SDK: + ```sh + git clone https://github.com/emscripten-core/emsdk.git && cd emsdk + ``` + ```sh + ./emsdk install latest-fastcomp + ``` + ```sh + ./emsdk activate latest-fastcomp + ``` -1. Clone Broadway.js - ```sh - git clone https://github.com/mbebenita/Broadway.git && cd Broadway/Decoder - ``` +1. Clone Broadway.js + ```sh + git clone https://github.com/mbebenita/Broadway.git && cd Broadway/Decoder + ``` -1. Edit `make.py`: - - Remove or comment the following options: - `'-s', 'NO_BROWSER=1',`\ - `'-s', 'PRECISE_I64_MATH=0',` - - Remove `"HEAP8", "HEAP16", "HEAP32"` from the `EXPORTED_FUNCTIONS` list. - - Increase total memory to make possible decode 4k videos - (or try to enable `ALLOW_MEMORY_GROWTH`, but this option has not been tested):\ - `'-s', 'TOTAL_MEMORY=' + str(100*1024*1024),` - - Add the following options:\ - `'-s', "ENVIRONMENT='worker'",`\ - `'-s', 'WASM=1',` +1. Edit `make.py`: + - Remove or comment the following options: + `'-s', 'NO_BROWSER=1',`\ + `'-s', 'PRECISE_I64_MATH=0',` + - Remove `"HEAP8", "HEAP16", "HEAP32"` from the `EXPORTED_FUNCTIONS` list. + - Increase total memory to make possible decode 4k videos + (or try to enable `ALLOW_MEMORY_GROWTH`, but this option has not been tested):\ + `'-s', 'TOTAL_MEMORY=' + str(100*1024*1024),` + - Add the following options:\ + `'-s', "ENVIRONMENT='worker'",`\ + `'-s', 'WASM=1',` -1. Activate emsdk environment and build Broadway.js: - ```sh - . /tmp/emsdk/emsdk_env.sh - ``` - ```sh - python2 make.py - ``` +1. Activate emsdk environment and build Broadway.js: + ```sh + . /tmp/emsdk/emsdk_env.sh + ``` + ```sh + python2 make.py + ``` -1. Copy the following files to cvat-data 3rdparty source folder: - ```sh - cd .. - ``` - ```sh - cp Player/avc.wasm Player/Decoder.js Player/mp4.js /cvat-data/src/ - ``` - ```sh - js/3rdparty - ``` +1. Copy the following files to cvat-data 3rdparty source folder: + ```sh + cd .. + ``` + ```sh + cp Player/avc.wasm Player/Decoder.js Player/mp4.js /cvat-data/src/ + ``` + ```sh + js/3rdparty + ``` ### How work with a patch file ```bash diff --git a/cvat-ui/README.md b/cvat-ui/README.md index c32879e6..349d6ff2 100644 --- a/cvat-ui/README.md +++ b/cvat-ui/README.md @@ -36,5 +36,5 @@ npm run build npm run build -- --mode=development # without a minification ``` -Important: You also have to run CVAT REST API server (please read `CONTRIBUTING.md`) +Important: You also have to run CVAT REST API server (please read `https://openvinotoolkit.github.io/cvat/docs/contributing/`) to correct working since UI gets all necessary data (tasks, users, annotations) from there diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index ae3f43c1..e1a4936e 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.19.1", + "version": "1.21.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1244,9 +1244,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.168", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", - "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==" + "version": "4.14.170", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", + "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==" }, "@types/minimatch": { "version": "3.0.3", @@ -1272,9 +1272,9 @@ "integrity": "sha512-1fuOulBHWIxAPLBtLms+UtbeRDt6rL7gP5R+Yugfzdg+poCLxXqXTE8i+FpYeiytGRLUEtnFkjsY/j+usbQBqw==" }, "@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "@types/q": { "version": "1.5.2", @@ -1283,9 +1283,9 @@ "dev": true }, "@types/react": { - "version": "16.14.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.5.tgz", - "integrity": "sha512-YRRv9DNZhaVTVRh9Wmmit7Y0UFhEVqXqCSw3uazRWMxa2x85hWQZ5BN24i7GXZbaclaLXEcodEeIHsjBA8eAMw==", + "version": "16.14.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.10.tgz", + "integrity": "sha512-QadBsMyF6ldjEAXEhsmEW/L0uBDJT8yw7Qoe5sRnEKVrzMkiYoJwqoL5TKJOlArsn/wvIJM/XdVzkdL6+AS64Q==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1302,9 +1302,9 @@ } }, "@types/react-dom": { - "version": "16.9.12", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz", - "integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==", + "version": "16.9.13", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.13.tgz", + "integrity": "sha512-34Hr3XnmUSJbUVDxIw/e7dhQn2BJZhJmlAaPyPwfTQyuVS9mV/CeyghFcXyvkJXxI7notQJz8mF8FeCVvloJrA==", "requires": { "@types/react": "^16" } @@ -1329,9 +1329,9 @@ } }, "@types/react-router": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.13.tgz", - "integrity": "sha512-ZIuaO9Yrln54X6elg8q2Ivp6iK6p4syPsefEYAhRDAoqNh48C8VYUmB9RkXjKSQAJSJV0mbIFCX7I4vZDcHrjg==", + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz", + "integrity": "sha512-z3UlMG/x91SFEVmmvykk9FLTliDvfdIUky4k2rCfXWQ0NKbrP8o9BTCaCTPuYsB8gDkUnUmkcA2vYlm2DR+HAA==", "requires": { "@types/history": "*", "@types/react": "*" @@ -1377,9 +1377,9 @@ "integrity": "sha512-8k/67Z95Goa6Lznuykxkfhq9YU3l1Qe6LNZmwde1u7802a3x8v44oq0j91DICclxatTr0rNnhXx7+VTIetSrSQ==" }, "@types/scheduler": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", - "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@typescript-eslint/eslint-plugin": { "version": "4.5.0", @@ -2616,60 +2616,33 @@ "postcss-value-parser": "^4.0.2" }, "dependencies": { - "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" - } - }, "electron-to-chromium": { "version": "1.3.322", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", - "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", - "dev": true + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==" }, "node-releases": { "version": "1.1.42", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", - "dev": true, "requires": { "semver": "^6.3.0" } }, - "postcss": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", - "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -3078,14 +3051,30 @@ } }, "browserslist": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.2.tgz", - "integrity": "sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001004", - "electron-to-chromium": "^1.3.295", - "node-releases": "^1.1.38" + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "dependencies": { + "electron-to-chromium": { + "version": "1.3.738", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz", + "integrity": "sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + } } }, "buffer": { @@ -3462,6 +3451,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4015,9 +4010,9 @@ } }, "csstype": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", - "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" }, "currently-unhandled": { "version": "0.4.1", @@ -4999,7 +4994,6 @@ "@babel/plugin-transform-typeof-symbol": "^7.2.0", "@babel/plugin-transform-unicode-regex": "^7.4.4", "@babel/types": "^7.5.5", - "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", @@ -5879,25 +5873,13 @@ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.3.tgz", "integrity": "sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q==", "requires": { - "browserslist": "^4.8.0", "caniuse-lite": "^1.0.30001012", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.23", "postcss-value-parser": "^4.0.2" }, "dependencies": { - "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", - "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" - } - }, "caniuse-lite": { "version": "1.0.30001016", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz", @@ -5916,16 +5898,6 @@ "semver": "^6.3.0" } }, - "postcss": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", - "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6305,16 +6277,6 @@ "pako": "~1.0.5" } }, - "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", - "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" - } - }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -6925,7 +6887,6 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", "requires": { - "browserslist": "^4.6.2", "core-js-pure": "3.1.4", "semver": "^6.1.1" }, @@ -7057,17 +7018,13 @@ "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==" }, "css-has-pseudo": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", "requires": { - "postcss": "^7.0.6", "postcss-selector-parser": "^5.0.0-rc.4" }, "dependencies": { @@ -7098,7 +7055,6 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.23", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", "postcss-modules-scope": "^2.1.1", @@ -7107,16 +7063,6 @@ "schema-utils": "^2.6.0" }, "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "schema-utils": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", @@ -7136,10 +7082,7 @@ "css-prefers-color-scheme": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==" }, "cssdb": { "version": "4.4.0", @@ -7361,15 +7304,6 @@ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, "dns-txt": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", @@ -9396,10 +9330,7 @@ "icss-utils": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "requires": { - "postcss": "^7.0.14" - } + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==" }, "ieee754": { "version": "1.1.13", @@ -10519,7 +10450,6 @@ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "requires": { - "dns-packet": "^1.3.1", "thunky": "^1.0.2" } }, @@ -11474,29 +11404,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, - "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, "postcss-attribute-case-insensitive": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0" }, "dependencies": { @@ -11522,7 +11434,6 @@ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -11532,7 +11443,6 @@ "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", "postcss-values-parser": "^2.0.0" } }, @@ -11541,7 +11451,6 @@ "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", "requires": { - "postcss": "^7.0.14", "postcss-values-parser": "^2.0.1" } }, @@ -11551,7 +11460,6 @@ "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -11560,24 +11468,19 @@ "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-custom-media": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "requires": { - "postcss": "^7.0.14" - } + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==" }, "postcss-custom-properties": { "version": "8.0.11", "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", "requires": { - "postcss": "^7.0.17", "postcss-values-parser": "^2.0.1" } }, @@ -11586,7 +11489,6 @@ "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -11612,7 +11514,6 @@ "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -11638,7 +11539,6 @@ "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", "requires": { - "postcss": "^7.0.5", "postcss-values-parser": "^2.0.0" } }, @@ -11647,48 +11547,34 @@ "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-focus-visible": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==" }, "postcss-focus-within": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==" }, "postcss-font-variant": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", - "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==" }, "postcss-gap-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==" }, "postcss-image-set-function": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -11697,8 +11583,7 @@ "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", "requires": { - "lodash.template": "^4.5.0", - "postcss": "^7.0.2" + "lodash.template": "^4.5.0" } }, "postcss-lab-function": { @@ -11707,7 +11592,6 @@ "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -11726,7 +11610,6 @@ "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "requires": { "loader-utils": "^1.1.0", - "postcss": "^7.0.0", "postcss-load-config": "^2.0.0", "schema-utils": "^1.0.0" } @@ -11734,26 +11617,17 @@ "postcss-logical": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==" }, "postcss-media-minmax": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==" }, "postcss-modules-extract-imports": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==" }, "postcss-modules-local-by-default": { "version": "3.0.2", @@ -11761,7 +11635,6 @@ "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", "requires": { "icss-utils": "^4.1.1", - "postcss": "^7.0.16", "postcss-selector-parser": "^6.0.2", "postcss-value-parser": "^4.0.0" } @@ -11771,7 +11644,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz", "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==", "requires": { - "postcss": "^7.0.6", "postcss-selector-parser": "^6.0.0" } }, @@ -11780,40 +11652,29 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" + "icss-utils": "^4.0.0" } }, "postcss-nesting": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==" }, "postcss-overflow-shorthand": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==" }, "postcss-page-break": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==" }, "postcss-place": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -11823,13 +11684,11 @@ "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", "requires": { "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", "caniuse-lite": "^1.0.30000981", "css-blank-pseudo": "^0.1.4", "css-has-pseudo": "^0.10.0", "css-prefers-color-scheme": "^3.1.1", "cssdb": "^4.4.0", - "postcss": "^7.0.17", "postcss-attribute-case-insensitive": "^4.0.1", "postcss-color-functional-notation": "^2.0.1", "postcss-color-gray": "^5.0.0", @@ -11866,7 +11725,6 @@ "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -11890,18 +11748,14 @@ "postcss-replace-overflow-wrap": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==" }, "postcss-selector-matches": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "balanced-match": "^1.0.0" } }, "postcss-selector-not": { @@ -11909,8 +11763,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "balanced-match": "^1.0.0" } }, "postcss-selector-parser": { @@ -14468,18 +14321,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, - "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", - "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", - "escalade": "^3.1.1", - "node-releases": "^1.1.70" - } - }, "caniuse-lite": { "version": "1.0.30001185", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", @@ -14961,14 +14802,6 @@ "signal-exit": "^3.0.2" } }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", @@ -16032,7 +15865,6 @@ "@babel/plugin-transform-typeof-symbol": "^7.2.0", "@babel/plugin-transform-unicode-regex": "^7.4.4", "@babel/types": "^7.5.5", - "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", @@ -16665,25 +16497,13 @@ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.3.tgz", "integrity": "sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q==", "requires": { - "browserslist": "^4.8.0", "caniuse-lite": "^1.0.30001012", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.23", "postcss-value-parser": "^4.0.2" }, "dependencies": { - "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", - "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" - } - }, "camera-controls": { "version": "1.25.3", "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-1.25.3.tgz", @@ -16707,16 +16527,6 @@ "semver": "^6.3.0" } }, - "postcss": { - "version": "7.0.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", - "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -17049,16 +16859,6 @@ "pako": "~1.0.5" } }, - "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", - "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" - } - }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -17647,7 +17447,6 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", "requires": { - "browserslist": "^4.6.2", "core-js-pure": "3.1.4", "semver": "^6.1.1" }, @@ -17787,17 +17586,13 @@ "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==" }, "css-has-pseudo": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", "requires": { - "postcss": "^7.0.6", "postcss-selector-parser": "^5.0.0-rc.4" }, "dependencies": { @@ -17828,7 +17623,6 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.23", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", "postcss-modules-scope": "^2.1.1", @@ -17837,16 +17631,6 @@ "schema-utils": "^2.6.0" }, "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, "schema-utils": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", @@ -17866,10 +17650,7 @@ "css-prefers-color-scheme": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==" }, "cssdb": { "version": "4.4.0", @@ -18088,15 +17869,6 @@ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, "dns-txt": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", @@ -19516,10 +19288,7 @@ "icss-utils": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "requires": { - "postcss": "^7.0.14" - } + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==" }, "ieee754": { "version": "1.1.13", @@ -20540,7 +20309,6 @@ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "requires": { - "dns-packet": "^1.3.1", "thunky": "^1.0.2" } }, @@ -21400,29 +21168,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, - "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, "postcss-attribute-case-insensitive": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0" }, "dependencies": { @@ -21448,7 +21198,6 @@ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -21458,7 +21207,6 @@ "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", "postcss-values-parser": "^2.0.0" } }, @@ -21467,7 +21215,6 @@ "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", "requires": { - "postcss": "^7.0.14", "postcss-values-parser": "^2.0.1" } }, @@ -21477,7 +21224,6 @@ "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -21486,24 +21232,19 @@ "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-custom-media": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "requires": { - "postcss": "^7.0.14" - } + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==" }, "postcss-custom-properties": { "version": "8.0.11", "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", "requires": { - "postcss": "^7.0.17", "postcss-values-parser": "^2.0.1" } }, @@ -21512,7 +21253,6 @@ "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -21538,7 +21278,6 @@ "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -21564,7 +21303,6 @@ "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", "requires": { - "postcss": "^7.0.5", "postcss-values-parser": "^2.0.0" } }, @@ -21573,48 +21311,34 @@ "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, "postcss-focus-visible": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==" }, "postcss-focus-within": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==" }, "postcss-font-variant": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", - "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==" }, "postcss-gap-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==" }, "postcss-image-set-function": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -21623,8 +21347,7 @@ "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", "requires": { - "lodash.template": "^4.5.0", - "postcss": "^7.0.2" + "lodash.template": "^4.5.0" } }, "postcss-lab-function": { @@ -21633,7 +21356,6 @@ "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -21652,7 +21374,6 @@ "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "requires": { "loader-utils": "^1.1.0", - "postcss": "^7.0.0", "postcss-load-config": "^2.0.0", "schema-utils": "^1.0.0" } @@ -21660,26 +21381,17 @@ "postcss-logical": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==" }, "postcss-media-minmax": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==" }, "postcss-modules-extract-imports": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "requires": { - "postcss": "^7.0.5" - } + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==" }, "postcss-modules-local-by-default": { "version": "3.0.2", @@ -21687,7 +21399,6 @@ "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", "requires": { "icss-utils": "^4.1.1", - "postcss": "^7.0.16", "postcss-selector-parser": "^6.0.2", "postcss-value-parser": "^4.0.0" } @@ -21697,7 +21408,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz", "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==", "requires": { - "postcss": "^7.0.6", "postcss-selector-parser": "^6.0.0" } }, @@ -21706,40 +21416,29 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" + "icss-utils": "^4.0.0" } }, "postcss-nesting": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==" }, "postcss-overflow-shorthand": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==" }, "postcss-page-break": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==" }, "postcss-place": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", "requires": { - "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" } }, @@ -21749,13 +21448,11 @@ "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", "requires": { "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", "caniuse-lite": "^1.0.30000981", "css-blank-pseudo": "^0.1.4", "css-has-pseudo": "^0.10.0", "css-prefers-color-scheme": "^3.1.1", "cssdb": "^4.4.0", - "postcss": "^7.0.17", "postcss-attribute-case-insensitive": "^4.0.1", "postcss-color-functional-notation": "^2.0.1", "postcss-color-gray": "^5.0.0", @@ -21792,7 +21489,6 @@ "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", "requires": { - "postcss": "^7.0.2", "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { @@ -21816,18 +21512,14 @@ "postcss-replace-overflow-wrap": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "requires": { - "postcss": "^7.0.2" - } + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==" }, "postcss-selector-matches": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "balanced-match": "^1.0.0" } }, "postcss-selector-not": { @@ -21835,8 +21527,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "balanced-match": "^1.0.0" } }, "postcss-selector-parser": { @@ -24308,7 +23999,6 @@ "url": "^0.11.0", "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", - "ws": "^6.2.1", "yargs": "^13.3.2" }, "dependencies": { @@ -24453,14 +24143,6 @@ "signal-exit": "^3.0.2" } }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", @@ -24590,7 +24272,6 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "requires": { - "browserslist": "^4.12.0", "invariant": "^2.2.4", "semver": "^5.5.0" } @@ -25017,7 +24698,6 @@ "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "requires": { "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" @@ -25826,7 +25506,6 @@ "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", "@babel/types": "^7.11.0", - "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", @@ -29174,17 +28853,6 @@ "pako": "~1.0.5" } }, - "browserslist": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", - "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", - "requires": { - "caniuse-lite": "^1.0.30001093", - "electron-to-chromium": "^1.3.488", - "escalade": "^3.0.1", - "node-releases": "^1.1.58" - } - }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -29620,7 +29288,6 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", "requires": { - "browserslist": "^4.8.5", "semver": "7.0.0" }, "dependencies": { @@ -30438,7 +30105,6 @@ "@babel/plugin-transform-typeof-symbol": "^7.2.0", "@babel/plugin-transform-unicode-regex": "^7.6.2", "@babel/types": "^7.6.3", - "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", @@ -31198,16 +30864,6 @@ "pako": "~1.0.5" } }, - "browserslist": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.1.tgz", - "integrity": "sha512-QtULFqKIAtiyNx7NhZ/p4rB8m3xDozVo/pi5VgTlADLF2tNigz/QH+v0m5qhn7XfHT7u+607NcCNOnC0HZAlMg==", - "requires": { - "caniuse-lite": "^1.0.30000999", - "electron-to-chromium": "^1.3.284", - "node-releases": "^1.1.36" - } - }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -31714,7 +31370,6 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.3.2.tgz", "integrity": "sha512-gfiK4QnNXhnnHVOIZst2XHdFfdMTPxtR0EGs0TdILMlGIft+087oH6/Sw2xTTIjpWXC9vEwsJA8VG3XTGcmO5g==", "requires": { - "browserslist": "^4.7.0", "semver": "^6.3.0" }, "dependencies": { @@ -43549,7 +43204,6 @@ "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", "whatwg-url": "^8.0.0", - "ws": "^7.2.3", "xml-name-validator": "^3.0.0" }, "dependencies": { @@ -46900,11 +46554,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" - }, "xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -47213,9 +46862,9 @@ "dev": true }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "dev": true, "requires": { "ip": "^1.1.0", @@ -47384,12 +47033,6 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "electron-to-chromium": { - "version": "1.3.296", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.296.tgz", - "integrity": "sha512-s5hv+TSJSVRsxH190De66YHb50pBGTweT9XGWYu/LMR20KX6TsjFzObo36CjVAzM+PUeeKSBRtm/mISlCzeojQ==", - "dev": true - }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -47571,6 +47214,12 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -50016,8 +49665,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", @@ -51838,23 +51486,6 @@ } } }, - "node-releases": { - "version": "1.1.39", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.39.tgz", - "integrity": "sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "node-sass": { "version": "4.14.1", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", @@ -52789,9 +52420,9 @@ "dev": true }, "postcss": { - "version": "7.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", - "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -53874,9 +53505,9 @@ } }, "rc-menu": { - "version": "8.10.6", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-8.10.6.tgz", - "integrity": "sha512-RVkd8XChwSmVOdNULbqLNnABthRZWnhqct1Q74onEXTClsXvsLADMhlIJtw/umglVSECM+14TJdIli9rl2Bzlw==", + "version": "8.10.8", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-8.10.8.tgz", + "integrity": "sha512-0gnSR0nmR/60NnK+72EGd+QheHyPSQ3wYg1TwX1zl0JJ9Gm0purFFykCXVv/G0Jynpt0QySPAos+bpHpjMZdoQ==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -53889,17 +53520,17 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.5.tgz", + "integrity": "sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "rc-util": { - "version": "5.9.4", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.9.4.tgz", - "integrity": "sha512-pzFmYZsKLJ1p+Uv4NqA4aNBaFh8/hOQxOOxA5G4TiyPboa0o/PjminxUCKvoSwVJVW5YgleSM2XPCTpTV6DCsQ==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.13.1.tgz", + "integrity": "sha512-Dws2tjXBBihfjVQFlG5JzZ/5O3Wutctm0W94Wb1+M7GD2roWJPrQdSa4AkWm2pn0Ms32zoVPPkWodFeAYZPLfA==", "requires": { "@babel/runtime": "^7.12.5", "react-is": "^16.12.0", @@ -58050,9 +57681,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dev": true, "requires": { "async-limiter": "~1.0.0" diff --git a/cvat-ui/package.json b/cvat-ui/package.json index ceda7cb0..b3bb6360 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,11 +1,11 @@ { "name": "cvat-ui", - "version": "1.19.1", + "version": "1.21.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { "build": "webpack --config ./webpack.config.js", - "start": "REACT_APP_API_URL=http://localhost:7000 webpack-dev-server --config ./webpack.config.js --mode=development", + "start": "webpack-dev-server --env.API_URL=http://localhost:7000 --config ./webpack.config.js --mode=development", "type-check": "tsc --noEmit", "type-check:watch": "npm run type-check -- --watch", "lint": "eslint './src/**/*.{ts,tsx}'", @@ -49,14 +49,14 @@ }, "dependencies": { "@ant-design/icons": "^4.6.2", - "@types/lodash": "^4.14.168", + "@types/lodash": "^4.14.170", "@types/platform": "^1.3.3", - "@types/react": "^16.14.5", + "@types/react": "^16.14.10", "@types/react-color": "^3.0.4", - "@types/react-dom": "^16.9.12", + "@types/react-dom": "^16.9.13", "@types/react-redux": "^7.1.16", "@types/react-resizable": "^1.7.2", - "@types/react-router": "^5.1.13", + "@types/react-router": "^5.1.15", "@types/react-router-dom": "^5.1.7", "@types/react-share": "^3.0.3", "@types/redux-logger": "^3.0.8", @@ -73,6 +73,7 @@ "mousetrap": "^1.6.5", "platform": "^1.3.6", "prop-types": "^15.7.2", + "rc-menu": "^8.10.8", "react": "^16.14.0", "react-awesome-query-builder": "^3.0.0", "react-color": "^2.19.3", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 898306a0..96bfb8d2 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -7,7 +7,7 @@ import { ActionCreator, AnyAction, Dispatch, Store, } from 'redux'; import { ThunkAction } from 'utils/redux'; -import { RectDrawingMethod } from 'cvat-canvas-wrapper'; +import { RectDrawingMethod, CuboidDrawingMethod, Canvas } from 'cvat-canvas-wrapper'; import getCore from 'cvat-core-wrapper'; import logger, { LogType } from 'cvat-logger'; import { getCVATStore } from 'cvat-store'; @@ -146,7 +146,6 @@ export enum AnnotationActionTypes { GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED', SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS', SPLIT_ANNOTATIONS_FAILED = 'SPLIT_ANNOTATIONS_FAILED', - UPDATE_TAB_CONTENT_HEIGHT = 'UPDATE_TAB_CONTENT_HEIGHT', COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR', COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS', @@ -196,6 +195,8 @@ export enum AnnotationActionTypes { GET_PREDICTIONS_SUCCESS = 'GET_PREDICTIONS_SUCCESS', HIDE_SHOW_CONTEXT_IMAGE = 'HIDE_SHOW_CONTEXT_IMAGE', GET_CONTEXT_IMAGE = 'GET_CONTEXT_IMAGE', + GET_CONTEXT_IMAGE_SUCCESS = 'GET_CONTEXT_IMAGE_SUCCESS', + GET_CONTEXT_IMAGE_FAILED = 'GET_CONTEXT_IMAGE_FAILED', } export function saveLogsAsync(): ThunkAction { @@ -574,15 +575,6 @@ export function activateObject(activatedStateID: number | null, activatedAttribu }; } -export function updateTabContentHeight(tabContentHeight: number): AnyAction { - return { - type: AnnotationActionTypes.UPDATE_TAB_CONTENT_HEIGHT, - payload: { - tabContentHeight, - }, - }; -} - export function collapseSidebar(): AnyAction { return { type: AnnotationActionTypes.COLLAPSE_SIDEBAR, @@ -697,7 +689,8 @@ export function getPredictionsAsync(): ThunkAction { }; } -export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): ThunkAction { +export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number, + forceUpdate?: boolean): ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; @@ -708,13 +701,14 @@ export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameSte throw Error(`Required frame ${toFrame} is out of the current job`); } - if (toFrame === frame) { + if (toFrame === frame && !forceUpdate) { dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: state.annotation.player.frame.number, data: state.annotation.player.frame.data, filename: state.annotation.player.frame.filename, + hasRelatedContext: state.annotation.player.frame.hasRelatedContext, delay: state.annotation.player.frame.delay, changeTime: state.annotation.player.frame.changeTime, states: state.annotation.annotations.states, @@ -726,7 +720,6 @@ export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameSte return; } - // Start async requests dispatch({ type: AnnotationActionTypes.CHANGE_FRAME, @@ -766,6 +759,7 @@ export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameSte number: toFrame, data, filename: data.filename, + hasRelatedContext: data.hasRelatedContext, states, minZ, maxZ, @@ -807,7 +801,6 @@ export function undoActionAsync(sessionInstance: any, frame: number): ThunkActio true, ); - dispatch(changeFrameAsync(undo[1])); await sessionInstance.actions.undo(); const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters); @@ -823,6 +816,11 @@ export function undoActionAsync(sessionInstance: any, frame: number): ThunkActio maxZ, }, }); + + const undoOnFrame = undo[1]; + if (frame !== undoOnFrame) { + dispatch(changeFrameAsync(undoOnFrame)); + } } catch (error) { dispatch({ type: AnnotationActionTypes.UNDO_ACTION_FAILED, @@ -851,7 +849,7 @@ export function redoActionAsync(sessionInstance: any, frame: number): ThunkActio }, true, ); - dispatch(changeFrameAsync(redo[1])); + await sessionInstance.actions.redo(); const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters); @@ -867,6 +865,11 @@ export function redoActionAsync(sessionInstance: any, frame: number): ThunkActio maxZ, }, }); + + const redoOnFrame = redo[1]; + if (frame !== redoOnFrame) { + dispatch(changeFrameAsync(redoOnFrame)); + } } catch (error) { dispatch({ type: AnnotationActionTypes.REDO_ACTION_FAILED, @@ -1031,6 +1034,7 @@ export function getJobAsync(tid: number, jid: number, initialFrame: number, init states, frameNumber, frameFilename: frameData.filename, + frameHasRelatedContext: frameData.hasRelatedContext, frameData, colors, filters, @@ -1139,6 +1143,7 @@ export function rememberObject(createParams: { activeShapeType?: ShapeType; activeNumOfPoints?: number; activeRectDrawingMethod?: RectDrawingMethod; + activeCuboidDrawingMethod?: CuboidDrawingMethod; }): AnyAction { return { type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT, @@ -1430,8 +1435,9 @@ export function pasteShapeAsync(): ThunkAction { activeControl, }, }); - - canvasInstance.cancel(); + if (canvasInstance instanceof Canvas) { + canvasInstance.cancel(); + } if (initialState.objectType === ObjectType.TAG) { const objectState = new cvat.classes.ObjectState({ objectType: ObjectType.TAG, @@ -1484,11 +1490,12 @@ export function repeatDrawShapeAsync(): ThunkAction { activeShapeType, activeNumOfPoints, activeRectDrawingMethod, + activeCuboidDrawingMethod, }, } = getStore().getState().annotation; let activeControl = ActiveControl.CURSOR; - if (activeInteractor) { + if (activeInteractor && canvasInstance instanceof Canvas) { if (activeInteractor.type === 'tracker') { canvasInstance.interact({ enabled: true, @@ -1506,7 +1513,6 @@ export function repeatDrawShapeAsync(): ThunkAction { return; } - if (activeShapeType === ShapeType.RECTANGLE) { activeControl = ActiveControl.DRAW_RECTANGLE; } else if (activeShapeType === ShapeType.POINTS) { @@ -1518,7 +1524,6 @@ export function repeatDrawShapeAsync(): ThunkAction { } else if (activeShapeType === ShapeType.CUBOID) { activeControl = ActiveControl.DRAW_CUBOID; } - dispatch({ type: AnnotationActionTypes.REPEAT_DRAW_SHAPE, payload: { @@ -1526,7 +1531,9 @@ export function repeatDrawShapeAsync(): ThunkAction { }, }); - canvasInstance.cancel(); + if (canvasInstance instanceof Canvas) { + canvasInstance.cancel(); + } if (activeObjectType === ObjectType.TAG) { const objectState = new cvat.classes.ObjectState({ objectType: ObjectType.TAG, @@ -1538,6 +1545,7 @@ export function repeatDrawShapeAsync(): ThunkAction { canvasInstance.draw({ enabled: true, rectDrawingMethod: activeRectDrawingMethod, + cuboidDrawingMethod: activeCuboidDrawingMethod, numberOfPoints: activeNumOfPoints, shapeType: activeShapeType, crosshair: [ShapeType.RECTANGLE, ShapeType.CUBOID].includes(activeShapeType), @@ -1575,8 +1583,9 @@ export function redrawShapeAsync(): ThunkAction { activeControl, }, }); - - canvasInstance.cancel(); + if (canvasInstance instanceof Canvas) { + canvasInstance.cancel(); + } canvasInstance.draw({ enabled: true, redraw: activatedStateID, @@ -1632,35 +1641,27 @@ export function hideShowContextImage(hidden: boolean): AnyAction { }; } -export function getContextImage(): ThunkAction { +export function getContextImageAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; - const { frame, contextImage } = state.annotation.player; + const { number: frameNumber } = state.annotation.player.frame; try { - const context = await job.frames.contextImage(job.task.id, frame.number); - const loaded = true; - const contextImageHide = contextImage.hidden; dispatch({ type: AnnotationActionTypes.GET_CONTEXT_IMAGE, - payload: { - context, - loaded, - contextImageHide, - }, + payload: {}, + }); + + const contextImageData = await job.frames.contextImage(job.task.id, frameNumber); + dispatch({ + type: AnnotationActionTypes.GET_CONTEXT_IMAGE_SUCCESS, + payload: { contextImageData }, }); } catch (error) { - const context = ''; - const loaded = true; - const contextImageHide = contextImage.hidden; dispatch({ - type: AnnotationActionTypes.GET_CONTEXT_IMAGE, - payload: { - context, - loaded, - contextImageHide, - }, + type: AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED, + payload: { error }, }); } }; diff --git a/cvat-ui/src/actions/projects-actions.ts b/cvat-ui/src/actions/projects-actions.ts index 5408a42f..7a8c8b0c 100644 --- a/cvat-ui/src/actions/projects-actions.ts +++ b/cvat-ui/src/actions/projects-actions.ts @@ -5,9 +5,8 @@ import { Dispatch, ActionCreator } from 'redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import { ProjectsQuery, CombinedState } from 'reducers/interfaces'; +import { ProjectsQuery } from 'reducers/interfaces'; import { getTasksSuccess, updateTaskSuccess } from 'actions/tasks-actions'; -import { getCVATStore } from 'cvat-store'; import getCore from 'cvat-core-wrapper'; const cvat = getCore(); @@ -31,8 +30,8 @@ export enum ProjectsActionTypes { // prettier-ignore const projectActions = { getProjects: () => createAction(ProjectsActionTypes.GET_PROJECTS), - getProjectsSuccess: (array: any[], count: number) => ( - createAction(ProjectsActionTypes.GET_PROJECTS_SUCCESS, { array, count }) + getProjectsSuccess: (array: any[], previews: string[], count: number) => ( + createAction(ProjectsActionTypes.GET_PROJECTS_SUCCESS, { array, previews, count }) ), getProjectsFailed: (error: any) => createAction(ProjectsActionTypes.GET_PROJECTS_FAILED, { error }), updateProjectsGettingQuery: (query: Partial) => ( @@ -60,7 +59,7 @@ const projectActions = { export type ProjectActions = ActionUnion; export function getProjectsAsync(query: Partial): ThunkAction { - return async (dispatch: ActionCreator): Promise => { + return async (dispatch: ActionCreator, getState): Promise => { dispatch(projectActions.getProjects()); dispatch(projectActions.updateProjectsGettingQuery(query)); @@ -69,6 +68,7 @@ export function getProjectsAsync(query: Partial): ThunkAction { page: 1, ...query, }; + for (const key in filteredQuery) { if (filteredQuery[key] === null || typeof filteredQuery[key] === 'undefined') { delete filteredQuery[key]; @@ -85,38 +85,38 @@ export function getProjectsAsync(query: Partial): ThunkAction { const array = Array.from(result); - const tasks: any[] = []; - const taskPreviewPromises: Promise[] = []; - - for (const project of array) { - taskPreviewPromises.push( - ...(project as any).tasks.map((task: any): string => { - tasks.push(task); - return (task as any).frames.preview().catch(() => ''); - }), - ); - } + // Appropriate tasks fetching proccess needs with retrieving only a single project + if (Object.keys(filteredQuery).includes('id')) { + const tasks: any[] = []; + const [project] = array; + const taskPreviewPromises: Promise[] = (project as any).tasks.map((task: any): string => { + tasks.push(task); + return (task as any).frames.preview().catch(() => ''); + }); - const taskPreviews = await Promise.all(taskPreviewPromises); - - dispatch(projectActions.getProjectsSuccess(array, result.count)); - - const store = getCVATStore(); - const state: CombinedState = store.getState(); - - if (!state.tasks.fetching) { - dispatch( - getTasksSuccess(tasks, taskPreviews, tasks.length, { - page: 1, - assignee: null, - id: null, - mode: null, - name: null, - owner: null, - search: null, - status: null, - }), - ); + const taskPreviews = await Promise.all(taskPreviewPromises); + + const state = getState(); + + dispatch(projectActions.getProjectsSuccess(array, taskPreviews, result.count)); + + if (!state.tasks.fetching) { + dispatch( + getTasksSuccess(tasks, taskPreviews, tasks.length, { + page: 1, + assignee: null, + id: null, + mode: null, + name: null, + owner: null, + search: null, + status: null, + }), + ); + } + } else { + const previewPromises = array.map((project): string => (project as any).preview().catch(() => '')); + dispatch(projectActions.getProjectsSuccess(array, await Promise.all(previewPromises), result.count)); } }; } diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index 6fca99c3..966743c4 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -26,6 +26,7 @@ export enum SettingsActionTypes { SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE', CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', + CHANGE_DEFAULT_APPROX_POLY_THRESHOLD = 'CHANGE_DEFAULT_APPROX_POLY_THRESHOLD', SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING', SWITCH_INTELLIGENT_POLYGON_CROP = 'SWITCH_INTELLIGENT_POLYGON_CROP', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', @@ -270,6 +271,15 @@ export function switchSettingsDialog(show?: boolean): AnyAction { }; } +export function changeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD, + payload: { + approxPolyAccuracy, + }, + }; +} + export function setSettings(settings: Partial): AnyAction { return { type: SettingsActionTypes.SET_SETTINGS, diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 3dcf75bc..c0fabf97 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -35,6 +35,13 @@ export enum TasksActionTypes { UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED', HIDE_EMPTY_TASKS = 'HIDE_EMPTY_TASKS', + EXPORT_TASK = 'EXPORT_TASK', + EXPORT_TASK_SUCCESS = 'EXPORT_TASK_SUCCESS', + EXPORT_TASK_FAILED = 'EXPORT_TASK_FAILED', + IMPORT_TASK = 'IMPORT_TASK', + IMPORT_TASK_SUCCESS = 'IMPORT_TASK_SUCCESS', + IMPORT_TASK_FAILED = 'IMPORT_TASK_FAILED', + SWITCH_MOVE_TASK_MODAL_VISIBLE = 'SWITCH_MOVE_TASK_MODAL_VISIBLE', } function getTasks(): AnyAction { @@ -213,6 +220,49 @@ export function loadAnnotationsAsync( }; } +function importTask(): AnyAction { + const action = { + type: TasksActionTypes.IMPORT_TASK, + payload: {}, + }; + + return action; +} + +function importTaskSuccess(task: any): AnyAction { + const action = { + type: TasksActionTypes.IMPORT_TASK_SUCCESS, + payload: { + task, + }, + }; + + return action; +} + +function importTaskFailed(error: any): AnyAction { + const action = { + type: TasksActionTypes.IMPORT_TASK_FAILED, + payload: { + error, + }, + }; + + return action; +} + +export function importTaskAsync(file: File): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(importTask()); + const taskInstance = await cvat.classes.Task.import(file); + dispatch(importTaskSuccess(taskInstance)); + } catch (error) { + dispatch(importTaskFailed(error)); + } + }; +} + function exportDataset(task: any, exporter: any): AnyAction { const action = { type: TasksActionTypes.EXPORT_DATASET, @@ -267,6 +317,56 @@ export function exportDatasetAsync(task: any, exporter: any): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(exportTask(taskInstance.id)); + + try { + const url = await taskInstance.export(); + const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; + downloadAnchor.href = url; + downloadAnchor.click(); + dispatch(exportTaskSuccess(taskInstance.id)); + } catch (error) { + dispatch(exportTaskFailed(taskInstance.id, error)); + } + }; +} + function deleteTask(taskID: number): AnyAction { const action = { type: TasksActionTypes.DELETE_TASK, @@ -519,3 +619,46 @@ export function hideEmptyTasks(hideEmpty: boolean): AnyAction { return action; } + +export function switchMoveTaskModalVisible(visible: boolean, taskId: number | null = null): AnyAction { + const action = { + type: TasksActionTypes.SWITCH_MOVE_TASK_MODAL_VISIBLE, + payload: { + taskId, + visible, + }, + }; + + return action; +} + +interface LabelMap { + label_id: number; + new_label_name: string | null; + clear_attributes: boolean; +} + +export function moveTaskToProjectAsync( + taskInstance: any, + projectId: any, + labelMap: LabelMap[], +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(updateTask()); + try { + // eslint-disable-next-line no-param-reassign + taskInstance.labels = labelMap.map((mapper) => { + const [label] = taskInstance.labels.filter((_label: any) => mapper.label_id === _label.id); + label.name = mapper.new_label_name; + return label; + }); + // eslint-disable-next-line no-param-reassign + taskInstance.projectId = projectId; + await taskInstance.save(); + const [task] = await cvat.tasks.get({ id: taskInstance.id }); + dispatch(updateTaskSuccess(task, task.id)); + } catch (error) { + dispatch(updateTaskFailed(error, taskInstance)); + } + }; +} diff --git a/cvat-ui/src/assets/fullscreen-icon.svg b/cvat-ui/src/assets/fullscreen-icon.svg index a5290f04..e620d72c 100644 --- a/cvat-ui/src/assets/fullscreen-icon.svg +++ b/cvat-ui/src/assets/fullscreen-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/assets/info-icon.svg b/cvat-ui/src/assets/info-icon.svg index f2f7b1dd..96ca1120 100644 --- a/cvat-ui/src/assets/info-icon.svg +++ b/cvat-ui/src/assets/info-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/assets/main-menu-icon.svg b/cvat-ui/src/assets/main-menu-icon.svg index e712d571..4ad687e6 100644 --- a/cvat-ui/src/assets/main-menu-icon.svg +++ b/cvat-ui/src/assets/main-menu-icon.svg @@ -1 +1 @@ - + diff --git a/cvat-ui/src/assets/object-filter-icon.svg b/cvat-ui/src/assets/object-filter-icon.svg index b62e9623..7ab2b729 100644 --- a/cvat-ui/src/assets/object-filter-icon.svg +++ b/cvat-ui/src/assets/object-filter-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/assets/redo-icon.svg b/cvat-ui/src/assets/redo-icon.svg index 61cf325a..a0cdb866 100644 --- a/cvat-ui/src/assets/redo-icon.svg +++ b/cvat-ui/src/assets/redo-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/assets/save-icon.svg b/cvat-ui/src/assets/save-icon.svg index a87e524e..9101c59e 100644 --- a/cvat-ui/src/assets/save-icon.svg +++ b/cvat-ui/src/assets/save-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/assets/undo-icon.svg b/cvat-ui/src/assets/undo-icon.svg index d193a314..7575a69c 100644 --- a/cvat-ui/src/assets/undo-icon.svg +++ b/cvat-ui/src/assets/undo-icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 9ef0e0ac..4097dbe0 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -13,6 +13,7 @@ $layout-lg-grid-color: rgba(0, 0, 0, 0.15); $header-color: #d8d8d8; $text-color: #303030; +$text-color-secondary: rgba(0, 0, 0, 0.45); $hover-menu-color: rgba(24, 144, 255, 0.05); $completed-progress-color: #61c200; $inprogress-progress-color: #1890ff; diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index 52e0f05b..aa4d2acf 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -6,6 +6,7 @@ import './styles.scss'; import React from 'react'; import Menu from 'antd/lib/menu'; import Modal from 'antd/lib/modal'; +import { LoadingOutlined } from '@ant-design/icons'; // eslint-disable-next-line import/no-extraneous-dependencies import { MenuInfo } from 'rc-menu/lib/interface'; import DumpSubmenu from './dump-submenu'; @@ -25,6 +26,7 @@ interface Props { inferenceIsActive: boolean; taskDimension: DimensionType; onClickMenu: (params: MenuInfo, file?: File) => void; + exportIsActive: boolean; } export enum Actions { @@ -33,7 +35,9 @@ export enum Actions { EXPORT_TASK_DATASET = 'export_task_dataset', DELETE_TASK = 'delete_task', RUN_AUTO_ANNOTATION = 'run_auto_annotation', + MOVE_TASK_TO_PROJECT = 'move_task_to_project', OPEN_BUG_TRACKER = 'open_bug_tracker', + EXPORT_TASK = 'export_task', } export default function ActionsMenuComponent(props: Props): JSX.Element { @@ -49,6 +53,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { exportActivities, loadActivity, taskDimension, + exportIsActive, } = props; let latestParams: MenuInfo | null = null; @@ -127,7 +132,12 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { Automatic annotation + + {exportIsActive && } + Export Task +
    + Move to project Delete ); diff --git a/cvat-ui/src/components/actions-menu/load-submenu.tsx b/cvat-ui/src/components/actions-menu/load-submenu.tsx index 347ddbfa..6f72a7c1 100644 --- a/cvat-ui/src/components/actions-menu/load-submenu.tsx +++ b/cvat-ui/src/components/actions-menu/load-submenu.tsx @@ -47,7 +47,12 @@ export default function LoadSubmenu(props: Props): JSX.Element { return false; }} > -
    - + - + - + + + + + ); + } + private renderContent(): JSX.Element { const { libraryInitialized, initializationProgress, initializationError } = this.state; @@ -331,7 +423,9 @@ class OpenCVControlComponent extends React.PureComponent {this.renderDrawingContent()} - + + {this.renderImageContent()} + ) : ( <> @@ -384,6 +478,7 @@ class OpenCVControlComponent extends React.PureComponent ) : ( - - - + <> + { + if (libraryInitialized !== openCVWrapper.isInitialized) { + this.setState({ + libraryInitialized: openCVWrapper.isInitialized, + }); + } + }} + > + + + {isActivated ? ( + { + this.setState({ approxPolyAccuracy: value }); + }} + /> + ) : null} + ); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index f19971f1..9f4ff760 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -13,6 +13,7 @@ import Text from 'antd/lib/typography/Text'; import Tabs from 'antd/lib/tabs'; import { Row, Col } from 'antd/lib/grid'; import notification from 'antd/lib/notification'; +import message from 'antd/lib/message'; import Progress from 'antd/lib/progress'; import InputNumber from 'antd/lib/input-number'; @@ -20,6 +21,7 @@ import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; import range from 'utils/range'; import getCore from 'cvat-core-wrapper'; +import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper'; import { CombinedState, ActiveControl, Model, ObjectType, ShapeType, } from 'reducers/interfaces'; @@ -31,6 +33,9 @@ import { } from 'actions/annotation-actions'; import DetectorRunner from 'components/model-runner-modal/detector-runner'; import LabelSelector from 'components/label-selector/label-selector'; +import ApproximationAccuracy, { + thresholdFromAccuracy, +} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy'; import withVisibilityHandling from './handle-popover-visibility'; interface StateToProps { @@ -46,6 +51,7 @@ interface StateToProps { trackers: Model[]; curZOrder: number; aiToolsRef: MutableRefObject; + defaultApproxPolyAccuracy: number; } interface DispatchToProps { @@ -60,6 +66,7 @@ const CustomPopover = withVisibilityHandling(Popover, 'tools-control'); function mapStateToProps(state: CombinedState): StateToProps { const { annotation } = state; + const { settings } = state; const { number: frame } = annotation.player.frame; const { instance: jobInstance } = annotation.job; const { instance: canvasInstance, activeControl } = annotation.canvas; @@ -79,32 +86,35 @@ function mapStateToProps(state: CombinedState): StateToProps { frame, curZOrder: annotation.annotations.zLayer.cur, aiToolsRef: annotation.aiToolsRef, + defaultApproxPolyAccuracy: settings.workspace.defaultApproxPolyAccuracy, }; } const mapDispatchToProps = { onInteractionStart: interactWithCanvas, updateAnnotations: updateAnnotationsAsync, - fetchAnnotations: fetchAnnotationsAsync, createAnnotations: createAnnotationsAsync, + fetchAnnotations: fetchAnnotationsAsync, }; type Props = StateToProps & DispatchToProps; interface State { activeInteractor: Model | null; activeLabelID: number; - interactiveStateID: number | null; activeTracker: Model | null; trackingProgress: number | null; trackingFrames: number; fetching: boolean; + pointsRecieved: boolean; + approxPolyAccuracy: number; mode: 'detection' | 'interaction' | 'tracking'; } export class ToolsControlComponent extends React.PureComponent { private interactionIsAborted: boolean; - private interactionIsDone: boolean; + private latestResponseResult: number[][]; + private latestResult: number[][]; public constructor(props: Props) { super(props); @@ -112,13 +122,16 @@ export class ToolsControlComponent extends React.PureComponent { activeInteractor: props.interactors.length ? props.interactors[0] : null, activeTracker: props.trackers.length ? props.trackers[0] : null, activeLabelID: props.labels.length ? props.labels[0].id : null, - interactiveStateID: null, + approxPolyAccuracy: props.defaultApproxPolyAccuracy, trackingProgress: null, trackingFrames: 10, fetching: false, + pointsRecieved: false, mode: 'interaction', }; + this.latestResponseResult = []; + this.latestResult = []; this.interactionIsAborted = false; this.interactionIsDone = false; } @@ -130,16 +143,39 @@ export class ToolsControlComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.canceled', this.cancelListener); } - public componentDidUpdate(prevProps: Props): void { - const { isActivated } = this.props; + public componentDidUpdate(prevProps: Props, prevState: State): void { + const { isActivated, defaultApproxPolyAccuracy, canvasInstance } = this.props; + const { approxPolyAccuracy, activeInteractor } = this.state; + if (prevProps.isActivated && !isActivated) { window.removeEventListener('contextmenu', this.contextmenuDisabler); } else if (!prevProps.isActivated && isActivated) { // reset flags when start interaction/tracking + this.setState({ + approxPolyAccuracy: defaultApproxPolyAccuracy, + pointsRecieved: false, + }); + this.latestResult = []; + this.latestResponseResult = []; this.interactionIsDone = false; this.interactionIsAborted = false; window.addEventListener('contextmenu', this.contextmenuDisabler); } + + if (prevState.approxPolyAccuracy !== approxPolyAccuracy) { + if (isActivated && activeInteractor !== null && this.latestResponseResult.length) { + this.approximateResponsePoints(this.latestResponseResult).then((points: number[][]) => { + this.latestResult = points; + canvasInstance.interact({ + enabled: true, + intermediateShape: { + shapeType: ShapeType.POLYGON, + points: this.latestResult.flat(), + }, + }); + }); + } + } } public componentWillUnmount(): void { @@ -149,12 +185,6 @@ export class ToolsControlComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.canceled', this.cancelListener); } - private getInteractiveState(): any | null { - const { states } = this.props; - const { interactiveStateID } = this.state; - return states.filter((_state: any): boolean => _state.clientID === interactiveStateID)[0] || null; - } - private contextmenuDisabler = (e: MouseEvent): void => { if ( e.target && @@ -166,10 +196,9 @@ export class ToolsControlComponent extends React.PureComponent { }; private cancelListener = async (): Promise => { - const { - isActivated, jobInstance, frame, fetchAnnotations, - } = this.props; - const { interactiveStateID, fetching } = this.state; + const { isActivated } = this.props; + const { fetching } = this.state; + this.latestResult = []; if (isActivated) { if (fetching && !this.interactionIsDone) { @@ -177,15 +206,6 @@ export class ToolsControlComponent extends React.PureComponent { this.setState({ fetching: false }); this.interactionIsAborted = true; } - - if (interactiveStateID !== null) { - const state = this.getInteractiveState(); - this.setState({ interactiveStateID: null }); - await state.delete(frame); - fetchAnnotations(); - } - - await jobInstance.actions.freeze(false); } }; @@ -197,103 +217,69 @@ export class ToolsControlComponent extends React.PureComponent { jobInstance, isActivated, activeLabelID, - fetchAnnotations, - updateAnnotations, + canvasInstance, + createAnnotations, } = this.props; - const { activeInteractor, interactiveStateID, fetching } = this.state; + const { activeInteractor, fetching } = this.state; if (!isActivated) { return; } try { - if (fetching) { - this.interactionIsDone = (e as CustomEvent).detail.isDone; - return; - } - + this.interactionIsDone = (e as CustomEvent).detail.isDone; const interactor = activeInteractor as Model; - let result = []; if ((e as CustomEvent).detail.shapesUpdated) { this.setState({ fetching: true }); try { - result = await core.lambda.call(jobInstance.task, interactor, { + this.latestResponseResult = await core.lambda.call(jobInstance.task, interactor, { frame, pos_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 0), neg_points: convertShapesForInteractor((e as CustomEvent).detail.shapes, 2), }); + this.latestResult = this.latestResponseResult; + if (this.interactionIsAborted) { // while the server request // user has cancelled interaction (for example pressed ESC) + // need to clean variables that have been just set + this.latestResult = []; + this.latestResponseResult = []; return; } + + this.latestResult = await this.approximateResponsePoints(this.latestResponseResult); } finally { - this.setState({ fetching: false }); + this.setState({ fetching: false, pointsRecieved: !!this.latestResult.length }); } } - if (this.interactionIsDone) { - // while the server request, user has done interaction (for example pressed N) + if (!this.latestResult.length) { + return; + } + + if (this.interactionIsDone && !fetching) { const object = new core.classes.ObjectState({ frame, objectType: ObjectType.SHAPE, label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null, shapeType: ShapeType.POLYGON, - points: result.flat(), + points: this.latestResult.flat(), occluded: false, zOrder: curZOrder, }); - await jobInstance.annotations.put([object]); - fetchAnnotations(); + createAnnotations(jobInstance, frame, [object]); } else { - // no shape yet, then create it and save to collection - if (interactiveStateID === null) { - // freeze history for interaction time - // (points updating shouldn't cause adding new actions to history) - await jobInstance.actions.freeze(true); - const object = new core.classes.ObjectState({ - frame, - objectType: ObjectType.SHAPE, - label: labels.length ? labels.filter((label: any) => label.id === activeLabelID)[0] : null, + canvasInstance.interact({ + enabled: true, + intermediateShape: { shapeType: ShapeType.POLYGON, - points: result.flat(), - occluded: false, - zOrder: curZOrder, - }); - // need a clientID of a created object to interact with it further - // so, we do not use createAnnotationAction - const [clientID] = await jobInstance.annotations.put([object]); - - // update annotations on a canvas - fetchAnnotations(); - this.setState({ interactiveStateID: clientID }); - return; - } - - const state = this.getInteractiveState(); - if ((e as CustomEvent).detail.isDone) { - const finalObject = new core.classes.ObjectState({ - frame: state.frame, - objectType: state.objectType, - label: state.label, - shapeType: state.shapeType, - points: result.length ? result.flat() : state.points, - occluded: state.occluded, - zOrder: state.zOrder, - }); - this.setState({ interactiveStateID: null }); - await state.delete(frame); - await jobInstance.actions.freeze(false); - await jobInstance.annotations.put([finalObject]); - fetchAnnotations(); - } else { - state.points = result.flat(); - updateAnnotations([state]); - fetchAnnotations(); - } + points: this.latestResult.flat(), + }, + }); } } catch (err) { notification.error({ @@ -315,7 +301,8 @@ export class ToolsControlComponent extends React.PureComponent { const { activeLabelID } = this.state; const [label] = jobInstance.task.labels.filter((_label: any): boolean => _label.id === activeLabelID); - if (!(e as CustomEvent).detail.isDone) { + const { isDone, shapesUpdated } = (e as CustomEvent).detail; + if (!isDone || !shapesUpdated) { return; } @@ -376,8 +363,27 @@ export class ToolsControlComponent extends React.PureComponent { }); }; + private async approximateResponsePoints(points: number[][]): Promise { + const { approxPolyAccuracy } = this.state; + if (points.length > 3) { + if (!openCVWrapper.isInitialized) { + const hide = message.loading('OpenCV.js initialization..'); + try { + await openCVWrapper.initialize(() => {}); + } finally { + hide(); + } + } + + const threshold = thresholdFromAccuracy(approxPolyAccuracy); + return openCVWrapper.contours.approxPoly(points, threshold); + } + + return points; + } + public async trackState(state: any): Promise { - const { jobInstance, frame } = this.props; + const { jobInstance, frame, fetchAnnotations } = this.props; const { activeTracker, trackingFrames } = this.state; const { clientID, points } = state; @@ -429,6 +435,7 @@ export class ToolsControlComponent extends React.PureComponent { } } finally { this.setState({ trackingProgress: null, fetching: false }); + fetchAnnotations(); } } @@ -633,7 +640,7 @@ export class ToolsControlComponent extends React.PureComponent { private renderDetectorBlock(): JSX.Element { const { - jobInstance, detectors, curZOrder, frame, fetchAnnotations, + jobInstance, detectors, curZOrder, frame, createAnnotations, } = this.props; if (!detectors.length) { @@ -672,8 +679,7 @@ export class ToolsControlComponent extends React.PureComponent { }), ); - await jobInstance.annotations.put(states); - fetchAnnotations(); + createAnnotations(jobInstance, frame, states); } catch (error) { notification.error({ description: error.toString(), @@ -718,7 +724,9 @@ export class ToolsControlComponent extends React.PureComponent { const { interactors, detectors, trackers, isActivated, canvasInstance, labels, } = this.props; - const { fetching, trackingProgress } = this.state; + const { + fetching, trackingProgress, approxPolyAccuracy, activeInteractor, pointsRecieved, + } = this.state; if (![...interactors, ...detectors, ...trackers].length) return null; @@ -758,6 +766,14 @@ export class ToolsControlComponent extends React.PureComponent { )} + {isActivated && activeInteractor !== null && pointsRecieved ? ( + { + this.setState({ approxPolyAccuracy: value }); + }} + /> + ) : null} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx index ffd78023..1504cd63 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx @@ -17,7 +17,6 @@ import { CombinedState } from 'reducers/interfaces'; export default function LabelsListComponent(): JSX.Element { const dispatch = useDispatch(); - const tabContentHeight = useSelector((state: CombinedState) => state.annotation.tabContentHeight); const frame = useSelector((state: CombinedState): number => state.annotation.player.frame.number); const frameIssues = useSelector((state: CombinedState): any[] => state.review.frameIssues); const issues = useSelector((state: CombinedState): any[] => state.review.issues); @@ -50,7 +49,7 @@ export default function LabelsListComponent(): JSX.Element { }; return ( -
    + <>
    @@ -122,6 +121,6 @@ export default function LabelsListComponent(): JSX.Element { ), )}
    -
    + ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx index 99ee07ea..f6d3a132 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx @@ -14,7 +14,6 @@ import GlobalHotKeys from 'utils/mousetrap-react'; function LabelsListComponent(): JSX.Element { const dispatch = useDispatch(); const labels = useSelector((state: CombinedState) => state.annotation.job.labels); - const listHeight = useSelector((state: CombinedState) => state.annotation.tabContentHeight); const activatedStateID = useSelector((state: CombinedState) => state.annotation.annotations.activatedStateID); const states = useSelector((state: CombinedState) => state.annotation.annotations.states); const keyMap = useSelector((state: CombinedState) => state.shortcuts.keyMap); @@ -87,7 +86,7 @@ function LabelsListComponent(): JSX.Element { }; return ( -
    +
    {labelIDs.map( (labelID: number): JSX.Element => ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx index d33a5785..591919af 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx @@ -14,6 +14,7 @@ import LabelSelector from 'components/label-selector/label-selector'; import ItemMenu from './object-item-menu'; interface Props { + jobInstance: any; readonly: boolean; clientID: number; serverID: number | undefined; @@ -76,6 +77,7 @@ function ItemTopComponent(props: Props): JSX.Element { toForeground, resetCuboidPerspective, activateTracking, + jobInstance, } = props; const [menuVisible, setMenuVisible] = useState(false); @@ -124,6 +126,7 @@ function ItemTopComponent(props: Props): JSX.Element { onVisibleChange={changeMenuVisible} placement='bottomLeft' overlay={ItemMenu({ + jobInstance, readonly, serverID, locked, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx index d6783d98..8768007b 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx @@ -19,7 +19,9 @@ import { BackgroundIcon, ForegroundIcon, ResetPerspectiveIcon, ColorizeIcon, } from 'icons'; import CVATTooltip from 'components/common/cvat-tooltip'; -import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces'; +import { + ObjectType, ShapeType, ColorBy, DimensionType, +} from 'reducers/interfaces'; import ColorPicker from './color-picker'; interface Props { @@ -49,6 +51,7 @@ interface Props { resetCuboidPerspective(): void; changeColorPickerVisible(visible: boolean): void; activateTracking(): void; + jobInstance: any; } interface ItemProps { @@ -227,23 +230,25 @@ function RemoveItem(props: ItemProps): JSX.Element { export default function ItemMenu(props: Props): JSX.Element { const { - readonly, shapeType, objectType, colorBy, + readonly, shapeType, objectType, colorBy, jobInstance, } = props; + const is2D = jobInstance.task.dimension === DimensionType.DIM_2D; + return ( {!readonly && } {!readonly && } - {!readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && ( + {is2D && !readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && ( )} - {!readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( + {is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( )} - {!readonly && shapeType === ShapeType.CUBOID && } - {!readonly && objectType !== ObjectType.TAG && } - {!readonly && objectType !== ObjectType.TAG && } + {is2D && !readonly && shapeType === ShapeType.CUBOID && } + {is2D && objectType !== ObjectType.TAG && } + {is2D && !readonly && objectType !== ObjectType.TAG && } {[ColorBy.INSTANCE, ColorBy.GROUP].includes(colorBy) && } {!readonly && } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 578e239f..426ad7b6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -22,11 +22,10 @@ interface Props { attrValues: Record; color: string; colorBy: ColorBy; - labels: any[]; attributes: any[]; collapsed: boolean; - + jobInstance: any; activate(): void; copy(): void; propagate(): void; @@ -76,12 +75,10 @@ function ObjectItemComponent(props: Props): JSX.Element { labelID, color, colorBy, - attributes, labels, collapsed, normalizedKeyMap, - activate, copy, propagate, @@ -96,6 +93,7 @@ function ObjectItemComponent(props: Props): JSX.Element { collapse, resetCuboidPerspective, activateTracking, + jobInstance, } = props; const type = @@ -117,6 +115,7 @@ function ObjectItemComponent(props: Props): JSX.Element { style={{ backgroundColor: `${color}88` }} > + <> -
    + ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index 6d15c3ce..51af99a8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT import './styles.scss'; -import React, { Dispatch, useEffect, TransitionEvent } from 'react'; +import React, { Dispatch, TransitionEvent } from 'react'; import { AnyAction } from 'redux'; import { connect } from 'react-redux'; import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; @@ -12,13 +12,12 @@ import Tabs from 'antd/lib/tabs'; import Layout from 'antd/lib/layout'; import { Canvas } from 'cvat-canvas-wrapper'; -import { CombinedState } from 'reducers/interfaces'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { CombinedState, DimensionType } from 'reducers/interfaces'; import LabelsList from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; -import { - collapseSidebar as collapseSidebarAction, - updateTabContentHeight as updateTabContentHeightAction, -} from 'actions/annotation-actions'; -import AppearanceBlock, { computeHeight } from 'components/annotation-page/appearance-block'; +import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; +import { collapseSidebar as collapseSidebarAction } from 'actions/annotation-actions'; +import AppearanceBlock from 'components/annotation-page/appearance-block'; import IssuesListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/issues-list'; interface OwnProps { @@ -27,12 +26,12 @@ interface OwnProps { interface StateToProps { sidebarCollapsed: boolean; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; + jobInstance: any; } interface DispatchToProps { collapseSidebar(): void; - updateTabContentHeight(): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -40,12 +39,14 @@ function mapStateToProps(state: CombinedState): StateToProps { annotation: { sidebarCollapsed, canvas: { instance: canvasInstance }, + job: { instance: jobInstance }, }, } = state; return { sidebarCollapsed, canvasInstance, + jobInstance, }; } @@ -54,33 +55,18 @@ function mapDispatchToProps(dispatch: Dispatch): DispatchToProps { collapseSidebar(): void { dispatch(collapseSidebarAction()); }, - updateTabContentHeight(): void { - const height = computeHeight(); - dispatch(updateTabContentHeightAction(height)); - }, }; } function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { const { - sidebarCollapsed, canvasInstance, collapseSidebar, updateTabContentHeight, objectsList, + sidebarCollapsed, + canvasInstance, + collapseSidebar, + objectsList, + jobInstance, } = props; - useEffect(() => { - const alignTabHeight = (): void => { - if (!sidebarCollapsed) { - updateTabContentHeight(); - } - }; - - window.addEventListener('resize', alignTabHeight); - alignTabHeight(); - - return () => { - window.removeEventListener('resize', alignTabHeight); - }; - }, []); - const collapse = (): void => { const [collapser] = window.document.getElementsByClassName('cvat-objects-sidebar'); const listener = (event: TransitionEvent): void => { @@ -95,9 +81,15 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E (collapser as HTMLElement).addEventListener('transitionend', listener as any); } + adjustContextImagePosition(!sidebarCollapsed); collapseSidebar(); }; + let is2D = true; + if (jobInstance) { + is2D = jobInstance.task.dimension === DimensionType.DIM_2D; + } + return ( Labels} key='labels'> - Issues} key='issues'> - - + + {is2D ? + ( + Issues} key='issues'> + + + ) : null} + {!sidebarCollapsed && } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index c6631603..2bf1fa3c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -6,8 +6,6 @@ .cvat-objects-appearance-collapse.ant-collapse { width: 100%; - bottom: 0; - position: absolute; border-radius: 0; > .ant-collapse-item { @@ -29,7 +27,7 @@ > .ant-collapse-content { background: $background-color-2; border-bottom: none; - height: 230px; + padding-bottom: $grid-unit-size * 3; > .ant-collapse-content-box { padding: 10px; @@ -399,3 +397,29 @@ .cvat-objects-sidebar-label-item-disabled { opacity: 0.5; } + +.cvat-workspace-settings-approx-poly-threshold { + .ant-slider-track { + background: linear-gradient(90deg, #1890ff 0%, #61c200 100%); + } +} + +.cvat-approx-poly-threshold-wrapper { + @extend .cvat-workspace-settings-approx-poly-threshold; + + width: $grid-unit-size * 32; + position: absolute; + background: $background-color-2; + top: 8px; + left: 50%; + border-radius: 6px; + border: 1px solid $border-color-3; + z-index: 100; + padding: $grid-unit-size / 2 $grid-unit-size * 2 $grid-unit-size / 2 $grid-unit-size / 2; + + .ant-slider-mark { + position: static; + margin-top: 4px; + pointer-events: none; + } +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 81c60a53..6897e2f1 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -8,6 +8,55 @@ height: 100%; } +.cvat-context-image-wrapper { + height: auto; + width: $grid-unit-size * 32; + position: absolute; + top: $grid-unit-size; + right: $grid-unit-size; + z-index: 100; + background: black; + display: flex; + flex-direction: column; + justify-content: space-between; + user-select: none; + + > .cvat-context-image-wrapper-header { + height: $grid-unit-size * 4; + width: 100%; + z-index: 101; + background: rgba(0, 0, 0, 0.2); + position: absolute; + top: 0; + left: 0; + } + + > .ant-image { + margin: $grid-unit-size / 2; + } + + > span { + position: absolute; + font-size: 18px; + top: 7px; + right: 7px; + z-index: 102; + color: white; + + &:hover { + > svg { + transform: scale(1.2); + } + } + } +} + +.cvat-context-image { + width: 100%; + height: auto; + display: block; +} + .cvat-objects-sidebar-sider { top: 0; right: 0; @@ -24,6 +73,23 @@ overflow-y: auto; overflow-x: hidden; + > .ant-layout-sider-children { + display: flex; + flex-direction: column; + + > .cvat-objects-sidebar-tabs { + flex-grow: 10; + + > div { + display: flex; + + div[role='tabpanel'] { + height: 100%; + } + } + } + } + &.ant-layout-sider-collapsed { overflow: initial; } @@ -56,8 +122,7 @@ .cvat-issue-control, .cvat-tools-control, .cvat-extra-controls-control, -.cvat-opencv-control, -.cvat-context-image-control { +.cvat-opencv-control { border-radius: 3.3px; transform: scale(0.65); padding: 2px; @@ -76,7 +141,7 @@ } } -.cvat-extra-controls-control { +.cvat-antd-icon-control { > svg { width: 40px; height: 40px; @@ -163,6 +228,15 @@ } } +.cvat-opencv-image-tool { + @extend .cvat-opencv-drawing-tool; +} + +.cvat-opencv-image-tool-active { + color: #40a9ff; + border-color: #40a9ff; +} + .cvat-setup-tag-popover-content, .cvat-draw-shape-popover-content { padding: $grid-unit-size; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/context-image/context-image.tsx deleted file mode 100644 index cdb114d7..00000000 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/context-image/context-image.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useEffect } from 'react'; - -interface Props { - frame: number; - contextImageHide: boolean; - loaded: boolean; - data: string; - getContextImage(): void; -} - -export default function ContextImage(props: Props): JSX.Element { - const { - contextImageHide, loaded, data, getContextImage, - } = props; - - useEffect(() => { - if (!contextImageHide && !loaded) { - getContextImage(); - } - }, [contextImageHide, loaded]); - - if (!contextImageHide && data !== '') { - return ( -
    - Context not available -
    - ); - } - return null; -} diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx index 304f8ebb..acd88c61 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx @@ -6,42 +6,148 @@ import React from 'react'; import Layout from 'antd/lib/layout'; import { ActiveControl } from 'reducers/interfaces'; import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; -import CursorControl from './cursor-control'; -import MoveControl from './move-control'; -import DrawCuboidControl from './draw-cuboid-control'; -import PhotoContextControl from './photo-context'; +import MoveControl, { + Props as MoveControlProps, +} from 'components/annotation-page/standard-workspace/controls-side-bar/move-control'; +import CursorControl, { + Props as CursorControlProps, +} from 'components/annotation-page/standard-workspace/controls-side-bar/cursor-control'; +import DrawCuboidControl, { + Props as DrawCuboidControlProps, +} from 'components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control'; +import GroupControl, { + Props as GroupControlProps, +} from 'components/annotation-page/standard-workspace/controls-side-bar/group-control'; +import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; +import ControlVisibilityObserver from 'components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer'; interface Props { + keyMap: KeyMap; canvasInstance: Canvas; activeControl: ActiveControl; normalizedKeyMap: Record; - contextImageHide: boolean; - hideShowContextImage: (hidden: boolean) => void; + labels: any[]; + jobInstance: any; + repeatDrawShape(): void; + redrawShape(): void; + pasteShape(): void; + groupObjects(enabled: boolean): void; + resetGroup(): void; } +const ObservedCursorControl = ControlVisibilityObserver(CursorControl); +const ObservedMoveControl = ControlVisibilityObserver(MoveControl); +const ObservedDrawCuboidControl = ControlVisibilityObserver(DrawCuboidControl); +const ObservedGroupControl = ControlVisibilityObserver(GroupControl); + export default function ControlsSideBarComponent(props: Props): JSX.Element { const { - canvasInstance, activeControl, normalizedKeyMap, contextImageHide, hideShowContextImage, + canvasInstance, + pasteShape, + activeControl, + normalizedKeyMap, + keyMap, + labels, + redrawShape, + repeatDrawShape, + groupObjects, + resetGroup, + jobInstance, } = props; + const preventDefault = (event: KeyboardEvent | undefined): void => { + if (event) { + event.preventDefault(); + } + }; + + let subKeyMap: any = { + CANCEL: keyMap.CANCEL, + }; + + let handlers: any = { + CANCEL: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeControl !== ActiveControl.CURSOR) { + canvasInstance.cancel(); + } + }, + }; + + if (labels.length) { + handlers = { + ...handlers, + PASTE_SHAPE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + canvasInstance.cancel(); + pasteShape(); + }, + SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const drawing = [ActiveControl.DRAW_CUBOID].includes(activeControl); + + if (!drawing) { + canvasInstance.cancel(); + if (event && event.shiftKey) { + redrawShape(); + } else { + repeatDrawShape(); + } + } else { + canvasInstance.draw({ enabled: false }); + } + }, + SWITCH_GROUP_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const grouping = activeControl === ActiveControl.GROUP; + if (!grouping) { + canvasInstance.cancel(); + } + canvasInstance.group({ enabled: !grouping }); + groupObjects(!grouping); + }, + RESET_GROUP: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const grouping = activeControl === ActiveControl.GROUP; + if (!grouping) { + return; + } + resetGroup(); + canvasInstance.group({ enabled: false }); + groupObjects(false); + }, + }; + subKeyMap = { + ...subKeyMap, + PASTE_SHAPE: keyMap.PASTE_SHAPE, + SWITCH_DRAW_MODE: keyMap.SWITCH_DRAW_MODE, + SWITCH_GROUP_MODE: keyMap.SWITCH_GROUP_MODE, + RESET_GROUP: keyMap.RESET_GROUP, + }; + } + return ( - - + - - + - ); diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/cursor-control.tsx deleted file mode 100644 index eaddbd81..00000000 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/cursor-control.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Icon from '@ant-design/icons'; - -import { CursorIcon } from 'icons'; -import { ActiveControl } from 'reducers/interfaces'; -import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; -import CVATTooltip from 'components/common/cvat-tooltip'; - -interface Props { - canvasInstance: Canvas; - cursorShortkey: string; - activeControl: ActiveControl; -} - -function CursorControl(props: Props): JSX.Element { - const { activeControl, cursorShortkey, canvasInstance } = props; - - return ( - - canvasInstance.cancel() : undefined} - /> - - ); -} - -export default React.memo(CursorControl); diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/draw-cuboid-control.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/draw-cuboid-control.tsx deleted file mode 100644 index 216cbdc6..00000000 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/draw-cuboid-control.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Popover from 'antd/lib/popover'; -import Icon from '@ant-design/icons'; - -import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; -import { ShapeType } from 'reducers/interfaces'; - -import { CubeIcon } from 'icons'; - -import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; - -interface Props { - canvasInstance: Canvas; - isDrawing: boolean; -} - -function DrawPolygonControl(props: Props): JSX.Element { - const { canvasInstance, isDrawing } = props; - - const dynamcPopoverPros = isDrawing ? - { - overlayStyle: { - display: 'none', - }, - } : - {}; - - const dynamicIconProps = isDrawing ? - { - className: 'cvat-draw-cuboid-control cvat-active-canvas-control', - onClick: (): void => { - canvasInstance.draw({ enabled: false }); - }, - } : - { - className: 'cvat-draw-cuboid-control', - }; - - return ( - } - > - - - ); -} - -export default React.memo(DrawPolygonControl); diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/move-control.tsx deleted file mode 100644 index 4a41c6df..00000000 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/move-control.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Icon from '@ant-design/icons'; - -import { MoveIcon } from 'icons'; -import { ActiveControl } from 'reducers/interfaces'; -import CVATTooltip from 'components/common/cvat-tooltip'; -import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; - -interface Props { - canvasInstance: Canvas; - activeControl: ActiveControl; -} - -function MoveControl(props: Props): JSX.Element { - const { activeControl } = props; - - return ( - - - - ); -} - -export default React.memo(MoveControl); diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx index 3a7f1c6c..e5723149 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx @@ -6,11 +6,12 @@ import React from 'react'; import CameraIcon from '@ant-design/icons/CameraOutlined'; import CVATTooltip from 'components/common/cvat-tooltip'; -import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { Canvas } from 'cvat-canvas-wrapper'; import { ActiveControl } from 'reducers/interfaces'; interface Props { - canvasInstance: Canvas; + canvasInstance: Canvas3d | Canvas; activeControl: ActiveControl; hideShowContextImage: (hidden: boolean) => void; contextImageHide: boolean; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index 4e2cbdaa..b9394fd2 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -8,12 +8,21 @@ import Layout from 'antd/lib/layout'; import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper3D'; import ControlsSideBarContainer from 'containers/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar'; +import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; +import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; +import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; +import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; +import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; export default function StandardWorkspace3DComponent(): JSX.Element { return ( + } /> + + + ); } diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss index ad321301..d1c21adc 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss @@ -4,173 +4,12 @@ @import 'base.scss'; -.cvat-standard-workspace.ant-layout { - height: 100%; -} - -.cvat-contextImage { - width: $grid-unit-size * 32; - position: absolute; - background: $border-color-3; - top: $grid-unit-size; - right: $grid-unit-size; - z-index: 100; - border-radius: $grid-unit-size; - border: 1px solid $border-color-3; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: $grid-unit-size/2; -} - -.cvat-contextImage-show { - max-width: 100%; - max-height: 100%; -} - -.cvat-contextImage-loading { - text-align: center; -} - -.cvat-objects-sidebar-filter-input { - width: calc(100% - 35px); -} - -.cvat-objects-sidebar-sider { - top: 0; - right: 0; - left: auto; - background-color: $background-color-2; - border-left: 1px solid $border-color-1; - border-bottom: 1px solid $border-color-1; - border-radius: $grid-unit-size/2 0 0 $grid-unit-size/2; - z-index: 2; -} - -.cvat-objects-sidebar { - height: 100%; -} - -.cvat-rotate-canvas-controls-right > svg { - transform: scaleX(-1); -} - -.cvat-canvas-controls-sidebar { - background-color: $background-color-2; - border-right: 1px solid $border-color-1; - - > div { - > i { - border-radius: 3.3px; - transform: scale(0.65); - padding: $grid-unit-size/4; - - &:hover { - background: $header-color; - transform: scale(0.75); - } - - &:active { - transform: scale(0.65); - } - - > svg { - transform: scale(0.8); - } - } - } -} - -.cvat-active-canvas-control { - background: $header-color; - transform: scale(0.75); -} - -.cvat-rotate-canvas-controls-left, -.cvat-rotate-canvas-controls-right { - transform: scale(0.65); - border-radius: $grid-unit-size/2; - - &:hover { - transform: scale(0.75); - } - - &:active { - transform: scale(0.65); - } -} - -.cvat-rotate-canvas-controls > .ant-popover-content > .ant-popover-inner > div > .ant-popover-inner-content { - padding: 0; -} - -.cvat-draw-shape-popover, -.cvat-tools-control-popover { - > .ant-popover-content > .ant-popover-inner > div > .ant-popover-inner-content { - padding: 0; - } -} - -.cvat-tools-track-button, -.cvat-tools-interact-button { - width: 100%; - margin-top: $grid-unit-size; -} - -.cvat-draw-shape-popover-points-selector { - width: 100%; -} - -.cvat-tools-control-popover-content { - width: fit-content; - padding: $grid-unit-size; - border-radius: $grid-unit-size/2; - background: $background-color-2; -} - -.cvat-draw-shape-popover-content { - padding: $grid-unit-size; - border-radius: $grid-unit-size/2; - background: $background-color-2; - width: 270px; - - > div { - margin-top: $grid-unit-size/2; - } - - > div:nth-child(3) > div > div { - width: 100%; - } - - > div:last-child { - span { - width: 100%; - } - - button { - width: 100%; - - &:nth-child(1) { - border-radius: $grid-unit-size/2 0 0 $grid-unit-size/2; - } - - &:nth-child(2) { - border-radius: 0 $grid-unit-size/2 $grid-unit-size/2 0; - } - } - } -} - .cvat-canvas-container-overflow { overflow: hidden; width: 100%; height: 100%; } -.cvat-control-side-bar-icon-size { - font-size: $grid-unit-size * 5; -} - .cvat-canvas3d-perspective { height: 100%; width: 100%; @@ -303,3 +142,22 @@ height: 100%; cursor: ew-resize; } + +#cvat_canvas_loading_animation { + z-index: 1; + position: absolute; + width: 100%; + height: 100%; +} + +#cvat_canvas_loading_circle { + fill-opacity: 0; + stroke: #09c; + stroke-width: 3px; + stroke-dasharray: 50; + animation: loadingAnimation 1s linear infinite; +} + +.cvat_canvas_hidden { + display: none; +} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index dd53134f..969d63aa 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -16,7 +16,7 @@ .ant-layout-header.cvat-annotation-header { background-color: $background-color-2; border-bottom: 1px solid $border-color-1; - height: 54px; + height: 48px; padding: 0; > div:first-child { @@ -39,14 +39,14 @@ .ant-btn.cvat-annotation-header-button { padding: 0; - width: 54px; - height: 54px; - text-align: center; + width: 48px; + height: 48px; user-select: none; color: $text-color; display: flex; flex-direction: column; align-items: center; + justify-content: center; margin: 0 3px; > span:not([role='img']) { @@ -55,21 +55,15 @@ } > span[role='img'] { - transform: scale(0.8); - padding: 3px; + font-size: 24px; } &:hover > span[role='img'] { - transform: scale(0.85); + transform: scale(1.1); } &:active > span[role='img'] { - transform: scale(0.8); - } - - > * { - display: block; - line-height: 0; + transform: scale(1.05); } &.filters-armed { @@ -135,7 +129,7 @@ button.cvat-predictor-button { } .cvat-annotation-header-player-group > div { - height: 54px; + height: 48px; line-height: 0; flex-wrap: nowrap; } @@ -212,9 +206,11 @@ button.cvat-predictor-button { justify-content: flex-end; > div { - display: block; - height: 54px; - margin-right: 15px; + display: flex; + height: 48px; + align-items: center; + justify-content: center; + padding-right: 8px; } } diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss index ce04f704..a7c18550 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss @@ -8,7 +8,7 @@ height: 100%; } -.cvat-tag-annotation-sidebar { +.cvat-tag-annotation-sidebar:not(.ant-layout-sider-collapsed) { background: $background-color-2; padding: 5px; diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index 2292c48e..c55e49f1 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -21,7 +21,9 @@ import { rememberObject, } from 'actions/annotation-actions'; import { Canvas } from 'cvat-canvas-wrapper'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { CombinedState, ObjectType } from 'reducers/interfaces'; +import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import LabelSelector from 'components/label-selector/label-selector'; import getCore from 'cvat-core-wrapper'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; @@ -33,7 +35,7 @@ interface StateToProps { states: any[]; labels: any[]; jobInstance: any; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; frameNumber: number; keyMap: KeyMap; normalizedKeyMap: Record; @@ -203,7 +205,10 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen className={`cvat-objects-sidebar-sider ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger-left`} - onClick={() => setSidebarCollapsed(!sidebarCollapsed)} + onClick={() => { + adjustContextImagePosition(!sidebarCollapsed); + setSidebarCollapsed(!sidebarCollapsed); + }} > {sidebarCollapsed ? : } @@ -222,7 +227,10 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen className={`cvat-objects-sidebar-sider ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger-left`} - onClick={() => setSidebarCollapsed(!sidebarCollapsed)} + onClick={() => { + adjustContextImagePosition(!sidebarCollapsed); + setSidebarCollapsed(!sidebarCollapsed); + }} > {sidebarCollapsed ? : } diff --git a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx index 7cbf552f..258cf2b2 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx @@ -11,6 +11,7 @@ import { MenuInfo } from 'rc-menu/lib/interface'; import DumpSubmenu from 'components/actions-menu/dump-submenu'; import LoadSubmenu from 'components/actions-menu/load-submenu'; import ExportSubmenu from 'components/actions-menu/export-submenu'; +import { DimensionType } from '../../../reducers/interfaces'; interface Props { taskMode: string; @@ -158,6 +159,8 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { } } + const is2d = jobInstance.task.dimension === DimensionType.DIM_2D; + return ( {DumpSubmenu({ @@ -189,7 +192,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { Open the task - {jobStatus === 'annotation' && Request a review} + {jobStatus === 'annotation' && is2d && Request a review} {jobStatus === 'annotation' && Finish the job} {jobStatus === 'validation' && isReviewer && ( Submit the review diff --git a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx index dbbad65f..472cb500 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { Col } from 'antd/lib/grid'; -import Icon from '@ant-design/icons'; +import Icon, { CheckOutlined } from '@ant-design/icons'; import Modal from 'antd/lib/modal'; import Button from 'antd/lib/button'; import Timeline from 'antd/lib/timeline'; @@ -14,6 +14,8 @@ import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotati import { MainMenuIcon, SaveIcon, UndoIcon, RedoIcon, } from 'icons'; +import { ActiveControl } from 'reducers/interfaces'; +import CVATTooltip from 'components/common/cvat-tooltip'; interface Props { saving: boolean; @@ -23,9 +25,12 @@ interface Props { saveShortcut: string; undoShortcut: string; redoShortcut: string; + drawShortcut: string; + activeControl: ActiveControl; onSaveAnnotation(): void; onUndoClick(): void; onRedoClick(): void; + onFinishDraw(): void; } function LeftGroup(props: Props): JSX.Element { @@ -37,11 +42,22 @@ function LeftGroup(props: Props): JSX.Element { saveShortcut, undoShortcut, redoShortcut, + drawShortcut, + activeControl, onSaveAnnotation, onUndoClick, onRedoClick, + onFinishDraw, } = props; + const includesDoneButton = [ + ActiveControl.DRAW_POLYGON, + ActiveControl.DRAW_POLYLINE, + ActiveControl.DRAW_POINTS, + ActiveControl.AI_TOOLS, + ActiveControl.OPENCV_TOOLS, + ].includes(activeControl); + return ( }> @@ -50,44 +66,53 @@ function LeftGroup(props: Props): JSX.Element { Menu - - - + + + + + + + + + + {includesDoneButton ? ( + + + + ) : null} ); } diff --git a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx index c4727e6a..a3805442 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx @@ -25,11 +25,8 @@ interface Props { workspace: Workspace; predictor: PredictorState; isTrainingActive: boolean; - showStatistics(): void; - switchPredictor(predictorEnabled: boolean): void; - showFilters(): void; changeWorkspace(workspace: Workspace): void; diff --git a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx index 2ac1339d..10d7563b 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx @@ -11,6 +11,7 @@ import Spin from 'antd/lib/spin'; import Text from 'antd/lib/typography/Text'; import CVATTooltip from 'components/common/cvat-tooltip'; +import { DimensionType } from 'reducers/interfaces'; interface Props { collecting: boolean; @@ -24,13 +25,25 @@ interface Props { jobStatus: string; savingJobStatus: boolean; closeStatistics(): void; + jobInstance: any; } export default function StatisticsModalComponent(props: Props): JSX.Element { const { - collecting, data, visible, assignee, reviewer, startFrame, stopFrame, bugTracker, closeStatistics, + collecting, + data, + visible, + assignee, + reviewer, + startFrame, + stopFrame, + bugTracker, + closeStatistics, + jobInstance, } = props; + const is2D = jobInstance.task.dimension === DimensionType.DIM_2D; + const baseProps = { cancelButtonProps: { style: { display: 'none' } }, okButtonProps: { style: { width: 100 } }, @@ -77,7 +90,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { }); const makeShapesTracksTitle = (title: string): JSX.Element => ( - + {title} @@ -138,6 +151,24 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { }, ]; + const columns3D = [ + { + title: Label , + dataIndex: 'label', + key: 'label', + }, + { + title: makeShapesTracksTitle('Cuboids'), + dataIndex: 'cuboid', + key: 'cuboid', + }, + { + title: Total , + dataIndex: 'total', + key: 'total', + }, + ]; + return (
    @@ -191,7 +222,13 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { Annotations statistics - +
    diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index cae83c7c..5c2867b9 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -6,7 +6,7 @@ import React from 'react'; import Input from 'antd/lib/input'; import { Col, Row } from 'antd/lib/grid'; -import { PredictorState, Workspace } from 'reducers/interfaces'; +import { ActiveControl, PredictorState, Workspace } from 'reducers/interfaces'; import LeftGroup from './left-group'; import PlayerButtons from './player-buttons'; import PlayerNavigation from './player-navigation'; @@ -27,6 +27,7 @@ interface Props { saveShortcut: string; undoShortcut: string; redoShortcut: string; + drawShortcut: string; playPauseShortcut: string; nextFrameShortcut: string; previousFrameShortcut: string; @@ -37,6 +38,7 @@ interface Props { focusFrameInputShortcut: string; predictor: PredictorState; isTrainingActive: boolean; + activeControl: ActiveControl; changeWorkspace(workspace: Workspace): void; switchPredictor(predictorEnabled: boolean): void; showStatistics(): void; @@ -56,8 +58,8 @@ interface Props { onURLIconClick(): void; onUndoClick(): void; onRedoClick(): void; + onFinishDraw(): void; jobInstance: any; - hideShowContextImage(): any; } export default function AnnotationTopBarComponent(props: Props): JSX.Element { @@ -76,6 +78,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { saveShortcut, undoShortcut, redoShortcut, + drawShortcut, playPauseShortcut, nextFrameShortcut, previousFrameShortcut, @@ -85,6 +88,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { nextButtonType, predictor, focusFrameInputShortcut, + activeControl, showStatistics, switchPredictor, showFilters, @@ -104,6 +108,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { onURLIconClick, onUndoClick, onRedoClick, + onFinishDraw, jobInstance, isTrainingActive, } = props; @@ -118,9 +123,12 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { saveShortcut={saveShortcut} undoShortcut={undoShortcut} redoShortcut={redoShortcut} + activeControl={activeControl} + drawShortcut={drawShortcut} onSaveAnnotation={onSaveAnnotation} onUndoClick={onUndoClick} onRedoClick={onRedoClick} + onFinishDraw={onFinishDraw} /> diff --git a/cvat-ui/src/components/create-project-page/create-project-content.tsx b/cvat-ui/src/components/create-project-page/create-project-content.tsx index b17a4958..52fac3c4 100644 --- a/cvat-ui/src/components/create-project-page/create-project-content.tsx +++ b/cvat-ui/src/components/create-project-page/create-project-content.tsx @@ -96,7 +96,7 @@ function AdaptiveAutoAnnotationForm({ formRef }: { formRef: RefObject }): JSX.Element { +function AdvancedConfigurationForm({ formRef }: { formRef: RefObject }): JSX.Element { return (
    - + + Default number of points in polygon approximation + + + + + + Works for serverless interactors and OpenCV scissors + + ); } + +export default React.memo(WorkspaceSettingsComponent); diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 8937be10..5b4a373a 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -27,7 +27,7 @@ enum ConstructorMode { UPDATE = 'UPDATE', } -interface LabelsEditortProps { +interface LabelsEditorProps { labels: Label[]; onSubmit: (labels: any[]) => void; } @@ -39,8 +39,8 @@ interface LabelsEditorState { labelForUpdate: Label | null; } -export default class LabelsEditor extends React.PureComponent { - public constructor(props: LabelsEditortProps) { +export default class LabelsEditor extends React.PureComponent { + public constructor(props: LabelsEditorProps) { super(props); this.state = { @@ -53,10 +53,10 @@ export default class LabelsEditor extends React.PureComponent void; +} + +export default function LabelMapperItem(props: LabelMapperItemProps): JSX.Element { + const { + label, value, onChange, projectLabels, labelMappers, + } = props; + + const labelNames = labelMappers.map((mapper) => mapper.newLabelName).filter((el) => el); + + return ( + + + {label.name.length > 12 ? ( + + {`${label.name.slice(0, 12)}...`} + + ) : ( + {label.name} + )} + + + + + + + + onChange({ + ...value, + clearAttributes: _value.target.checked, + })} + > + Clear attributes + + + + ); +} diff --git a/cvat-ui/src/components/move-task-modal/move-task-modal.tsx b/cvat-ui/src/components/move-task-modal/move-task-modal.tsx new file mode 100644 index 00000000..fe356899 --- /dev/null +++ b/cvat-ui/src/components/move-task-modal/move-task-modal.tsx @@ -0,0 +1,160 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React, { useState, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import Modal from 'antd/lib/modal'; +import { Row, Col } from 'antd/lib/grid'; +import Divider from 'antd/lib/divider'; +import notification from 'antd/lib/notification'; +import { QuestionCircleFilled } from '@ant-design/icons'; + +import ProjectSearch from 'components/create-task-page/project-search-field'; +import CVATTooltip from 'components/common/cvat-tooltip'; +import { CombinedState } from 'reducers/interfaces'; +import { switchMoveTaskModalVisible, moveTaskToProjectAsync } from 'actions/tasks-actions'; +import getCore from 'cvat-core-wrapper'; +import LabelMapperItem, { LabelMapperItemValue } from './label-mapper-item'; + +const core = getCore(); + +export default function MoveTaskModal(): JSX.Element { + const visible = useSelector((state: CombinedState) => state.tasks.moveTask.modalVisible); + const task = useSelector((state: CombinedState) => { + const [taskInstance] = state.tasks.current.filter((_task) => _task.instance.id === state.tasks.moveTask.taskId); + return taskInstance?.instance; + }); + const taskUpdating = useSelector((state: CombinedState) => state.tasks.updating); + const dispatch = useDispatch(); + + const [projectId, setProjectId] = useState(null); + const [project, setProject] = useState(null); + const [labelMap, setLabelMap] = useState<{ [key: string]: LabelMapperItemValue }>({}); + + const initValues = (): void => { + if (task) { + const labelValues: { [key: string]: LabelMapperItemValue } = {}; + task.labels.forEach((label: any) => { + labelValues[label.id] = { + labelId: label.id, + newLabelName: null, + clearAttributes: true, + }; + }); + setLabelMap(labelValues); + } + }; + + const onCancel = (): void => { + dispatch(switchMoveTaskModalVisible(false)); + initValues(); + setProject(null); + setProjectId(null); + }; + + const submitMove = async (): Promise => { + if (!projectId) { + notification.error({ + message: 'Project not selected', + }); + return; + } + if (!Object.values(labelMap).every((map) => map.newLabelName !== null)) { + notification.error({ + message: 'Not all labels mapped', + description: 'Please choose any action to not mapped labels first', + }); + return; + } + dispatch( + moveTaskToProjectAsync( + task, + projectId, + Object.values(labelMap).map((map) => ({ + label_id: map.labelId, + new_label_name: map.newLabelName, + clear_attributes: map.clearAttributes, + })), + ), + ); + onCancel(); + }; + + useEffect(() => { + if (projectId) { + core.projects.get({ id: projectId }).then((_project: any) => { + if (projectId) { + setProject(_project[0]); + const { labels } = _project[0]; + const labelValues: { [key: string]: LabelMapperItemValue } = {}; + Object.entries(labelMap).forEach(([id, label]) => { + const taskLabelName = task.labels.filter((_label: any) => _label.id === label.labelId)[0].name; + const [autoNewLabel] = labels.filter((_label: any) => _label.name === taskLabelName); + labelValues[id] = { + labelId: label.labelId, + newLabelName: autoNewLabel ? autoNewLabel.name : null, + clearAttributes: true, + }; + }); + setLabelMap(labelValues); + } + }); + } else { + setProject(null); + } + }, [projectId]); + + useEffect(() => { + initValues(); + }, [task?.id]); + + return ( + + {`Move task ${task?.id} to project`} + {/* TODO: replace placeholder */} + + + + + )} + className='cvat-task-move-modal' + > + + Project: + + _project.id !== task?.projectId} + /> + + + Label mapping + {!!Object.keys(labelMap).length && + !taskUpdating && + task?.labels.map((label: any) => ( + { + setLabelMap({ + ...labelMap, + [value.labelId]: value, + }); + }} + /> + ))} + + ); +} diff --git a/cvat-ui/src/components/move-task-modal/styles.scss b/cvat-ui/src/components/move-task-modal/styles.scss new file mode 100644 index 00000000..6373a0a8 --- /dev/null +++ b/cvat-ui/src/components/move-task-modal/styles.scss @@ -0,0 +1,34 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base.scss'; + +.cvat-task-move-modal > .ant-modal-content { + > .ant-modal-body { + > div:nth-child(1) { + margin-bottom: $grid-unit-size * 2; + + > div:nth-child(1) { + padding-right: $grid-unit-size * 2; + } + } + } + + > .ant-modal-header .anticon { + margin-left: $grid-unit-size; + + > svg { + color: $text-color-secondary; + } + } + + .ant-select { + margin: 0 $grid-unit-size; + width: $grid-unit-size * 25; + } +} + +.cvat-move-task-label-mapper-item { + margin: $grid-unit-size * 2 0; +} diff --git a/cvat-ui/src/components/project-page/project-page.tsx b/cvat-ui/src/components/project-page/project-page.tsx index 348b5d0e..385d58c5 100644 --- a/cvat-ui/src/components/project-page/project-page.tsx +++ b/cvat-ui/src/components/project-page/project-page.tsx @@ -28,18 +28,16 @@ export default function ProjectPageComponent(): JSX.Element { const id = +useParams().id; const dispatch = useDispatch(); const history = useHistory(); - const projects = useSelector((state: CombinedState) => state.projects.current); + const projects = useSelector((state: CombinedState) => state.projects.current).map((project) => project.instance); const projectsFetching = useSelector((state: CombinedState) => state.projects.fetching); const deletes = useSelector((state: CombinedState) => state.projects.activities.deletes); const taskDeletes = useSelector((state: CombinedState) => state.tasks.activities.deletes); const tasksActiveInferences = useSelector((state: CombinedState) => state.models.inferences); const tasks = useSelector((state: CombinedState) => state.tasks.current); - const projectSubsets = useSelector((state: CombinedState) => { - const [project] = state.projects.current.filter((_project) => _project.id === id); - return project ? ([...new Set(project.tasks.map((task: any) => task.subset))] as string[]) : []; - }); const [project] = projects.filter((_project) => _project.id === id); + const projectSubsets = ['']; + if (project) projectSubsets.push(...project.subsets); const deleteActivity = project && id in deletes ? deletes[id] : null; useEffect(() => { @@ -90,7 +88,7 @@ export default function ProjectPageComponent(): JSX.Element { - {projectSubsets.map((subset) => ( + {projectSubsets.map((subset: string) => ( {subset && {subset}} {tasks diff --git a/cvat-ui/src/components/project-page/styles.scss b/cvat-ui/src/components/project-page/styles.scss index 4769c556..71767d2d 100644 --- a/cvat-ui/src/components/project-page/styles.scss +++ b/cvat-ui/src/components/project-page/styles.scss @@ -4,6 +4,11 @@ @import '../../base.scss'; +.cvat-project-page { + overflow-y: auto; + height: 100%; +} + .cvat-project-details { width: 100%; height: auto; diff --git a/cvat-ui/src/components/projects-page/project-item.tsx b/cvat-ui/src/components/projects-page/project-item.tsx index 6d80e04c..2ca8523f 100644 --- a/cvat-ui/src/components/projects-page/project-item.tsx +++ b/cvat-ui/src/components/projects-page/project-item.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -22,24 +22,18 @@ interface Props { } export default function ProjectItemComponent(props: Props): JSX.Element { - const { projectInstance } = props; + const { + projectInstance: { instance, preview }, + } = props; const history = useHistory(); - const ownerName = projectInstance.owner ? projectInstance.owner.username : null; - const updated = moment(projectInstance.updatedDate).fromNow(); + const ownerName = instance.owner ? instance.owner.username : null; + const updated = moment(instance.updatedDate).fromNow(); const deletes = useSelector((state: CombinedState) => state.projects.activities.deletes); - const deleted = projectInstance.id in deletes ? deletes[projectInstance.id] : false; - - let projectPreview = null; - if (projectInstance.tasks.length) { - // prettier-ignore - projectPreview = useSelector((state: CombinedState) => ( - state.tasks.current.find((task) => task.instance.id === projectInstance.tasks[0].id)?.preview - )); - } + const deleted = instance.id in deletes ? deletes[instance.id] : false; const onOpenProject = (): void => { - history.push(`/projects/${projectInstance.id}`); + history.push(`/projects/${instance.id}`); }; const style: React.CSSProperties = {}; @@ -52,10 +46,10 @@ export default function ProjectItemComponent(props: Props): JSX.Element { return ( - {projectInstance.name} + {instance.name} )} description={( @@ -88,7 +82,7 @@ export default function ProjectItemComponent(props: Props): JSX.Element { {`Last updated ${updated}`}
    - }> + }>
    diff --git a/cvat-ui/src/components/projects-page/project-list.tsx b/cvat-ui/src/components/projects-page/project-list.tsx index 1d713890..494b501f 100644 --- a/cvat-ui/src/components/projects-page/project-list.tsx +++ b/cvat-ui/src/components/projects-page/project-list.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,14 +8,14 @@ import { Row, Col } from 'antd/lib/grid'; import Pagination from 'antd/lib/pagination'; import { getProjectsAsync } from 'actions/projects-actions'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, Project } from 'reducers/interfaces'; import ProjectItem from './project-item'; export default function ProjectListComponent(): JSX.Element { const dispatch = useDispatch(); const projectsCount = useSelector((state: CombinedState) => state.projects.count); const { page } = useSelector((state: CombinedState) => state.projects.gettingQuery); - let projectInstances = useSelector((state: CombinedState) => state.projects.current); + const projectInstances = useSelector((state: CombinedState) => state.projects.current); const gettingQuery = useSelector((state: CombinedState) => state.projects.gettingQuery); function changePage(p: number): void { @@ -27,7 +27,7 @@ export default function ProjectListComponent(): JSX.Element { ); } - projectInstances = projectInstances.reduce((rows, key, index) => { + const projects = projectInstances.reduce((rows, key, index) => { if (index % 4 === 0) { rows.push([key]); } else { @@ -38,14 +38,14 @@ export default function ProjectListComponent(): JSX.Element { return ( <> - +
    - {projectInstances.map( - (row: any[]): JSX.Element => ( - - {row.map((instance: any) => ( - - + {projects.map( + (row: Project[]): JSX.Element => ( + + {row.map((project: Project) => ( + + ))} diff --git a/cvat-ui/src/components/projects-page/styles.scss b/cvat-ui/src/components/projects-page/styles.scss index 44fb6615..dd60013d 100644 --- a/cvat-ui/src/components/projects-page/styles.scss +++ b/cvat-ui/src/components/projects-page/styles.scss @@ -117,3 +117,7 @@ object-fit: cover; } } + +.cvat-project-list-content { + padding-bottom: $grid-unit-size; +} diff --git a/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx index 9030009b..b22005a2 100644 --- a/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx +++ b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx @@ -12,6 +12,7 @@ import { CombinedState } from 'reducers/interfaces'; interface StateToProps { visible: boolean; + jobInstance: any; } interface DispatchToProps { @@ -21,10 +22,14 @@ interface DispatchToProps { function mapStateToProps(state: CombinedState): StateToProps { const { shortcuts: { visibleShortcutsHelp: visible }, + annotation: { + job: { instance: jobInstance }, + }, } = state; return { visible, + jobInstance, }; } @@ -36,8 +41,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }; } -function ShorcutsDialog(props: StateToProps & DispatchToProps): JSX.Element | null { - const { visible, switchShortcutsDialog } = props; +function ShortcutsDialog(props: StateToProps & DispatchToProps): JSX.Element | null { + const { visible, switchShortcutsDialog, jobInstance } = props; const keyMap = getApplicationKeyMap(); const splitToRows = (data: string[]): JSX.Element[] => @@ -76,13 +81,17 @@ function ShorcutsDialog(props: StateToProps & DispatchToProps): JSX.Element | nu }, ]; - const dataSource = Object.keys(keyMap).map((key: string, id: number) => ({ - key: id, - name: keyMap[key].name || key, - description: keyMap[key].description || '', - shortcut: keyMap[key].sequences, - action: [keyMap[key].action], - })); + const dimensionType = jobInstance ? jobInstance.task.dimension : undefined; + + const dataSource = Object.keys(keyMap) + .filter((key: string) => !dimensionType || keyMap[key].applicable.includes(dimensionType)) + .map((key: string, id: number) => ({ + key: id, + name: keyMap[key].name || key, + description: keyMap[key].description || '', + shortcut: keyMap[key].sequences, + action: [keyMap[key].action], + })); return ( -
    +
    ); } -export default connect(mapStateToProps, mapDispatchToProps)(ShorcutsDialog); +export default connect(mapStateToProps, mapDispatchToProps)(ShortcutsDialog); diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx index a6b7fde1..627dbf9d 100644 --- a/cvat-ui/src/components/task-page/task-page.tsx +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -13,6 +13,7 @@ import Result from 'antd/lib/result'; import DetailsContainer from 'containers/task-page/details'; import JobListContainer from 'containers/task-page/job-list'; import ModelRunnerModal from 'components/model-runner-modal/model-runner-dialog'; +import MoveTaskModal from 'components/move-task-modal/move-task-modal'; import { Task } from 'reducers/interfaces'; import TopBarComponent from './top-bar'; @@ -83,6 +84,7 @@ class TaskPageComponent extends React.PureComponent { + {updating && } ); diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx index 39ee2110..43b25f9e 100644 --- a/cvat-ui/src/components/task-page/user-selector.tsx +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -29,6 +29,7 @@ const searchUsers = debounce( .get({ search: searchValue, limit: 10, + is_active: true, }) .then((result: User[]) => { if (result) { @@ -50,7 +51,7 @@ export default function UserSelector(props: Props): JSX.Element { const autocompleteRef = useRef(null); useEffect(() => { - core.users.get({ limit: 10 }).then((result: User[]) => { + core.users.get({ limit: 10, is_active: true }).then((result: User[]) => { if (result) { setInitialUsers(result); } diff --git a/cvat-ui/src/components/tasks-page/styles.scss b/cvat-ui/src/components/tasks-page/styles.scss index 73ad1e57..2c6dce8c 100644 --- a/cvat-ui/src/components/tasks-page/styles.scss +++ b/cvat-ui/src/components/tasks-page/styles.scss @@ -11,6 +11,23 @@ height: 100%; width: 100%; + .cvat-tasks-page-top-bar { + > div:nth-child(1) { + > div:nth-child(1) { + width: 100%; + + > div:nth-child(1) { + display: flex; + + > span:nth-child(2) { + width: 200px; + margin-left: 10px; + } + } + } + } + } + > div:nth-child(2) { height: 83%; padding-top: 10px; @@ -19,22 +36,6 @@ > div:nth-child(3) { padding-top: 10px; } - - > div:nth-child(1) { - > div:nth-child(1) { - display: flex; - - > span:nth-child(2) { - width: 200px; - margin-left: 10px; - } - } - - > div:nth-child(2) { - display: flex; - justify-content: flex-end; - } - } } /* empty-tasks icon */ @@ -157,3 +158,11 @@ #cvat-create-task-button { padding: 0 30px; } + +#cvat-import-task-button { + padding: 0 30px; +} + +#cvat-import-task-button-loading { + margin-left: 10; +} diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 32e45599..e8dae76a 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -74,7 +74,7 @@ class TaskItemComponent extends React.PureComponent job.status === 'completed').length; - // Progress appearence depends on number of jobs + // Progress appearance depends on number of jobs let progressColor = null; let progressText = null; if (numOfCompleted && numOfCompleted === numOfJobs) { diff --git a/cvat-ui/src/components/tasks-page/task-list.tsx b/cvat-ui/src/components/tasks-page/task-list.tsx index 6cd7b96b..3146c60b 100644 --- a/cvat-ui/src/components/tasks-page/task-list.tsx +++ b/cvat-ui/src/components/tasks-page/task-list.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -7,6 +7,7 @@ import { Row, Col } from 'antd/lib/grid'; import Pagination from 'antd/lib/pagination'; import ModelRunnerModal from 'components/model-runner-modal/model-runner-dialog'; +import MoveTaskModal from 'components/move-task-modal/move-task-modal'; import TaskItem from 'containers/tasks-page/task-item'; export interface ContentListProps { @@ -43,6 +44,7 @@ export default function TaskListComponent(props: ContentListProps): JSX.Element + ); } diff --git a/cvat-ui/src/components/tasks-page/tasks-page.tsx b/cvat-ui/src/components/tasks-page/tasks-page.tsx index daafb44d..6e0df457 100644 --- a/cvat-ui/src/components/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/components/tasks-page/tasks-page.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -25,6 +25,8 @@ interface TasksPageProps { numberOfHiddenTasks: number; onGetTasks: (gettingQuery: TasksQuery) => void; hideEmptyTasks: (hideEmpty: boolean) => void; + onImportTask: (file: File) => void; + taskImporting: boolean; } function getSearchField(gettingQuery: TasksQuery): string { @@ -81,9 +83,20 @@ class TasksPageComponent extends React.PureComponent; @@ -194,7 +209,12 @@ class TasksPageComponent extends React.PureComponent - + {numberOfVisibleTasks ? ( ) : ( diff --git a/cvat-ui/src/components/tasks-page/top-bar.tsx b/cvat-ui/src/components/tasks-page/top-bar.tsx index 1e4ac486..ce2d33c1 100644 --- a/cvat-ui/src/components/tasks-page/top-bar.tsx +++ b/cvat-ui/src/components/tasks-page/top-bar.tsx @@ -5,50 +5,84 @@ import React from 'react'; import { useHistory } from 'react-router'; import { Row, Col } from 'antd/lib/grid'; -import { PlusOutlined } from '@ant-design/icons'; +import { PlusOutlined, UploadOutlined, LoadingOutlined } from '@ant-design/icons'; import Button from 'antd/lib/button'; import Input from 'antd/lib/input'; import Text from 'antd/lib/typography/Text'; +import Upload from 'antd/lib/upload'; import SearchTooltip from 'components/search-tooltip/search-tooltip'; interface VisibleTopBarProps { onSearch: (value: string) => void; + onFileUpload(file: File): void; searchValue: string; + taskImporting: boolean; } export default function TopBarComponent(props: VisibleTopBarProps): JSX.Element { - const { searchValue, onSearch } = props; + const { + searchValue, onSearch, onFileUpload, taskImporting, + } = props; const history = useHistory(); return ( - <> - - - Tasks - - - - - - - - - + + + + + Tasks + + + + + + + + { + onFileUpload(file); + return false; + }} + className='cvat-import-task' + > + + + + + + + + + + + ); } diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts index 65d408ed..e5df214c 100644 --- a/cvat-ui/src/consts.ts +++ b/cvat-ui/src/consts.ts @@ -11,11 +11,11 @@ const GITTER_PUBLIC_URL = 'https://gitter.im/opencv-cvat/public'; const FORUM_URL = 'https://software.intel.com/en-us/forums/intel-distribution-of-openvino-toolkit'; const GITHUB_URL = 'https://github.com/openvinotoolkit/cvat'; const GITHUB_IMAGE_URL = - 'https://raw.githubusercontent.com/openvinotoolkit/cvat/develop/cvat/apps/documentation/static/documentation/images/cvat.jpg'; + 'https://github.com/openvinotoolkit/cvat/raw/develop/site/content/en/images/cvat.jpg'; const SHARE_MOUNT_GUIDE_URL = - 'https://github.com/openvinotoolkit/cvat/blob/master/cvat/apps/documentation/installation.md#share-path'; + 'https://openvinotoolkit.github.io/cvat/docs/administration/basics/installation/#share-path'; const NUCLIO_GUIDE = - 'https://github.com/openvinotoolkit/cvat/blob/develop/cvat/apps/documentation/installation.md#semi-automatic-and-automatic-annotation'; + 'https://openvinotoolkit.github.io/cvat//docs/administration/advanced/installation_automatic_annotation/'; const CANVAS_BACKGROUND_COLORS = ['#ffffff', '#f1f1f1', '#e5e5e5', '#d8d8d8', '#CCCCCC', '#B3B3B3', '#999999']; const NEW_LABEL_COLOR = '#b3b3b3'; const LATEST_COMMENTS_SHOWN_QUICK_ISSUE = 3; diff --git a/cvat-ui/src/containers/actions-menu/actions-menu.tsx b/cvat-ui/src/containers/actions-menu/actions-menu.tsx index acc6a451..5923928c 100644 --- a/cvat-ui/src/containers/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/containers/actions-menu/actions-menu.tsx @@ -4,16 +4,21 @@ import React from 'react'; import { connect } from 'react-redux'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { MenuInfo } from 'rc-menu/lib/interface'; import ActionsMenuComponent, { Actions } from 'components/actions-menu/actions-menu'; import { CombinedState } from 'reducers/interfaces'; import { modelsActions } from 'actions/models-actions'; import { - dumpAnnotationsAsync, loadAnnotationsAsync, exportDatasetAsync, deleteTaskAsync, + dumpAnnotationsAsync, + loadAnnotationsAsync, + exportDatasetAsync, + deleteTaskAsync, + exportTaskAsync, + switchMoveTaskModalVisible, } from 'actions/tasks-actions'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { MenuInfo } from 'rc-menu/lib/interface'; interface OwnProps { taskInstance: any; @@ -25,6 +30,7 @@ interface StateToProps { dumpActivities: string[] | null; exportActivities: string[] | null; inferenceIsActive: boolean; + exportIsActive: boolean; } interface DispatchToProps { @@ -33,6 +39,8 @@ interface DispatchToProps { exportDataset: (taskInstance: any, exporter: any) => void; deleteTask: (taskInstance: any) => void; openRunModelWindow: (taskInstance: any) => void; + exportTask: (taskInstance: any) => void; + openMoveTaskToProjectWindow: (taskInstance: any) => void; } function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { @@ -43,7 +51,9 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const { formats: { annotationFormats }, tasks: { - activities: { dumps, loads, exports: activeExports }, + activities: { + dumps, loads, exports: activeExports, backups, + }, }, } = state; @@ -53,6 +63,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { loadActivity: tid in loads ? loads[tid] : null, annotationFormats, inferenceIsActive: tid in state.models.inferences, + exportIsActive: tid in backups, }; } @@ -73,6 +84,12 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { openRunModelWindow: (taskInstance: any): void => { dispatch(modelsActions.showRunModelDialog(taskInstance)); }, + exportTask: (taskInstance: any): void => { + dispatch(exportTaskAsync(taskInstance)); + }, + openMoveTaskToProjectWindow: (taskId: number): void => { + dispatch(switchMoveTaskModalVisible(true, taskId)); + }, }; } @@ -84,12 +101,15 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): dumpActivities, exportActivities, inferenceIsActive, + exportIsActive, loadAnnotations, dumpAnnotations, exportDataset, deleteTask, openRunModelWindow, + exportTask, + openMoveTaskToProjectWindow, } = props; function onClickMenu(params: MenuInfo, file?: File): void { @@ -119,10 +139,13 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): if (action === Actions.DELETE_TASK) { deleteTask(taskInstance); } else if (action === Actions.OPEN_BUG_TRACKER) { - // eslint-disable-next-line window.open(`${taskInstance.bugTracker}`, '_blank'); } else if (action === Actions.RUN_AUTO_ANNOTATION) { openRunModelWindow(taskInstance); + } else if (action === Actions.EXPORT_TASK) { + exportTask(taskInstance); + } else if (action === Actions.MOVE_TASK_TO_PROJECT) { + openMoveTaskToProjectWindow(taskInstance.id); } } } @@ -140,6 +163,7 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): inferenceIsActive={inferenceIsActive} onClickMenu={onClickMenu} taskDimension={taskInstance.dimension} + exportIsActive={exportIsActive} /> ); } diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx index 70e94de3..cc92a231 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -50,10 +50,11 @@ import { } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas-wrapper'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; interface StateToProps { sidebarCollapsed: boolean; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; jobInstance: any; activatedStateID: number | null; activatedAttributeID: number | null; diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx index f65bd054..8912a460 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx @@ -5,43 +5,137 @@ import { connect } from 'react-redux'; import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper3D'; -import { confirmCanvasReady, getContextImage, resetCanvas } from 'actions/annotation-actions'; +import { + activateObject, + confirmCanvasReady, + createAnnotationsAsync, + dragCanvas, + editShape, + groupAnnotationsAsync, + groupObjects, + resetCanvas, + shapeDrawn, + updateAnnotationsAsync, + updateCanvasContextMenu, +} from 'actions/annotation-actions'; -import { CombinedState } from 'reducers/interfaces'; +import { + ActiveControl, + ColorBy, + CombinedState, + ContextMenuType, + GridColor, + ObjectType, + Workspace, +} from 'reducers/interfaces'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { Canvas } from 'cvat-canvas-wrapper'; +import { KeyMap } from '../../../utils/mousetrap-react'; interface StateToProps { - canvasInstance: Canvas3d; + canvasInstance: Canvas3d | Canvas; jobInstance: any; frameData: any; curZLayer: number; - contextImageHide: boolean; - loaded: boolean; - data: string; annotations: any[]; + sidebarCollapsed: boolean; + activatedStateID: number | null; + activatedAttributeID: number | null; + selectedStatesID: number[]; + frameIssues: any[] | null; + frameAngle: number; + frameFetching: boolean; + frame: number; + opacity: number; + colorBy: ColorBy; + selectedOpacity: number; + outlined: boolean; + outlineColor: string; + showBitmap: boolean; + showProjections: boolean; + grid: boolean; + gridSize: number; + gridColor: GridColor; + gridOpacity: number; + activeLabelID: number; + activeObjectType: ObjectType; + brightnessLevel: number; + contrastLevel: number; + saturationLevel: number; + resetZoom: boolean; + aamZoomMargin: number; + contextMenuVisibility: boolean; + showObjectsTextAlways: boolean; + showAllInterpolationTracks: boolean; + workspace: Workspace; + minZLayer: number; + maxZLayer: number; + automaticBordering: boolean; + switchableAutomaticBordering: boolean; + keyMap: KeyMap; + canvasBackgroundColor: string; } interface DispatchToProps { + onDragCanvas: (enabled: boolean) => void; onSetupCanvas(): void; - getContextImage(): void; + onGroupObjects: (enabled: boolean) => void; onResetCanvas(): void; + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onUpdateAnnotations(states: any[]): void; + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onActivateObject: (activatedStateID: number | null) => void; + onShapeDrawn: () => void; + onEditShape: (enabled: boolean) => void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; } function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { - canvas: { instance: canvasInstance }, + canvas: { + activeControl, + instance: canvasInstance, + contextMenu: { visible: contextMenuVisibility }, + }, + drawing: { activeLabelID, activeObjectType }, job: { instance: jobInstance }, player: { - frame: { data: frameData }, - contextImage: { hidden: contextImageHide, data, loaded }, + frame: { data: frameData, number: frame, fetching: frameFetching }, + frameAngles, }, annotations: { states: annotations, - zLayer: { cur: curZLayer }, + activatedStateID, + activatedAttributeID, + selectedStatesID, + zLayer: { cur: curZLayer, min: minZLayer, max: maxZLayer }, }, + sidebarCollapsed, + workspace, }, + settings: { + player: { + canvasBackgroundColor, + grid, + gridSize, + gridColor, + gridOpacity, + brightnessLevel, + contrastLevel, + saturationLevel, + resetZoom, + }, + workspace: { + aamZoomMargin, showObjectsTextAlways, showAllInterpolationTracks, automaticBordering, + }, + shapes: { + opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, + }, + }, + review: { frameIssues, issuesHidden }, + shortcuts: { keyMap }, } = state; return { @@ -49,24 +143,95 @@ function mapStateToProps(state: CombinedState): StateToProps { jobInstance, frameData, curZLayer, - contextImageHide, - loaded, - data, + contextMenuVisibility, annotations, + sidebarCollapsed, + frameIssues: + issuesHidden || ![Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) ? null : frameIssues, + frameAngle: frameAngles[frame - jobInstance.startFrame], + frameFetching, + frame, + activatedStateID, + activatedAttributeID, + selectedStatesID, + opacity, + colorBy, + selectedOpacity, + outlined, + outlineColor, + showBitmap, + showProjections, + grid, + gridSize, + gridColor, + gridOpacity, + activeLabelID, + activeObjectType, + brightnessLevel, + contrastLevel, + saturationLevel, + resetZoom, + aamZoomMargin, + showObjectsTextAlways, + showAllInterpolationTracks, + minZLayer, + maxZLayer, + automaticBordering, + workspace, + keyMap, + canvasBackgroundColor, + switchableAutomaticBordering: + activeControl === ActiveControl.DRAW_POLYGON || + activeControl === ActiveControl.DRAW_POLYLINE || + activeControl === ActiveControl.EDIT, }; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { + onDragCanvas(enabled: boolean): void { + dispatch(dragCanvas(enabled)); + }, onSetupCanvas(): void { dispatch(confirmCanvasReady()); }, - getContextImage(): void { - dispatch(getContextImage()); - }, onResetCanvas(): void { dispatch(resetCanvas()); }, + onGroupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(createAnnotationsAsync(sessionInstance, frame, states)); + }, + onShapeDrawn(): void { + dispatch(shapeDrawn()); + }, + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); + }, + onActivateObject(activatedStateID: number | null): void { + if (activatedStateID === null) { + dispatch(updateCanvasContextMenu(false, 0, 0)); + } + + dispatch(activateObject(activatedStateID, null)); + }, + onEditShape(enabled: boolean): void { + dispatch(editShape(enabled)); + }, + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); + }, + onUpdateContextMenu( + visible: boolean, + left: number, + top: number, + type: ContextMenuType, + pointID?: number, + ): void { + dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); + }, }; } diff --git a/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx index 44f98442..8f3c67e3 100644 --- a/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx @@ -5,17 +5,7 @@ import { connect } from 'react-redux'; import { Canvas } from 'cvat-canvas-wrapper'; -import { - selectIssuePosition as selectIssuePositionAction, - mergeObjects, - groupObjects, - splitTrack, - redrawShapeAsync, - rotateCurrentFrame, - repeatDrawShapeAsync, - pasteShapeAsync, - resetAnnotationsGroup, -} from 'actions/annotation-actions'; +import { selectIssuePosition as selectIssuePositionAction, rotateCurrentFrame } from 'actions/annotation-actions'; import ControlsSideBarComponent from 'components/annotation-page/review-workspace/controls-side-bar/controls-side-bar'; import { ActiveControl, CombinedState, Rotation } from 'reducers/interfaces'; import { KeyMap } from 'utils/mousetrap-react'; @@ -29,15 +19,8 @@ interface StateToProps { } interface DispatchToProps { - mergeObjects(enabled: boolean): void; - groupObjects(enabled: boolean): void; - splitTrack(enabled: boolean): void; rotateFrame(angle: Rotation): void; selectIssuePosition(enabled: boolean): void; - resetGroup(): void; - repeatDrawShape(): void; - pasteShape(): void; - redrawShape(): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -53,7 +36,7 @@ function mapStateToProps(state: CombinedState): StateToProps { return { rotateAll, - canvasInstance, + canvasInstance: canvasInstance as Canvas, activeControl, normalizedKeyMap, keyMap, @@ -62,33 +45,12 @@ function mapStateToProps(state: CombinedState): StateToProps { function dispatchToProps(dispatch: any): DispatchToProps { return { - mergeObjects(enabled: boolean): void { - dispatch(mergeObjects(enabled)); - }, - groupObjects(enabled: boolean): void { - dispatch(groupObjects(enabled)); - }, - splitTrack(enabled: boolean): void { - dispatch(splitTrack(enabled)); - }, selectIssuePosition(enabled: boolean): void { dispatch(selectIssuePositionAction(enabled)); }, rotateFrame(rotation: Rotation): void { dispatch(rotateCurrentFrame(rotation)); }, - repeatDrawShape(): void { - dispatch(repeatDrawShapeAsync()); - }, - pasteShape(): void { - dispatch(pasteShapeAsync()); - }, - resetGroup(): void { - dispatch(resetAnnotationsGroup()); - }, - redrawShape(): void { - dispatch(redrawShapeAsync()); - }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 9cd2669c..ca5ce632 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -9,6 +9,7 @@ import { RadioChangeEvent } from 'antd/lib/radio'; import { CombinedState, ShapeType, ObjectType } from 'reducers/interfaces'; import { rememberObject } from 'actions/annotation-actions'; import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; interface OwnProps { @@ -22,14 +23,16 @@ interface DispatchToProps { objectType: ObjectType, points?: number, rectDrawingMethod?: RectDrawingMethod, + cuboidDrawingMethod?: CuboidDrawingMethod, ): void; } interface StateToProps { normalizedKeyMap: Record; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; shapeType: ShapeType; labels: any[]; + jobInstance: any; } function mapDispatchToProps(dispatch: any): DispatchToProps { @@ -40,6 +43,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { objectType: ObjectType, points?: number, rectDrawingMethod?: RectDrawingMethod, + cuboidDrawingMethod?: CuboidDrawingMethod, ): void { dispatch( rememberObject({ @@ -48,6 +52,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { activeLabelID: labelID, activeNumOfPoints: points, activeRectDrawingMethod: rectDrawingMethod, + activeCuboidDrawingMethod: cuboidDrawingMethod, }), ); }, @@ -58,7 +63,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const { annotation: { canvas: { instance: canvasInstance }, - job: { labels }, + job: { labels, instance: jobInstance }, }, shortcuts: { normalizedKeyMap }, } = state; @@ -68,6 +73,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { canvasInstance, labels, normalizedKeyMap, + jobInstance, }; } @@ -124,7 +130,7 @@ class DrawShapePopoverContainer extends React.PureComponent { crosshair: [ShapeType.RECTANGLE, ShapeType.CUBOID].includes(shapeType), }); - onDrawStart(shapeType, selectedLabelID, objectType, numberOfPoints, rectDrawingMethod); + onDrawStart(shapeType, selectedLabelID, objectType, numberOfPoints, rectDrawingMethod, cuboidDrawingMethod); } private onChangeRectDrawingMethod = (event: RadioChangeEvent): void => { @@ -165,12 +171,12 @@ class DrawShapePopoverContainer extends React.PureComponent { } = this.state; const { - normalizedKeyMap, labels, shapeType, canvasInstance, + normalizedKeyMap, labels, shapeType, jobInstance, } = this.props; return ( ; aiToolsRef: MutableRefObject; + canvasInstance: Canvas | Canvas3d; } interface DispatchToProps { @@ -72,7 +75,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { player: { frame: { number: frameNumber }, }, - canvas: { ready, activeControl }, + canvas: { instance: canvasInstance, ready, activeControl }, aiToolsRef, }, settings: { @@ -103,6 +106,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { maxZLayer, normalizedKeyMap, aiToolsRef, + canvasInstance, }; } @@ -222,11 +226,14 @@ class ObjectItemContainer extends React.PureComponent { private activate = (): void => { const { - objectState, ready, activeControl, activateObject, + objectState, ready, activeControl, activateObject, canvasInstance, } = this.props; if (ready && activeControl === ActiveControl.CURSOR) { activateObject(objectState.clientID); + if (canvasInstance instanceof Canvas3d) { + canvasInstance.activate(objectState.clientID); + } } }; @@ -343,6 +350,7 @@ class ObjectItemContainer extends React.PureComponent { colorBy, normalizedKeyMap, readonly, + jobInstance, } = this.props; let stateColor = ''; @@ -356,6 +364,7 @@ class ObjectItemContainer extends React.PureComponent { return ( ; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; } interface DispatchToProps { @@ -71,7 +71,6 @@ function mapStateToProps(state: CombinedState): StateToProps { frame: { number: frameNumber }, }, canvas: { instance: canvasInstance }, - tabContentHeight: listHeight, colors, }, settings: { @@ -94,7 +93,6 @@ function mapStateToProps(state: CombinedState): StateToProps { }); return { - listHeight, statesHidden, statesLocked, statesCollapsedAll: collapsedAll, @@ -263,7 +261,6 @@ class ObjectsListContainer extends React.PureComponent { colors, colorBy, readonly, - listHeight, statesCollapsedAll, updateAnnotations, changeGroupColor, @@ -290,6 +287,16 @@ class ObjectsListContainer extends React.PureComponent { NEXT_KEY_FRAME: keyMap.NEXT_KEY_FRAME, PREV_KEY_FRAME: keyMap.PREV_KEY_FRAME, CHANGE_OBJECT_COLOR: keyMap.CHANGE_OBJECT_COLOR, + TILT_UP: keyMap.TILT_UP, + TILT_DOWN: keyMap.TILT_DOWN, + ROTATE_LEFT: keyMap.ROTATE_LEFT, + ROTATE_RIGHT: keyMap.ROTATE_RIGHT, + MOVE_UP: keyMap.MOVE_UP, + MOVE_DOWN: keyMap.MOVE_DOWN, + MOVE_LEFT: keyMap.MOVE_LEFT, + MOVE_RIGHT: keyMap.MOVE_RIGHT, + ZOOM_IN: keyMap.ZOOM_IN, + ZOOM_OUT: keyMap.ZOOM_OUT, }; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -311,6 +318,16 @@ class ObjectsListContainer extends React.PureComponent { }; const handlers = { + TILT_UP: () => {}, // Handled by CVAT 3D Independently + TILT_DOWN: () => {}, + ROTATE_LEFT: () => {}, + ROTATE_RIGHT: () => {}, + MOVE_UP: () => {}, + MOVE_DOWN: () => {}, + MOVE_LEFT: () => {}, + MOVE_RIGHT: () => {}, + ZOOM_IN: () => {}, + ZOOM_OUT: () => {}, SWITCH_ALL_LOCK: (event: KeyboardEvent | undefined) => { preventDefault(event); this.lockAllStates(!statesLocked); @@ -441,7 +458,6 @@ class ObjectsListContainer extends React.PureComponent { <> ; - contextImageHide: boolean; - loaded: boolean; + labels: any[]; + jobInstance: any; } interface DispatchToProps { - hideShowContextImage(hidden: boolean): void; + repeatDrawShape(): void; + redrawShape(): void; + pasteShape(): void; + resetGroup(): void; + groupObjects(enabled: boolean): void; } function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { canvas: { instance: canvasInstance, activeControl }, - player: { - contextImage: { hidden: contextImageHide, loaded }, - }, + job: { labels, instance: jobInstance }, }, shortcuts: { keyMap, normalizedKeyMap }, } = state; @@ -39,15 +47,27 @@ function mapStateToProps(state: CombinedState): StateToProps { activeControl, normalizedKeyMap, keyMap, - contextImageHide, - loaded, + labels, + jobInstance, }; } function dispatchToProps(dispatch: any): DispatchToProps { return { - hideShowContextImage(hidden: boolean): void { - dispatch(hideShowContextImage(hidden)); + repeatDrawShape(): void { + dispatch(repeatDrawShapeAsync()); + }, + redrawShape(): void { + dispatch(redrawShapeAsync()); + }, + pasteShape(): void { + dispatch(pasteShapeAsync()); + }, + groupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + resetGroup(): void { + dispatch(resetAnnotationsGroup()); }, }; } diff --git a/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx index 18fba67f..1dff816e 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -61,6 +61,7 @@ class StatisticsModalContainer extends React.PureComponent { return ( ; - canvasInstance: Canvas; + canvasInstance: Canvas | Canvas3d; forceExit: boolean; predictor: PredictorState; + activeControl: ActiveControl; isTrainingActive: boolean; } @@ -83,7 +86,7 @@ function mapStateToProps(state: CombinedState): StateToProps { history, }, job: { instance: jobInstance }, - canvas: { ready: canvasIsReady, instance: canvasInstance }, + canvas: { ready: canvasIsReady, instance: canvasInstance, activeControl }, workspace, predictor, }, @@ -116,6 +119,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, forceExit, predictor, + activeControl, isTrainingActive: list.PREDICT, }; } @@ -218,48 +222,13 @@ class AnnotationTopBarContainer extends React.PureComponent { } public componentDidUpdate(prevProps: Props): void { - const { - jobInstance, - frameSpeed, - frameNumber, - frameDelay, - playing, - canvasIsReady, - canvasInstance, - onSwitchPlay, - onChangeFrame, - autoSaveInterval, - } = this.props; + const { autoSaveInterval } = this.props; if (autoSaveInterval !== prevProps.autoSaveInterval) { if (this.autoSaveInterval) window.clearInterval(this.autoSaveInterval); this.autoSaveInterval = window.setInterval(this.autoSave.bind(this), autoSaveInterval); } - - if (playing && canvasIsReady) { - if (frameNumber < jobInstance.stopFrame) { - let framesSkiped = 0; - if (frameSpeed === FrameSpeed.Fast && frameNumber + 1 < jobInstance.stopFrame) { - framesSkiped = 1; - } - if (frameSpeed === FrameSpeed.Fastest && frameNumber + 2 < jobInstance.stopFrame) { - framesSkiped = 2; - } - - setTimeout(() => { - const { playing: stillPlaying } = this.props; - if (stillPlaying) { - if (canvasInstance.isAbleToChangeFrame()) { - onChangeFrame(frameNumber + 1 + framesSkiped, stillPlaying, framesSkiped + 1); - } else { - onSwitchPlay(false); - } - } - }, frameDelay); - } else { - onSwitchPlay(false); - } - } + this.play(); } public componentWillUnmount(): void { @@ -449,6 +418,19 @@ class AnnotationTopBarContainer extends React.PureComponent { } }; + private onFinishDraw = (): void => { + const { activeControl, canvasInstance } = this.props; + if ( + [ActiveControl.AI_TOOLS, ActiveControl.OPENCV_TOOLS].includes(activeControl) && + canvasInstance instanceof Canvas + ) { + canvasInstance.interact({ enabled: false }); + return; + } + + canvasInstance.draw({ enabled: false }); + }; + private onURLIconClick = (): void => { const { frameNumber } = this.props; const { origin, pathname } = window.location; @@ -471,6 +453,47 @@ class AnnotationTopBarContainer extends React.PureComponent { return undefined; }; + private play(): void { + const { + jobInstance, + frameSpeed, + frameNumber, + frameDelay, + playing, + canvasIsReady, + canvasInstance, + onSwitchPlay, + onChangeFrame, + } = this.props; + + if (playing && canvasIsReady) { + if (frameNumber < jobInstance.stopFrame) { + let framesSkipped = 0; + if (frameSpeed === FrameSpeed.Fast && frameNumber + 1 < jobInstance.stopFrame) { + framesSkipped = 1; + } + if (frameSpeed === FrameSpeed.Fastest && frameNumber + 2 < jobInstance.stopFrame) { + framesSkipped = 2; + } + + setTimeout(() => { + const { playing: stillPlaying } = this.props; + if (stillPlaying) { + if (canvasInstance.isAbleToChangeFrame()) { + onChangeFrame(frameNumber + 1 + framesSkipped, stillPlaying, framesSkipped + 1); + } else if (jobInstance.task.dimension === DimensionType.DIM_2D) { + onSwitchPlay(false); + } else { + setTimeout(() => this.play(), frameDelay); + } + } + }, frameDelay); + } else { + onSwitchPlay(false); + } + } + } + private autoSave(): void { const { autoSave, saving } = this.props; @@ -518,10 +541,11 @@ class AnnotationTopBarContainer extends React.PureComponent { normalizedKeyMap, canvasInstance, predictor, + isTrainingActive, + activeControl, searchAnnotations, changeWorkspace, switchPredictor, - isTrainingActive, } = this.props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -647,6 +671,7 @@ class AnnotationTopBarContainer extends React.PureComponent { saveShortcut={normalizedKeyMap.SAVE_JOB} undoShortcut={normalizedKeyMap.UNDO} redoShortcut={normalizedKeyMap.REDO} + drawShortcut={normalizedKeyMap.SWITCH_DRAW_MODE} playPauseShortcut={normalizedKeyMap.PLAY_PAUSE} nextFrameShortcut={normalizedKeyMap.NEXT_FRAME} previousFrameShortcut={normalizedKeyMap.PREV_FRAME} @@ -657,8 +682,10 @@ class AnnotationTopBarContainer extends React.PureComponent { focusFrameInputShortcut={normalizedKeyMap.FOCUS_INPUT_FRAME} onUndoClick={this.undo} onRedoClick={this.redo} + onFinishDraw={this.onFinishDraw} jobInstance={jobInstance} isTrainingActive={isTrainingActive} + activeControl={activeControl} /> ); diff --git a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx index 2384c166..d92f4429 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -13,6 +13,7 @@ import { switchShowingObjectsTextAlways, switchAutomaticBordering, switchIntelligentPolygonCrop, + changeDefaultApproxPolyAccuracy, } from 'actions/settings-actions'; import { CombinedState } from 'reducers/interfaces'; @@ -25,6 +26,7 @@ interface StateToProps { aamZoomMargin: number; showAllInterpolationTracks: boolean; showObjectsTextAlways: boolean; + defaultApproxPolyAccuracy: number; automaticBordering: boolean; intelligentPolygonCrop: boolean; } @@ -37,6 +39,7 @@ interface DispatchToProps { onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; onSwitchIntelligentPolygonCrop(enabled: boolean): void; + onChangeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -49,6 +52,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, + defaultApproxPolyAccuracy, } = workspace; return { @@ -59,6 +63,7 @@ function mapStateToProps(state: CombinedState): StateToProps { showObjectsTextAlways, automaticBordering, intelligentPolygonCrop, + defaultApproxPolyAccuracy, }; } @@ -85,6 +90,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchIntelligentPolygonCrop(enabled: boolean): void { dispatch(switchIntelligentPolygonCrop(enabled)); }, + onChangeDefaultApproxPolyAccuracy(threshold: number): void { + dispatch(changeDefaultApproxPolyAccuracy(threshold)); + }, }; } diff --git a/cvat-ui/src/containers/tasks-page/tasks-page.tsx b/cvat-ui/src/containers/tasks-page/tasks-page.tsx index b18aaf00..54a7f6c1 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-page.tsx @@ -8,7 +8,7 @@ import { Task, TasksQuery, CombinedState } from 'reducers/interfaces'; import TasksPageComponent from 'components/tasks-page/tasks-page'; -import { getTasksAsync, hideEmptyTasks } from 'actions/tasks-actions'; +import { getTasksAsync, hideEmptyTasks, importTaskAsync } from 'actions/tasks-actions'; interface StateToProps { tasksFetching: boolean; @@ -16,11 +16,13 @@ interface StateToProps { numberOfTasks: number; numberOfVisibleTasks: number; numberOfHiddenTasks: number; + taskImporting: boolean; } interface DispatchToProps { onGetTasks: (gettingQuery: TasksQuery) => void; hideEmptyTasks: (hideEmpty: boolean) => void; + onImportTask: (file: File) => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -34,6 +36,7 @@ function mapStateToProps(state: CombinedState): StateToProps { numberOfHiddenTasks: tasks.hideEmpty ? tasks.current.filter((task: Task): boolean => !task.instance.jobs.length).length : 0, + taskImporting: state.tasks.importing, }; } @@ -45,6 +48,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { hideEmptyTasks: (hideEmpty: boolean): void => { dispatch(hideEmptyTasks(hideEmpty)); }, + onImportTask: (file: File): void => { + dispatch(importTaskAsync(file)); + }, }; } diff --git a/cvat-ui/src/cvat-canvas-wrapper.ts b/cvat-ui/src/cvat-canvas-wrapper.ts index 3b0c1e21..10bd8fbc 100644 --- a/cvat-ui/src/cvat-canvas-wrapper.ts +++ b/cvat-ui/src/cvat-canvas-wrapper.ts @@ -22,7 +22,7 @@ export function convertShapesForInteractor(shapes: InteractionResult[], button: }; return shapes - .filter((shape: InteractionResult): boolean => shape.shapeType === 'points' && shape.button === button) + .filter((shape: InteractionResult): boolean => shape.button === button) .map((shape: InteractionResult): number[] => shape.points) .flat() .reduce(reducer, []); diff --git a/cvat-ui/src/cvat-canvas3d-wrapper.ts b/cvat-ui/src/cvat-canvas3d-wrapper.ts index 58cb7de4..38109c9c 100644 --- a/cvat-ui/src/cvat-canvas3d-wrapper.ts +++ b/cvat-ui/src/cvat-canvas3d-wrapper.ts @@ -7,9 +7,10 @@ import { Canvas3dVersion, MouseInteraction, ViewType, - CAMERA_ACTION, + CameraAction, + ViewsDOM, } from 'cvat-canvas3d/src/typescript/canvas3d'; export { - Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CAMERA_ACTION, + Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CameraAction, ViewsDOM, }; diff --git a/cvat-ui/src/cvat-core-wrapper.ts b/cvat-ui/src/cvat-core-wrapper.ts index 66bbb3ac..f62cde27 100644 --- a/cvat-ui/src/cvat-core-wrapper.ts +++ b/cvat-ui/src/cvat-core-wrapper.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,8 +6,7 @@ import _cvat from 'cvat-core/src/api'; const cvat: any = _cvat; -cvat.config.backendAPI = - typeof process.env.REACT_APP_API_URL === 'undefined' ? '/api/v1' : `${process.env.REACT_APP_API_URL}/api/v1`; +cvat.config.backendAPI = '/api/v1'; export default function getCore(): any { return cvat; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 22426c4f..eb71b8fa 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -20,6 +20,12 @@ import { Workspace, } from './interfaces'; +function updateActivatedStateID(newStates: any[], prevActivatedStateID: number | null): number | null { + return prevActivatedStateID === null || newStates.some((_state: any) => _state.clientID === prevActivatedStateID) ? + prevActivatedStateID : + null; +} + const defaultState: AnnotationState = { activities: { loads: {}, @@ -51,6 +57,7 @@ const defaultState: AnnotationState = { number: 0, filename: '', data: null, + hasRelatedContext: false, fetching: false, delay: 0, changeTime: null, @@ -58,8 +65,8 @@ const defaultState: AnnotationState = { playing: false, frameAngles: [], contextImage: { - loaded: false, - data: '', + fetching: false, + data: null, hidden: false, }, }, @@ -108,7 +115,6 @@ const defaultState: AnnotationState = { filtersPanelVisible: false, requestReviewDialogVisible: false, submitReviewDialogVisible: false, - tabContentHeight: 0, predictor: { enabled: false, error: null, @@ -145,6 +151,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { openTime, frameNumber: number, frameFilename: filename, + frameHasRelatedContext, colors, filters, frameData: data, @@ -154,10 +161,13 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { const isReview = job.status === TaskStatus.REVIEW; let workspaceSelected = Workspace.STANDARD; + let activeShapeType = ShapeType.RECTANGLE; if (job.task.dimension === DimensionType.DIM_3D) { workspaceSelected = Workspace.STANDARD3D; + activeShapeType = ShapeType.CUBOID; } + return { ...state, job: { @@ -189,6 +199,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { frame: { ...state.player.frame, filename, + hasRelatedContext: frameHasRelatedContext, number, data, }, @@ -198,6 +209,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state.drawing, activeLabelID: job.task.labels.length ? job.task.labels[0].id : null, activeObjectType: job.task.mode === 'interpolation' ? ObjectType.TRACK : ObjectType.SHAPE, + activeShapeType, }, canvas: { ...state.canvas, @@ -226,11 +238,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state.player.frame, fetching: false, }, - contextImage: { - loaded: false, - data: '', - hidden: state.player.contextImage.hidden, - }, }, }; } @@ -251,16 +258,20 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.CHANGE_FRAME_SUCCESS: { + const { activatedStateID } = state.annotations; const { - number, data, filename, states, minZ, maxZ, curZ, delay, changeTime, + number, + data, + filename, + hasRelatedContext, + states, + minZ, + maxZ, + curZ, + delay, + changeTime, } = action.payload; - const activatedStateID = states - .map((_state: any) => _state.clientID) - .includes(state.annotations.activatedStateID) ? - state.annotations.activatedStateID : - null; - return { ...state, player: { @@ -268,6 +279,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { frame: { data, filename, + hasRelatedContext, number, fetching: false, changeTime, @@ -275,12 +287,12 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, contextImage: { ...state.player.contextImage, - loaded: false, + ...(state.player.frame.number === number ? {} : { data: null }), }, }, annotations: { ...state.annotations, - activatedStateID, + activatedStateID: updateActivatedStateID(states, activatedStateID), states, zLayer: { min: minZ, @@ -332,11 +344,14 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { } case AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS: { const { states } = action.payload; + const { activatedStateID } = state.annotations; + return { ...state, annotations: { ...state.annotations, states, + activatedStateID: updateActivatedStateID(states, activatedStateID), saving: { ...state.annotations.saving, uploading: false, @@ -393,13 +408,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { appearanceCollapsed: !state.appearanceCollapsed, }; } - case AnnotationActionTypes.UPDATE_TAB_CONTENT_HEIGHT: { - const { tabContentHeight } = action.payload; - return { - ...state, - tabContentHeight, - }; - } case AnnotationActionTypes.COLLAPSE_OBJECT_ITEMS: { const { states, collapsed } = action.payload; @@ -960,21 +968,16 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { } case AnnotationActionTypes.REDO_ACTION_SUCCESS: case AnnotationActionTypes.UNDO_ACTION_SUCCESS: { + const { activatedStateID } = state.annotations; const { history, states, minZ, maxZ, } = action.payload; - const activatedStateID = states - .map((_state: any) => _state.clientID) - .includes(state.annotations.activatedStateID) ? - state.annotations.activatedStateID : - null; - return { ...state, annotations: { ...state.annotations, - activatedStateID, + activatedStateID: updateActivatedStateID(states, activatedStateID), states, history, zLayer: { @@ -986,18 +989,14 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.FETCH_ANNOTATIONS_SUCCESS: { + const { activatedStateID } = state.annotations; const { states, minZ, maxZ } = action.payload; - const activatedStateID = states - .map((_state: any) => _state.clientID) - .includes(state.annotations.activatedStateID) ? - state.annotations.activatedStateID : - null; return { ...state, annotations: { ...state.annotations, - activatedStateID, + activatedStateID: updateActivatedStateID(states, activatedStateID), states, zLayer: { min: minZ, @@ -1170,30 +1169,52 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { } case AnnotationActionTypes.HIDE_SHOW_CONTEXT_IMAGE: { const { hidden } = action.payload; - const { loaded, data } = state.player.contextImage; return { ...state, player: { ...state.player, contextImage: { - loaded, - data, + ...state.player.contextImage, hidden, }, }, }; } case AnnotationActionTypes.GET_CONTEXT_IMAGE: { - const { context, loaded } = action.payload; + return { + ...state, + player: { + ...state.player, + contextImage: { + ...state.player.contextImage, + fetching: true, + }, + }, + }; + } + case AnnotationActionTypes.GET_CONTEXT_IMAGE_SUCCESS: { + const { contextImageData } = action.payload; return { ...state, player: { ...state.player, contextImage: { - loaded, - data: context, - hidden: state.player.contextImage.hidden, + ...state.player.contextImage, + fetching: false, + data: contextImageData, + }, + }, + }; + } + case AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED: { + return { + ...state, + player: { + ...state.player, + contextImage: { + ...state.player.contextImage, + fetching: false, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 4ddeb301..7da382b8 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -4,7 +4,7 @@ import { MutableRefObject } from 'react'; import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d'; -import { Canvas, RectDrawingMethod } from 'cvat-canvas-wrapper'; +import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; import { KeyMap } from 'utils/mousetrap-react'; @@ -30,10 +30,13 @@ export interface ProjectsQuery { owner: string | null; name: string | null; status: string | null; - [key: string]: string | number | null | undefined; + [key: string]: string | boolean | number | null | undefined; } -export type Project = any; +export interface Project { + instance: any; + preview: string; +} export interface ProjectsState { initialized: boolean; @@ -70,10 +73,15 @@ export interface Task { } export interface TasksState { + importing: boolean; initialized: boolean; fetching: boolean; updating: boolean; hideEmpty: boolean; + moveTask: { + modalVisible: boolean; + taskId: number | null; + }; gettingQuery: TasksQuery; count: number; current: Task[]; @@ -98,6 +106,9 @@ export interface TasksState { status: string; error: string; }; + backups: { + [tid: number]: boolean; + }; }; } @@ -242,9 +253,12 @@ export interface NotificationsState { updating: null | ErrorState; dumping: null | ErrorState; loading: null | ErrorState; - exporting: null | ErrorState; + exportingAsDataset: null | ErrorState; deleting: null | ErrorState; creating: null | ErrorState; + exporting: null | ErrorState; + importing: null | ErrorState; + moving: null | ErrorState; }; formats: { fetching: null | ErrorState; @@ -269,6 +283,7 @@ export interface NotificationsState { saving: null | ErrorState; jobFetching: null | ErrorState; frameFetching: null | ErrorState; + contextImageFetching: null | ErrorState; changingLabelColor: null | ErrorState; updating: null | ErrorState; creating: null | ErrorState; @@ -309,6 +324,8 @@ export interface NotificationsState { messages: { tasks: { loadingDone: string; + importingDone: string; + movingDone: string; }; models: { inferenceDone: string; @@ -417,6 +434,7 @@ export interface AnnotationState { frame: { number: number; filename: string; + hasRelatedContext: boolean; data: any | null; fetching: boolean; delay: number; @@ -425,8 +443,8 @@ export interface AnnotationState { playing: boolean; frameAngles: number[]; contextImage: { - loaded: boolean; - data: string; + fetching: boolean; + data: string | null; hidden: boolean; }; }; @@ -434,6 +452,7 @@ export interface AnnotationState { activeInteractor?: Model | OpenCVTool; activeShapeType: ShapeType; activeRectDrawingMethod?: RectDrawingMethod; + activeCuboidDrawingMethod?: CuboidDrawingMethod; activeNumOfPoints?: number; activeLabelID: number; activeObjectType: ObjectType; @@ -478,7 +497,6 @@ export interface AnnotationState { submitReviewDialogVisible: boolean; sidebarCollapsed: boolean; appearanceCollapsed: boolean; - tabContentHeight: number; workspace: Workspace; predictor: PredictorState; aiToolsRef: MutableRefObject; @@ -538,6 +556,7 @@ export interface WorkspaceSettingsState { showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; intelligentPolygonCrop: boolean; + defaultApproxPolyAccuracy: number; } export interface ShapesSettingsState { diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 497117be..e09db0b2 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -42,9 +42,12 @@ const defaultState: NotificationsState = { updating: null, dumping: null, loading: null, - exporting: null, + exportingAsDataset: null, deleting: null, creating: null, + exporting: null, + importing: null, + moving: null, }, formats: { fetching: null, @@ -69,6 +72,7 @@ const defaultState: NotificationsState = { saving: null, jobFetching: null, frameFetching: null, + contextImageFetching: null, changingLabelColor: null, updating: null, creating: null, @@ -109,6 +113,8 @@ const defaultState: NotificationsState = { messages: { tasks: { loadingDone: '', + importingDone: '', + movingDone: '', }, models: { inferenceDone: '', @@ -310,7 +316,7 @@ export default function (state = defaultState, action: AnyAction): Notifications ...state.errors, tasks: { ...state.errors.tasks, - exporting: { + exportingAsDataset: { message: 'Could not export dataset for the ' + `task ${taskID}`, @@ -439,6 +445,49 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case TasksActionTypes.EXPORT_TASK_FAILED: { + return { + ...state, + errors: { + ...state.errors, + tasks: { + ...state.errors.tasks, + exporting: { + message: 'Could not export the task', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } + case TasksActionTypes.IMPORT_TASK_FAILED: { + return { + ...state, + errors: { + ...state.errors, + tasks: { + ...state.errors.tasks, + importing: { + message: 'Could not import the task', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } + case TasksActionTypes.IMPORT_TASK_SUCCESS: { + const taskID = action.payload.task.id; + return { + ...state, + messages: { + ...state.messages, + tasks: { + ...state.messages.tasks, + importingDone: `Task has been imported succesfully Open task`, + }, + }, + }; + } case ProjectsActionTypes.GET_PROJECTS_FAILED: { return { ...state, @@ -689,6 +738,21 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED: { + return { + ...state, + errors: { + ...state.errors, + annotation: { + ...state.errors.annotation, + contextImageFetching: { + message: 'Could not fetch context image from the server', + reason: action.payload.error, + }, + }, + }, + }; + } case AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED: { return { ...state, diff --git a/cvat-ui/src/reducers/projects-reducer.ts b/cvat-ui/src/reducers/projects-reducer.ts index d6a7c1ee..d8fba25d 100644 --- a/cvat-ui/src/reducers/projects-reducer.ts +++ b/cvat-ui/src/reducers/projects-reducer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -50,12 +50,19 @@ export default (state: ProjectsState = defaultState, action: AnyAction): Project current: [], }; case ProjectsActionTypes.GET_PROJECTS_SUCCESS: { + const combinedWithPreviews = action.payload.array.map( + (project: any, index: number): Project => ({ + instance: project, + preview: action.payload.previews[index], + }), + ); + return { ...state, initialized: true, fetching: false, count: action.payload.count, - current: action.payload.array, + current: combinedWithPreviews, }; } case ProjectsActionTypes.GET_PROJECTS_FAILED: { @@ -110,13 +117,11 @@ export default (state: ProjectsState = defaultState, action: AnyAction): Project return { ...state, current: state.current.map( - (project): Project => { - if (project.id === action.payload.project.id) { - return action.payload.project; - } - - return project; - }, + (project): Project => ({ + ...project, + instance: project.instance.id === action.payload.project.id ? + action.payload.project : project.instance, + }), ), }; } @@ -124,13 +129,11 @@ export default (state: ProjectsState = defaultState, action: AnyAction): Project return { ...state, current: state.current.map( - (project): Project => { - if (project.id === action.payload.project.id) { - return action.payload.project; - } - - return project; - }, + (project): Project => ({ + ...project, + instance: project.instance.id === action.payload.project.id ? + action.payload.project : project.instance, + }), ), }; } diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index b4d4793b..301ff9a7 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -10,7 +10,7 @@ import { SettingsActionTypes } from 'actions/settings-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions'; import { - SettingsState, GridColor, FrameSpeed, ColorBy, + SettingsState, GridColor, FrameSpeed, ColorBy, DimensionType, } from './interfaces'; const defaultState: SettingsState = { @@ -31,6 +31,7 @@ const defaultState: SettingsState = { showObjectsTextAlways: false, showAllInterpolationTracks: false, intelligentPolygonCrop: true, + defaultApproxPolyAccuracy: 9, }, player: { canvasBackgroundColor: '#ffffff', @@ -277,6 +278,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD: { + return { + ...state, + workspace: { + ...state.workspace, + defaultApproxPolyAccuracy: action.payload.approxPolyAccuracy, + }, + }; + } case SettingsActionTypes.SWITCH_SETTINGS_DIALOG: { return { ...state, @@ -299,6 +309,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { ...state.player, resetZoom: job && job.task.mode === 'annotation', }, + shapes: { + ...defaultState.shapes, + ...(job.task.dimension === DimensionType.DIM_3D ? + { + opacity: 40, + selectedOpacity: 60, + } : + {}), + }, }; } case AuthActionTypes.LOGOUT_SUCCESS: { diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index 3863f3b7..ed219f9d 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -6,10 +6,10 @@ import { BoundariesActions, BoundariesActionTypes } from 'actions/boundaries-act import { AuthActions, AuthActionTypes } from 'actions/auth-actions'; import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions'; import { KeyMap, KeyMapItem } from 'utils/mousetrap-react'; -import { ShortcutsState } from './interfaces'; +import { DimensionType, ShortcutsState } from './interfaces'; function formatShortcuts(shortcuts: KeyMapItem): string { - const list: string[] = shortcuts.sequences as string[]; + const list: string[] = shortcuts.displayedSequences || (shortcuts.sequences as string[]); return `[${list .map((shortcut: string): string => { let keys = shortcut.split('+'); @@ -27,12 +27,14 @@ const defaultKeyMap = ({ description: 'Open/hide the list of available shortcuts', sequences: ['f1'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_SETTINGS: { name: 'Show settings', description: 'Open/hide settings dialog', sequences: ['f2'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_ALL_LOCK: { @@ -40,84 +42,98 @@ const defaultKeyMap = ({ description: 'Change locked state for all objects in the side bar', sequences: ['t l'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_LOCK: { name: 'Lock/unlock an object', description: 'Change locked state for an active object', sequences: ['l'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_ALL_HIDDEN: { name: 'Hide/show all objects', description: 'Change hidden state for objects in the side bar', sequences: ['t h'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_HIDDEN: { name: 'Hide/show an object', description: 'Change hidden state for an active object', sequences: ['h'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_OCCLUDED: { name: 'Switch occluded', description: 'Change occluded property for an active object', sequences: ['q', '/'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_KEYFRAME: { name: 'Switch keyframe', description: 'Change keyframe property for an active track', sequences: ['k'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SWITCH_OUTSIDE: { name: 'Switch outside', description: 'Change outside property for an active track', sequences: ['o'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, DELETE_OBJECT: { name: 'Delete object', description: 'Delete an active object. Use shift to force delete of locked objects', sequences: ['del', 'shift+del'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, TO_BACKGROUND: { name: 'To background', description: 'Put an active object "farther" from the user (decrease z axis value)', sequences: ['-', '_'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, TO_FOREGROUND: { name: 'To foreground', description: 'Put an active object "closer" to the user (increase z axis value)', sequences: ['+', '='], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, COPY_SHAPE: { name: 'Copy shape', description: 'Copy shape to CVAT internal clipboard', sequences: ['ctrl+c'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PROPAGATE_OBJECT: { name: 'Propagate object', description: 'Make a copy of the object on the following frames', sequences: ['ctrl+b'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, NEXT_KEY_FRAME: { name: 'Next keyframe', description: 'Go to the next keyframe of an active track', sequences: ['r'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, PREV_KEY_FRAME: { name: 'Previous keyframe', description: 'Go to the previous keyframe of an active track', sequences: ['e'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, NEXT_ATTRIBUTE: { @@ -125,24 +141,28 @@ const defaultKeyMap = ({ description: 'Go to the next attribute', sequences: ['down'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PREVIOUS_ATTRIBUTE: { name: 'Previous attribute', description: 'Go to the previous attribute', sequences: ['up'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, NEXT_OBJECT: { name: 'Next object', description: 'Go to the next object', sequences: ['tab'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PREVIOUS_OBJECT: { name: 'Previous object', description: 'Go to the previous object', sequences: ['shift+tab'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PASTE_SHAPE: { @@ -150,6 +170,7 @@ const defaultKeyMap = ({ description: 'Paste a shape from internal CVAT clipboard', sequences: ['ctrl+v'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_DRAW_MODE: { name: 'Draw mode', @@ -157,54 +178,63 @@ const defaultKeyMap = ({ 'Repeat the latest procedure of drawing with the same parameters (shift to redraw an existing shape)', sequences: ['shift+n', 'n'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, OPEN_REVIEW_ISSUE: { name: 'Open an issue', description: 'Create a new issues in the review workspace', sequences: ['n'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SWITCH_MERGE_MODE: { name: 'Merge mode', description: 'Activate or deactivate mode to merging shapes', sequences: ['m'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SWITCH_SPLIT_MODE: { name: 'Split mode', description: 'Activate or deactivate mode to splitting shapes', sequences: ['alt+m'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SWITCH_GROUP_MODE: { name: 'Group mode', description: 'Activate or deactivate mode to grouping shapes', sequences: ['g'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, RESET_GROUP: { name: 'Reset group', description: 'Reset group for selected shapes (in group mode)', sequences: ['shift+g'], action: 'keyup', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, CANCEL: { name: 'Cancel', description: 'Cancel any active canvas mode', sequences: ['esc'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, CLOCKWISE_ROTATION: { name: 'Rotate clockwise', description: 'Change image angle (add 90 degrees)', sequences: ['ctrl+r'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, ANTICLOCKWISE_ROTATION: { name: 'Rotate anticlockwise', - description: 'Change image angle (substract 90 degrees)', + description: 'Change image angle (subtract 90 degrees)', sequences: ['ctrl+shift+r'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SAVE_JOB: { @@ -212,90 +242,176 @@ const defaultKeyMap = ({ description: 'Send all changes of annotations to the server', sequences: ['ctrl+s'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, UNDO: { name: 'Undo action', description: 'Cancel the latest action related with objects', sequences: ['ctrl+z'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, REDO: { name: 'Redo action', description: 'Cancel undo action', sequences: ['ctrl+shift+z', 'ctrl+y'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, NEXT_FRAME: { name: 'Next frame', description: 'Go to the next frame', sequences: ['f'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PREV_FRAME: { name: 'Previous frame', description: 'Go to the previous frame', sequences: ['d'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, FORWARD_FRAME: { name: 'Forward frame', description: 'Go forward with a step', sequences: ['v'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, BACKWARD_FRAME: { name: 'Backward frame', description: 'Go backward with a step', sequences: ['c'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SEARCH_FORWARD: { name: 'Search forward', description: 'Search the next frame that satisfies to the filters', sequences: ['right'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SEARCH_BACKWARD: { name: 'Search backward', description: 'Search the previous frame that satisfies to the filters', sequences: ['left'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, PLAY_PAUSE: { name: 'Play/pause', description: 'Start/stop automatic changing frames', sequences: ['space'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, FOCUS_INPUT_FRAME: { name: 'Focus input frame', description: 'Focus on the element to change the current frame', - sequences: ['~'], + sequences: ['`'], + displayedSequences: ['~'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, SWITCH_AUTOMATIC_BORDERING: { name: 'Switch automatic bordering', description: 'Switch automatic bordering for polygons and polylines during drawing/editing', sequences: ['ctrl'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, CHANGE_OBJECT_COLOR: { name: 'Change color', description: 'Set the next color for an activated shape', sequences: ['enter'], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }, TOGGLE_LAYOUT_GRID: { name: 'Toggle layout grid', description: 'The grid is used to UI development', sequences: ['ctrl+alt+enter'], action: 'keydown', + applicable: [DimensionType.DIM_2D], }, SWITCH_LABEL: { name: 'Switch label', description: 'Changes a label for an activated object or for the next drawn object if no objects are activated', sequences: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'].map((val: string): string => `ctrl+${val}`), action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], + }, + TILT_UP: { + name: 'Camera Roll Angle Up', + description: 'Increases camera roll angle', + sequences: ['shift+arrowup'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + TILT_DOWN: { + name: 'Camera Roll Angle Down', + description: 'Decreases camera roll angle', + sequences: ['shift+arrowdown'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + ROTATE_LEFT: { + name: 'Camera Pitch Angle Left', + description: 'Decreases camera pitch angle', + sequences: ['shift+arrowleft'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + ROTATE_RIGHT: { + name: 'Camera Pitch Angle Right', + description: 'Increases camera pitch angle', + sequences: ['shift+arrowright'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + MOVE_UP: { + name: 'Camera Move Up', + description: 'Move the camera up', + sequences: ['alt+u'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + MOVE_DOWN: { + name: 'Camera Move Down', + description: 'Move the camera down', + sequences: ['alt+o'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + MOVE_LEFT: { + name: 'Camera Move Left', + description: 'Move the camera left', + sequences: ['alt+j'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + MOVE_RIGHT: { + name: 'Camera Move Right', + description: 'Move the camera right', + sequences: ['alt+l'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + ZOOM_IN: { + name: 'Camera Zoom In', + description: 'Performs zoom in', + sequences: ['alt+i'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], + }, + ZOOM_OUT: { + name: 'Camera Zoom Out', + description: 'Performs zoom out', + sequences: ['alt+k'], + action: 'keydown', + applicable: [DimensionType.DIM_3D], }, } as any) as KeyMap; diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts index 2cc8db82..78236132 100644 --- a/cvat-ui/src/reducers/tasks-reducer.ts +++ b/cvat-ui/src/reducers/tasks-reducer.ts @@ -1,8 +1,9 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT import { AnyAction } from 'redux'; +import { omit } from 'lodash'; import { BoundariesActionTypes } from 'actions/boundaries-actions'; import { TasksActionTypes } from 'actions/tasks-actions'; import { AuthActionTypes } from 'actions/auth-actions'; @@ -14,6 +15,10 @@ const defaultState: TasksState = { fetching: false, updating: false, hideEmpty: false, + moveTask: { + modalVisible: false, + taskId: null, + }, count: 0, current: [], gettingQuery: { @@ -36,7 +41,9 @@ const defaultState: TasksState = { status: '', error: '', }, + backups: {}, }, + importing: false, }; export default (state: TasksState = defaultState, action: AnyAction): TasksState => { @@ -238,6 +245,49 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState }, }; } + case TasksActionTypes.EXPORT_TASK: { + const { taskID } = action.payload; + const { backups } = state.activities; + + return { + ...state, + activities: { + ...state.activities, + backups: { + ...backups, + ...Object.fromEntries([[taskID, true]]), + }, + }, + }; + } + case TasksActionTypes.EXPORT_TASK_FAILED: + case TasksActionTypes.EXPORT_TASK_SUCCESS: { + const { taskID } = action.payload; + const { backups } = state.activities; + + delete backups[taskID]; + + return { + ...state, + activities: { + ...state.activities, + backups: omit(backups, [taskID]), + }, + }; + } + case TasksActionTypes.IMPORT_TASK: { + return { + ...state, + importing: true, + }; + } + case TasksActionTypes.IMPORT_TASK_FAILED: + case TasksActionTypes.IMPORT_TASK_SUCCESS: { + return { + ...state, + importing: false, + }; + } case TasksActionTypes.CREATE_TASK: { return { ...state, @@ -351,6 +401,16 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState hideEmpty: action.payload.hideEmpty, }; } + case TasksActionTypes.SWITCH_MOVE_TASK_MODAL_VISIBLE: { + return { + ...state, + moveTask: { + ...state.moveTask, + modalVisible: action.payload.visible, + taskId: action.payload.taskId, + }, + }; + } case BoundariesActionTypes.RESET_AFTER_ERROR: case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState }; diff --git a/cvat-ui/src/utils/mousetrap-react.tsx b/cvat-ui/src/utils/mousetrap-react.tsx index e8452e3d..ee1f4d92 100644 --- a/cvat-ui/src/utils/mousetrap-react.tsx +++ b/cvat-ui/src/utils/mousetrap-react.tsx @@ -9,7 +9,9 @@ export interface KeyMapItem { name: string; description: string; sequences: string[]; + displayedSequences?: string[]; action: 'keydown' | 'keyup' | 'keypress'; + applicable: any[]; } export interface KeyMap { diff --git a/cvat-ui/src/utils/opencv-wrapper/histogram-equalization.ts b/cvat-ui/src/utils/opencv-wrapper/histogram-equalization.ts new file mode 100644 index 00000000..7bf33966 --- /dev/null +++ b/cvat-ui/src/utils/opencv-wrapper/histogram-equalization.ts @@ -0,0 +1,109 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ImageProcessing } from './opencv-interfaces'; + +export interface HistogramEqualization extends ImageProcessing{ + processImage: (src:ImageData, frameNumber: number)=>ImageData; +} + +interface HashedImage{ + frameNumber: number, + frameData: ImageData, + timestamp: number, +} + +export default class HistogramEqualizationImplementation implements HistogramEqualization { + private readonly bufferSize: number = 20; + private cv:any; + private histHash: HashedImage[]; + public currentProcessedImage: number | undefined; + + constructor(cv:any) { + this.cv = cv; + this.histHash = []; + } + + public processImage(src:ImageData, frameNumber: number) : ImageData { + const hashedFrame = this.hashedFrame(frameNumber); + if (!hashedFrame) { + const { cv } = this; + let matImage = null; + const RGBImage = new cv.Mat(); + const YUVImage = new cv.Mat(); + const RGBDist = new cv.Mat(); + const YUVDist = new cv.Mat(); + const RGBADist = new cv.Mat(); + let channels = new cv.MatVector(); + const equalizedY = new cv.Mat(); + try { + this.currentProcessedImage = frameNumber; + matImage = cv.matFromImageData(src); + cv.cvtColor(matImage, RGBImage, cv.COLOR_RGBA2RGB, 0); + cv.cvtColor(RGBImage, YUVImage, cv.COLOR_RGB2YUV, 0); + cv.split(YUVImage, channels); + const [Y, U, V] = [channels.get(0), channels.get(1), channels.get(2)]; + channels.delete(); + channels = null; + cv.equalizeHist(Y, equalizedY); + Y.delete(); + channels = new cv.MatVector(); + channels.push_back(equalizedY); equalizedY.delete(); + channels.push_back(U); U.delete(); + channels.push_back(V); V.delete(); + cv.merge(channels, YUVDist); + cv.cvtColor(YUVDist, RGBDist, cv.COLOR_YUV2RGB, 0); + cv.cvtColor(RGBDist, RGBADist, cv.COLOR_RGB2RGBA, 0); + const arr = new Uint8ClampedArray(RGBADist.data, RGBADist.cols, RGBADist.rows); + const imgData = new ImageData(arr, src.width, src.height); + this.hashFrame(imgData, frameNumber); + return imgData; + } catch (e) { + console.log('Histogram equalization error', e); + return src; + } finally { + if (matImage) matImage.delete(); + if (channels) channels.delete(); + RGBImage.delete(); + YUVImage.delete(); + RGBDist.delete(); + YUVDist.delete(); + RGBADist.delete(); + } + } else { + this.currentProcessedImage = frameNumber; + return hashedFrame; + } + } + + private hashedFrame(frameNumber: number): ImageData|null { + const hashed = this.histHash.find((_hashed) => _hashed.frameNumber === frameNumber); + if (hashed) { + hashed.timestamp = Date.now(); + } + return hashed?.frameData || null; + } + + private hashFrame(frameData:ImageData, frameNumber:number):void{ + if (this.histHash.length >= this.bufferSize) { + const leastRecentlyUsed = this.histHash[0]; + const currentTimestamp = Date.now(); + let diff = currentTimestamp - leastRecentlyUsed.timestamp; + let leastIndex = 0; + for (let i = 1; i < this.histHash.length; i++) { + const currentDiff = currentTimestamp - this.histHash[i].timestamp; + if (currentDiff > diff) { + diff = currentDiff; + leastIndex = i; + } + } + this.histHash.splice(leastIndex, 1); + } + this.histHash.push({ + frameData, + frameNumber, + timestamp: Date.now(), + }); + } +} diff --git a/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts b/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts index cd05eec5..8fca2301 100644 --- a/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts +++ b/cvat-ui/src/utils/opencv-wrapper/intelligent-scissors.ts @@ -92,7 +92,6 @@ export default class IntelligentScissorsImplementation implements IntelligentSci if (points.length > 1) { let matImage = null; const contour = new cv.Mat(); - const approx = new cv.Mat(); try { const [prev, cur] = points.slice(-2); @@ -123,11 +122,10 @@ export default class IntelligentScissorsImplementation implements IntelligentSci tool.applyImage(matImage); tool.buildMap(new cv.Point(prevX, prevY)); tool.getContour(new cv.Point(curX, curY), contour); - cv.approxPolyDP(contour, approx, 2, false); const pathSegment = []; - for (let row = 0; row < approx.rows; row++) { - pathSegment.push(approx.intAt(row, 0) + offsetX, approx.intAt(row, 1) + offsetY); + for (let row = 0; row < contour.rows; row++) { + pathSegment.push(contour.intAt(row, 0) + offsetX, contour.intAt(row, 1) + offsetY); } state.anchors[points.length - 1] = { point: cur, @@ -140,7 +138,6 @@ export default class IntelligentScissorsImplementation implements IntelligentSci } contour.delete(); - approx.delete(); } } else { state.path.push(...pointsToNumberArray(applyOffset(points.slice(-1), -offsetX, -offsetY))); diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-interfaces.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-interfaces.ts new file mode 100644 index 00000000..fd82fb28 --- /dev/null +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-interfaces.ts @@ -0,0 +1,8 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +export interface ImageProcessing { + processImage: (src: ImageData, frameNumber: number) => ImageData; + currentProcessedImage: number|undefined +} diff --git a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts index 458fb38f..cc18cecb 100644 --- a/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts +++ b/cvat-ui/src/utils/opencv-wrapper/opencv-wrapper.ts @@ -4,6 +4,7 @@ import getCore from 'cvat-core-wrapper'; import waitFor from '../wait-for'; +import HistogramEqualizationImplementation, { HistogramEqualization } from './histogram-equalization'; import IntelligentScissorsImplementation, { IntelligentScissors } from './intelligent-scissors'; @@ -14,6 +15,14 @@ export interface Segmentation { intelligentScissorsFactory: () => IntelligentScissors; } +export interface Contours { + approxPoly: (points: number[] | any, threshold: number, closed?: boolean) => number[][]; +} + +export interface ImgProc { + hist: () => HistogramEqualization +} + export class OpenCVWrapper { private initialized: boolean; private cv: any; @@ -42,15 +51,15 @@ export class OpenCVWrapper { const decoder = new TextDecoder('utf-8'); const reader = (body as ReadableStream).getReader(); - let recieved = false; + let received = false; let receivedLength = 0; let decodedScript = ''; - while (!recieved) { + while (!received) { // await in the loop is necessary here // eslint-disable-next-line const { done, value } = await reader.read(); - recieved = done; + received = done; if (value instanceof Uint8Array) { decodedScript += decoder.decode(value); @@ -80,6 +89,39 @@ export class OpenCVWrapper { return this.initialized; } + public get contours(): Contours { + if (!this.initialized) { + throw new Error('Need to initialize OpenCV first'); + } + + const { cv } = this; + return { + approxPoly: (points: number[] | number[][], threshold: number, closed = true): number[][] => { + const isArrayOfArrays = Array.isArray(points[0]); + if (points.length < 3) { + // one pair of coordinates [x, y], approximation not possible + return (isArrayOfArrays ? points : [points]) as number[][]; + } + const rows = isArrayOfArrays ? points.length : points.length / 2; + const cols = 2; + + const approx = new cv.Mat(); + const contour = cv.matFromArray(rows, cols, cv.CV_32FC1, points.flat()); + try { + cv.approxPolyDP(contour, approx, threshold, closed); // approx output type is CV_32F + const result = []; + for (let row = 0; row < approx.rows; row++) { + result.push([approx.floatAt(row, 0), approx.floatAt(row, 1)]); + } + return result; + } finally { + approx.delete(); + contour.delete(); + } + }, + }; + } + public get segmentation(): Segmentation { if (!this.initialized) { throw new Error('Need to initialize OpenCV first'); @@ -89,6 +131,15 @@ export class OpenCVWrapper { intelligentScissorsFactory: () => new IntelligentScissorsImplementation(this.cv), }; } + + public get imgproc(): ImgProc { + if (!this.initialized) { + throw new Error('Need to initialize OpenCV first'); + } + return { + hist: () => new HistogramEqualizationImplementation(this.cv), + }; + } } export default new OpenCVWrapper(); diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index 50c13cfe..f99c03cb 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -12,7 +12,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const Dotenv = require('dotenv-webpack'); const CopyPlugin = require('copy-webpack-plugin'); -module.exports = { +module.exports = (env) => ({ target: 'web', mode: 'production', devtool: 'source-map', @@ -30,6 +30,17 @@ module.exports = { inline: true, port: 3000, historyApiFallback: true, + proxy: [ + { + context: (param) => + param.match( + /\/api\/.*|git\/.*|opencv\/.*|analytics\/.*|static\/.*|admin(?:\/(.*))?.*|documentation\/.*|django-rq(?:\/(.*))?/gm, + ), + target: env && env.API_URL, + secure: false, + changeOrigin: true, + }, + ], }, resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'], @@ -134,4 +145,4 @@ module.exports = { ]), ], node: { fs: 'empty' }, -}; +}); diff --git a/cvat/__init__.py b/cvat/__init__.py index 2207f0e7..52ec08c4 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (1, 4, 0, 'final', 0) +VERSION = (1, 5, 0, 'final', 0) __version__ = get_version(VERSION) diff --git a/cvat/apps/authentication/auth.py b/cvat/apps/authentication/auth.py index 5e19efb7..5c1f8ea3 100644 --- a/cvat/apps/authentication/auth.py +++ b/cvat/apps/authentication/auth.py @@ -159,6 +159,10 @@ def is_comment_author(db_user, db_comment): has_rights = (db_comment.author == db_user) return has_rights +@rules.predicate +def is_cloud_storage_owner(db_user, db_storage): + return db_storage.owner == db_user + # AUTH PERMISSIONS RULES rules.add_perm('engine.role.user', has_user_role) rules.add_perm('engine.role.admin', has_admin_role) @@ -190,6 +194,9 @@ rules.add_perm('engine.issue.destroy', has_admin_role | is_issue_owner) rules.add_perm('engine.comment.change', has_admin_role | is_comment_author) +rules.add_perm('engine.cloudstorage.create', has_admin_role | has_user_role) +rules.add_perm('engine.cloudstorage.change', has_admin_role | is_cloud_storage_owner) + class AdminRolePermission(BasePermission): # pylint: disable=no-self-use def has_permission(self, request, view): @@ -329,3 +336,21 @@ class CommentChangePermission(BasePermission): def has_object_permission(self, request, view, obj): return request.user.has_perm('engine.comment.change', obj) +class CloudStorageAccessPermission(BasePermission): + # pylint: disable=no-self-use + def has_object_permission(self, request, view, obj): + return request.user.has_perm("engine.cloudstorage.change", obj) + +class CloudStorageChangePermission(BasePermission): + # pylint: disable=no-self-use + def has_object_permission(self, request, view, obj): + return request.user.has_perm("engine.cloudstorage.change", obj) + +class CloudStorageGetQuerySetMixin(object): + def get_queryset(self): + queryset = super().get_queryset() + user = self.request.user + if has_admin_role(user) or self.detail: + return queryset + else: + return queryset.filter(owner=user) diff --git a/cvat/apps/authentication/auth_basic.py b/cvat/apps/authentication/auth_basic.py index 4c4ab1bc..53551ad1 100644 --- a/cvat/apps/authentication/auth_basic.py +++ b/cvat/apps/authentication/auth_basic.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -15,7 +15,7 @@ def create_user(sender, instance, created, **kwargs): db_group = Group.objects.get(name=AUTH_ROLE.ADMIN) instance.groups.add(db_group) - # create and verify EmailAdress for superuser accounts + # create and verify EmailAddress for superuser accounts if allauth_settings.EMAIL_REQUIRED: EmailAddress.objects.get_or_create(user=instance, email=instance.email, primary=True, verified=True) diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index f8e7522a..0fbd6bf0 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -86,9 +86,6 @@ class AnnotationIR: drop_count += 1 else: break - # Need to leave the last shape if all shapes are outside - if drop_count == len(shapes): - drop_count -= 1 return shapes[drop_count:] @@ -103,8 +100,12 @@ class AnnotationIR: if scoped_shapes: if not scoped_shapes[0]['keyframe']: segment_shapes.insert(0, scoped_shapes[0]) - if not scoped_shapes[-1]['keyframe']: + if not scoped_shapes[-1]['keyframe'] and \ + scoped_shapes[-1]['outside']: segment_shapes.append(scoped_shapes[-1]) + elif stop + 1 < len(interpolated_shapes) and \ + interpolated_shapes[stop + 1]['outside']: + segment_shapes.append(interpolated_shapes[stop + 1]) # Should delete 'interpolation_shapes' and 'keyframe' keys because # Track and TrackedShape models don't expect these fields @@ -113,7 +114,8 @@ class AnnotationIR: shape.pop('keyframe', None) track['shapes'] = segment_shapes - track['frame'] = track['shapes'][0]['frame'] + if 0 < len(segment_shapes): + track['frame'] = track['shapes'][0]['frame'] return track def slice(self, start, stop): @@ -123,8 +125,13 @@ class AnnotationIR: for t in self.tags if self._is_shape_inside(t, start, stop)] splitted_data.shapes = [deepcopy(s) for s in self.shapes if self._is_shape_inside(s, start, stop)] - splitted_data.tracks = [self._slice_track(t, start, stop) - for t in self.tracks if self._is_track_inside(t, start, stop)] + splitted_tracks = [] + for t in self.tracks: + if self._is_track_inside(t, start, stop): + track = self._slice_track(t, start, stop) + if 0 < len(track['shapes']): + splitted_tracks.append(track) + splitted_data.tracks = splitted_tracks return splitted_data @@ -429,24 +436,6 @@ class TrackManager(ObjectManager): obj["interpolated_shapes"].append(last_interpolated_shape) obj["interpolated_shapes"].append(shape) - @staticmethod - def normalize_shape(shape): - points = list(shape["points"]) - if len(points) == 2: - points.extend(points) # duplicate points for single point case - points = np.asarray(points).reshape(-1, 2) - broken_line = geometry.LineString(points) - points = [] - for off in range(0, 100, 1): - p = broken_line.interpolate(off / 100, True) - points.append(p.x) - points.append(p.y) - - shape = copy(shape) - shape["points"] = points - - return shape - @staticmethod def get_interpolated_shapes(track, start_frame, end_frame): def copy_shape(source, frame, points=None): @@ -464,11 +453,7 @@ class TrackManager(ObjectManager): for frame in range(shape0["frame"] + 1, shape1["frame"]): offset = (frame - shape0["frame"]) / distance - points = None - if shape1["outside"]: - points = np.asarray(shape0["points"]) - else: - points = shape0["points"] + diff * offset + points = shape0["points"] + diff * offset shapes.append(copy_shape(shape0, frame, points.tolist())) @@ -690,11 +675,7 @@ class TrackManager(ObjectManager): distance = shape1["frame"] - shape0["frame"] for frame in range(shape0["frame"] + 1, shape1["frame"]): offset = (frame - shape0["frame"]) / distance - points = None - if shape1["outside"]: - points = np.asarray(shape0["points"]) - else: - points = interpolate_position(shape0, shape1, offset) + points = interpolate_position(shape0, shape1, offset) shapes.append(copy_shape(shape0, frame, points)) diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index b800e18c..82716123 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -11,7 +11,7 @@ from django.utils import timezone import datumaro.components.extractor as datumaro from cvat.apps.engine.frame_provider import FrameProvider -from cvat.apps.engine.models import AttributeType, ShapeType +from cvat.apps.engine.models import AttributeType, ShapeType, DimensionType, Image as Img from datumaro.util import cast from datumaro.util.image import ByteImage, Image @@ -20,6 +20,7 @@ from .annotation import AnnotationManager, TrackManager class TaskData: Attribute = namedtuple('Attribute', 'name, value') + Shape = namedtuple("Shape", 'id, label_id') # 3d LabeledShape = namedtuple( 'LabeledShape', 'type, frame, label, points, occluded, attributes, source, group, z_order') LabeledShape.__new__.__defaults__ = (0, 0) @@ -30,7 +31,8 @@ class TaskData: Tag = namedtuple('Tag', 'frame, label, attributes, source, group') Tag.__new__.__defaults__ = (0, ) Frame = namedtuple( - 'Frame', 'idx, frame, name, width, height, labeled_shapes, tags') + 'Frame', 'idx, id, frame, name, width, height, labeled_shapes, tags, shapes, labels') + Labels = namedtuple('Label', 'id, name, color') def __init__(self, annotation_ir, db_task, host='', create_callback=None): self._annotation_ir = annotation_ir @@ -122,6 +124,7 @@ class TaskData: } for frame in range(self._db_task.data.size)} else: self._frame_info = {self.rel_frame_id(db_image.frame): { + "id": db_image.id, "path": db_image.path, "width": db_image.width, "height": db_image.height, @@ -234,6 +237,12 @@ class TaskData: attributes=self._export_attributes(shape["attributes"]), ) + def _export_shape(self, shape): + return TaskData.Shape( + id=shape["id"], + label_id=shape["label_id"] + ) + def _export_tag(self, tag): return TaskData.Tag( frame=self.abs_frame_id(tag["frame"]), @@ -243,6 +252,14 @@ class TaskData: attributes=self._export_attributes(tag["attributes"]), ) + @staticmethod + def _export_label(label): + return TaskData.Labels( + id=label.id, + name=label.name, + color=label.color + ) + def group_by_frame(self, include_empty=False): frames = {} def get_frame(idx): @@ -251,12 +268,15 @@ class TaskData: if frame not in frames: frames[frame] = TaskData.Frame( idx=idx, + id=frame_info.get('id',0), frame=frame, name=frame_info['path'], height=frame_info["height"], width=frame_info["width"], labeled_shapes=[], tags=[], + shapes=[], + labels={} ) return frames[frame] @@ -265,6 +285,7 @@ class TaskData: get_frame(idx) anno_manager = AnnotationManager(self._annotation_ir) + shape_data = '' 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: @@ -278,8 +299,13 @@ class TaskData: exported_shape = self._export_tracked_shape(shape) else: exported_shape = self._export_labeled_shape(shape) - get_frame(shape['frame']).labeled_shapes.append( - exported_shape) + shape_data = self._export_shape(shape) + get_frame(shape['frame']).labeled_shapes.append(exported_shape) + if shape_data: + get_frame(shape['frame']).shapes.append(shape_data) + for label in self._label_mapping.values(): + label = self._export_label(label) + get_frame(shape['frame']).labels.update({label.id: label}) for tag in self._annotation_ir.tags: get_frame(tag['frame']).tags.append(self._export_tag(tag)) @@ -457,17 +483,31 @@ class TaskData: return None class CvatTaskDataExtractor(datumaro.SourceExtractor): - def __init__(self, task_data, include_images=False): + def __init__(self, task_data, include_images=False, format_type=None, dimension=DimensionType.DIM_2D): super().__init__() - self._categories = self._load_categories(task_data) - + self._categories, self._user = self._load_categories(task_data, dimension=dimension) + self._dimension = dimension + self._format_type = format_type dm_items = [] is_video = task_data.meta['task']['mode'] == 'interpolation' ext = '' if is_video: ext = FrameProvider.VIDEO_FRAME_EXT - if include_images: + + if dimension == DimensionType.DIM_3D: + def _make_image(image_id, **kwargs): + loader = osp.join( + task_data.db_task.data.get_upload_dirname(), kwargs['path']) + related_images = [] + image = Img.objects.get(id=image_id) + for i in image.related_files.all(): + path = osp.realpath(str(i.path)) + if osp.isfile(path): + related_images.append(path) + return loader, related_images + + elif include_images: frame_provider = FrameProvider(task_data.db_task.data) if is_video: # optimization for videos: use numpy arrays instead of bytes @@ -490,14 +530,36 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): 'path': frame_data.name + ext, 'size': (frame_data.height, frame_data.width), } - if include_images: + + if dimension == DimensionType.DIM_3D: + dm_image = _make_image(frame_data.id, **image_args) + elif include_images: dm_image = _make_image(frame_data.idx, **image_args) else: dm_image = Image(**image_args) dm_anno = self._read_cvat_anno(frame_data, task_data) - dm_item = datumaro.DatasetItem(id=osp.splitext(frame_data.name)[0], - annotations=dm_anno, image=dm_image, - attributes={'frame': frame_data.frame}) + + if dimension == DimensionType.DIM_2D: + dm_item = datumaro.DatasetItem(id=osp.splitext(frame_data.name)[0], + annotations=dm_anno, image=dm_image, + attributes={'frame': frame_data.frame}) + elif dimension == DimensionType.DIM_3D: + attributes = {'frame': frame_data.frame} + if format_type == "sly_pointcloud": + attributes["name"] = self._user["name"] + attributes["createdAt"] = self._user["createdAt"] + attributes["updatedAt"] = self._user["updatedAt"] + attributes["labels"] = [] + index = 0 + for _, label in task_data.meta['task']['labels']: + attributes["labels"].append({"label_id": index, "name": label["name"], "color": label["color"]}) + attributes["track_id"] = -1 + index += 1 + + dm_item = datumaro.DatasetItem(id=osp.split(frame_data.name)[-1].split('.')[0], + annotations=dm_anno, point_cloud=dm_image[0], related_images=dm_image[1], + attributes=attributes) + dm_items.append(dm_item) self._items = dm_items @@ -513,19 +575,25 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): return self._categories @staticmethod - def _load_categories(cvat_anno): + def _load_categories(cvat_anno, dimension): categories = {} label_categories = datumaro.LabelCategories(attributes=['occluded']) + user_info = {} + if dimension == DimensionType.DIM_3D: + user_info = {"name": cvat_anno.meta['task']['owner']['username'], + "createdAt": cvat_anno.meta['task']['created'], + "updatedAt": cvat_anno.meta['task']['updated']} for _, label in cvat_anno.meta['task']['labels']: label_categories.add(label['name']) for _, attr in label['attributes']: label_categories.attributes.add(attr['name']) + categories[datumaro.AnnotationType.label] = label_categories - return categories + return categories, user_info def _read_cvat_anno(self, cvat_frame_anno, task_data): item_anno = [] @@ -554,6 +622,9 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): raise Exception( "Failed to convert attribute '%s'='%s': %s" % (a_name, a_value, e)) + if self._format_type == "sly_pointcloud" and (a_desc.get('input_type') == 'select' or a_desc.get('input_type') == 'radio'): + dm_attr[f"{a_name}__values"] = a_desc["values"] + return dm_attr for tag_obj in cvat_frame_anno.tags: @@ -565,7 +636,11 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): attributes=anno_attr, group=anno_group) item_anno.append(anno) - for shape_obj in cvat_frame_anno.labeled_shapes: + shapes = [] + for shape in cvat_frame_anno.shapes: + shapes.append({"id": shape.id, "label_id": shape.label_id}) + + for index, shape_obj in enumerate(cvat_frame_anno.labeled_shapes): anno_group = shape_obj.group or 0 anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) @@ -594,7 +669,18 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): label=anno_label, attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.CUBOID: - continue # Datumaro does not support cuboids + if self._dimension == DimensionType.DIM_3D: + if self._format_type == "sly_pointcloud": + anno_id = shapes[index]["id"] + anno_attr["label_id"] = shapes[index]["label_id"] + else: + anno_id = index + position, rotation, scale = anno_points[0:3], anno_points[3:6], anno_points[6:9] + anno = datumaro.Cuboid3d(id=anno_id, position=position, rotation=rotation, scale=scale, + label=anno_label, attributes=anno_attr, group=anno_group + ) + else: + continue else: raise Exception("Unknown shape type '%s'" % shape_obj.type) @@ -624,11 +710,15 @@ def match_dm_item(item, task_data, root_hint=None): return frame_number def find_dataset_root(dm_dataset, task_data): - longest_path = max(dm_dataset, key=lambda x: len(Path(x.id).parts)).id + longest_path = max(dm_dataset, key=lambda x: len(Path(x.id).parts), + default=None) + if longest_path is None: + return None + longest_path = longest_path.id + longest_match = task_data.match_frame_fuzzy(longest_path) if longest_match is None: return None - longest_match = osp.dirname(task_data.frame_info[longest_match]['path']) prefix = longest_match[:-len(osp.dirname(longest_path)) or None] if prefix.endswith('/'): @@ -641,6 +731,7 @@ def import_dm_annotations(dm_dataset, task_data): datumaro.AnnotationType.polygon: ShapeType.POLYGON, datumaro.AnnotationType.polyline: ShapeType.POLYLINE, datumaro.AnnotationType.points: ShapeType.POINTS, + datumaro.AnnotationType.cuboid_3d: ShapeType.CUBOID } if len(dm_dataset) == 0: @@ -675,11 +766,17 @@ def import_dm_annotations(dm_dataset, task_data): if hasattr(ann, 'label') and ann.label is None: raise CvatImportError("annotation has no label") if ann.type in shapes: + if ann.type == datumaro.AnnotationType.cuboid_3d: + try: + ann.points = [*ann.position,*ann.rotation,*ann.scale,0,0,0,0,0,0,0] + except Exception as e: + ann.points = ann.points + ann.z_order = 0 task_data.add_shape(task_data.LabeledShape( type=shapes[ann.type], frame=frame_number, + points = ann.points, label=label_cat.items[ann.label].name, - points=ann.points, occluded=ann.attributes.get('occluded') == True, z_order=ann.z_order, group=group_map.get(ann.group, 0), @@ -698,4 +795,4 @@ def import_dm_annotations(dm_dataset, task_data): )) except Exception as e: raise CvatImportError("Image {}: can't import annotation " - "#{} ({}): {}".format(item.id, idx, ann.type.name, e)) \ No newline at end of file + "#{} ({}): {}".format(item.id, idx, ann.type.name, e)) diff --git a/cvat/apps/dataset_manager/formats/README.md b/cvat/apps/dataset_manager/formats/README.md deleted file mode 100644 index 3af8cbfd..00000000 --- a/cvat/apps/dataset_manager/formats/README.md +++ /dev/null @@ -1,1114 +0,0 @@ - - - - - - -# Dataset and annotation formats - -## Contents - -- [How to add a format](#how-to-add) -- [Format descriptions](#formats) - - [CVAT](#cvat) - - [Datumaro](#datumaro) - - [LabelMe](#labelme) - - [MOT](#mot) - - [MOTS](#mots) - - [COCO](#coco) - - [PASCAL VOC and mask](#voc) - - [YOLO](#yolo) - - [TF detection API](#tfrecord) - - [ImageNet](#imagenet) - - [CamVid](#camvid) - - [WIDER Face](#widerface) - - [VGGFace2](#vggface2) - - [Market-1501](#market1501) - - [ICDAR13/15](#icdar) - -## How to add a new annotation format support - -1. Add a python script to `dataset_manager/formats` -1. Add an import statement to [registry.py](./registry.py). -1. Implement some importers and exporters as the format requires. - -Each format is supported by an importer and exporter. - -It can be a function or a class decorated with -`importer` or `exporter` from [registry.py](./registry.py). Examples: - -```python -@importer(name="MyFormat", version="1.0", ext="ZIP") -def my_importer(file_object, task_data, **options): - ... - -@importer(name="MyFormat", version="2.0", ext="XML") -class my_importer(file_object, task_data, **options): - def __call__(self, file_object, task_data, **options): - ... - -@exporter(name="MyFormat", version="1.0", ext="ZIP"): -def my_exporter(file_object, task_data, **options): - ... -``` - -Each decorator defines format parameters such as: - -- _name_ - -- _version_ - -- _file extension_. For the `importer` it can be a comma-separated list. - These parameters are combined to produce a visible name. It can be - set explicitly by the `display_name` argument. - -Importer arguments: - -- _file_object_ - a file with annotations or dataset -- _task_data_ - an instance of `TaskData` class. - -Exporter arguments: - -- _file_object_ - a file for annotations or dataset - -- _task_data_ - an instance of `TaskData` class. - -- _options_ - format-specific options. `save_images` is the option to - distinguish if dataset or just annotations are requested. - -[`TaskData`](../bindings.py) provides many task properties and interfaces -to add and read task annotations. - -Public members: - -- **TaskData. Attribute** - class, `namedtuple('Attribute', 'name, value')` - -- **TaskData. LabeledShape** - class, `namedtuple('LabeledShape', 'type, frame, label, points, occluded, attributes, group, z_order')` - -- **TrackedShape** - `namedtuple('TrackedShape', 'type, points, occluded, frame, attributes, outside, keyframe, z_order')` - -- **Track** - class, `namedtuple('Track', 'label, group, shapes')` - -- **Tag** - class, `namedtuple('Tag', 'frame, label, attributes, group')` - -- **Frame** - class, `namedtuple('Frame', 'frame, name, width, height, labeled_shapes, tags')` - -- **TaskData. shapes** - property, an iterator over `LabeledShape` objects - -- **TaskData. tracks** - property, an iterator over `Track` objects - -- **TaskData. tags** - property, an iterator over `Tag` objects - -- **TaskData. meta** - property, a dictionary with task information - -- **TaskData. group_by_frame()** - method, returns - an iterator over `Frame` objects, which groups annotation objects by frame. - Note that `TrackedShape` s will be represented as `LabeledShape` s. - -- **TaskData. add_tag(tag)** - method, - tag should be an instance of the `Tag` class - -- **TaskData. add_shape(shape)** - method, - shape should be an instance of the `Shape` class - -- **TaskData. add_track(track)** - method, - track should be an instance of the `Track` class - -Sample exporter code: - -```python -... -# dump meta info if necessary -... -# iterate over all frames -for frame_annotation in task_data.group_by_frame(): - # get frame info - image_name = frame_annotation.name - image_width = frame_annotation.width - image_height = frame_annotation.height - # iterate over all shapes on the frame - for shape in frame_annotation.labeled_shapes: - label = shape.label - xtl = shape.points[0] - ytl = shape.points[1] - xbr = shape.points[2] - ybr = shape.points[3] - # iterate over shape attributes - for attr in shape.attributes: - attr_name = attr.name - attr_value = attr.value -... -# dump annotation code -file_object.write(...) -... -``` - -Sample importer code: - -```python -... -#read file_object -... -for parsed_shape in parsed_shapes: - shape = task_data.LabeledShape( - type="rectangle", - points=[0, 0, 100, 100], - occluded=False, - attributes=[], - label="car", - outside=False, - frame=99, - ) -task_data.add_shape(shape) -``` - -## Format specifications - -### CVAT - -This is the native CVAT annotation format. It supports all CVAT annotations -features, so it can be used to make data backups. - -- supported annotations: Rectangles, Polygons, Polylines, - Points, Cuboids, Tags, Tracks - -- attributes are supported - -- [Format specification](/cvat/apps/documentation/xml_format.md) - -#### CVAT for images export - -Downloaded file: a ZIP file of the following structure: - -```bash -taskname.zip/ -├── images/ -| ├── img1.png -| └── img2.jpg -└── annotations.xml -``` - -- tracks are split by frames - -#### CVAT for videos export - -Downloaded file: a ZIP file of the following structure: - -```bash -taskname.zip/ -├── images/ -| ├── frame_000000.png -| └── frame_000001.png -└── annotations.xml -``` - -- shapes are exported as single-frame tracks - -#### CVAT loader - -Uploaded file: an XML file or a ZIP file of the structures above - -### Datumaro format - -[Datumaro](https://github.com/openvinotoolkit/datumaro/) is a tool, which can -help with complex dataset and annotation transformations, format conversions, -dataset statistics, merging, custom formats etc. It is used as a provider -of dataset support in CVAT, so basically, everything possible in CVAT -is possible in Datumaro too, but Datumaro can offer dataset operations. - -- supported annotations: any 2D shapes, labels -- supported attributes: any - -### [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/) - -- [Format specification](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/devkit_doc.pdf) - -- supported annotations: - - - Rectangles (detection and layout tasks) - - Tags (action- and classification tasks) - - Polygons (segmentation task) - -- supported attributes: - - - `occluded` (both UI option and a separate attribute) - - `truncated` and `difficult` (should be defined for labels as `checkbox` -es) - - action attributes (import only, should be defined as `checkbox` -es) - - arbitrary attributes (in the `attributes` secion of XML files) - -#### Pascal VOC export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── JPEGImages/ -│   ├── .jpg -│   ├── .jpg -│   └── .jpg -├── Annotations/ -│   ├── .xml -│   ├── .xml -│   └── .xml -├── ImageSets/ -│   └── Main/ -│   └── default.txt -└── labelmap.txt - -# labelmap.txt -# label : color_rgb : 'body' parts : actions -background::: -aeroplane::: -bicycle::: -bird::: -``` - -#### Pascal VOC import - -Uploaded file: a zip archive of the structure declared above or the following: - -```bash -taskname.zip/ -├── .xml -├── .xml -└── .xml -``` - -It must be possible for CVAT to match the frame name and file name -from annotation `.xml` file (the `filename` tag, e. g. -`2008_004457.jpg` ). - -There are 2 options: - -1. full match between frame name and file name from annotation `.xml` - (in cases when task was created from images or image archive). - -1. match by frame number. File name should be `.jpg` - or `frame_000000.jpg`. It should be used when task was created from video. - -#### Segmentation mask export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── labelmap.txt # optional, required for non-VOC labels -├── ImageSets/ -│   └── Segmentation/ -│   └── default.txt # list of image names without extension -├── SegmentationClass/ # merged class masks -│   ├── image1.png -│   └── image2.png -└── SegmentationObject/ # merged instance masks - ├── image1.png - └── image2.png - -# labelmap.txt -# label : color (RGB) : 'body' parts : actions -background:0,128,0:: -aeroplane:10,10,128:: -bicycle:10,128,0:: -bird:0,108,128:: -boat:108,0,100:: -bottle:18,0,8:: -bus:12,28,0:: -``` - -Mask is a `png` image with 1 or 3 channels where each pixel -has own color which corresponds to a label. -Colors are generated following to Pascal VOC [algorithm](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/devkit_doc.html#sec:voclabelcolormap). -`(0, 0, 0)` is used for background by default. - -- supported shapes: Rectangles, Polygons - -#### Segmentation mask import - -Uploaded file: a zip archive of the following structure: - -```bash - taskname.zip/ - ├── labelmap.txt # optional, required for non-VOC labels - ├── ImageSets/ - │   └── Segmentation/ - │   └── .txt - ├── SegmentationClass/ - │   ├── image1.png - │   └── image2.png - └── SegmentationObject/ - ├── image1.png - └── image2.png -``` - -It is also possible to import grayscale (1-channel) PNG masks. -For grayscale masks provide a list of labels with the number of lines equal -to the maximum color index on images. The lines must be in the right order -so that line index is equal to the color index. Lines can have arbitrary, -but different, colors. If there are gaps in the used color -indices in the annotations, they must be filled with arbitrary dummy labels. -Example: - -``` -q:0,128,0:: # color index 0 -aeroplane:10,10,128:: # color index 1 -_dummy2:2,2,2:: # filler for color index 2 -_dummy3:3,3,3:: # filler for color index 3 -boat:108,0,100:: # color index 3 -... -_dummy198:198,198,198:: # filler for color index 198 -_dummy199:199,199,199:: # filler for color index 199 -... -the last label:12,28,0:: # color index 200 -``` - -- supported shapes: Polygons - -#### How to create a task from Pascal VOC dataset - -1. Download the Pascal Voc dataset (Can be downloaded from the - [PASCAL VOC website](http://host.robots.ox.ac.uk/pascal/VOC/)) - -1. Create a CVAT task with the following labels: - - ```bash - aeroplane bicycle bird boat bottle bus car cat chair cow diningtable - dog horse motorbike person pottedplant sheep sofa train tvmonitor - ``` - - You can add `~checkbox=difficult:false ~checkbox=truncated:false` - attributes for each label if you want to use them. - - Select interesting image files (See [Creating an annotation task](cvat/apps/documentation/user_guide.md#creating-an-annotation-task) guide for details) - -1. zip the corresponding annotation files - -1. click `Upload annotation` button, choose `Pascal VOC ZIP 1.1` - - and select the zip file with annotations from previous step. - It may take some time. - -### [YOLO](https://pjreddie.com/darknet/yolo/) - -- [Format specification](https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects) -- supported annotations: Rectangles - -#### YOLO export - -Downloaded file: a zip archive with following structure: - -```bash -archive.zip/ -├── obj.data -├── obj.names -├── obj__data -│   ├── image1.txt -│   └── image2.txt -└── train.txt # list of subset image paths - -# the only valid subsets are: train, valid -# train.txt and valid.txt: -obj__data/image1.jpg -obj__data/image2.jpg - -# obj.data: -classes = 3 # optional -names = obj.names -train = train.txt -valid = valid.txt # optional -backup = backup/ # optional - -# obj.names: -cat -dog -airplane - -# image_name.txt: -# label_id - id from obj.names -# cx, cy - relative coordinates of the bbox center -# rw, rh - relative size of the bbox -# label_id cx cy rw rh -1 0.3 0.8 0.1 0.3 -2 0.7 0.2 0.3 0.1 -``` - -Each annotation `*.txt` file has a name that corresponds to the name of -the image file (e. g. `frame_000001.txt` is the annotation -for the `frame_000001.jpg` image). -The `*.txt` file structure: each line describes label and bounding box -in the following format `label_id cx cy w h`. -`obj.names` contains the ordered list of label names. - -#### YOLO import - -Uploaded file: a zip archive of the same structure as above -It must be possible to match the CVAT frame (image name) -and annotation file name. There are 2 options: - -1. full match between image name and name of annotation `*.txt` file - (in cases when a task was created from images or archive of images). - -1. match by frame number (if CVAT cannot match by name). File name - should be in the following format `.jpg` . - It should be used when task was created from a video. - -#### How to create a task from YOLO formatted dataset (from VOC for example) - -1. Follow the official [guide](https://pjreddie.com/darknet/yolo/)(see Training YOLO on VOC section) - and prepare the YOLO formatted annotation files. - -1. Zip train images - -```bash -zip images.zip -j -@ < train.txt -``` - -1. Create a CVAT task with the following labels: - - ```bash - aeroplane bicycle bird boat bottle bus car cat chair cow diningtable dog - horse motorbike person pottedplant sheep sofa train tvmonitor - ``` - - Select images. zip as data. Most likely you should use `share` - functionality because size of images. zip is more than 500Mb. - See [Creating an annotation task](cvat/apps/documentation/user_guide.md#creating-an-annotation-task) - guide for details. - -1. Create `obj.names` with the following content: - - ```bash - aeroplane - bicycle - bird - boat - bottle - bus - car - cat - chair - cow - diningtable - dog - horse - motorbike - person - pottedplant - sheep - sofa - train - tvmonitor - ``` - -1. Zip all label files together (we need to add only label files that correspond to the train subset) - - ```bash - cat train.txt | while read p; do echo ${p%/*/*}/labels/${${p##*/}%%.*}.txt; done | zip labels.zip -j -@ obj.names - ``` - -1. Click `Upload annotation` button, choose `YOLO 1.1` and select the zip - - file with labels from the previous step. - -### [MS COCO Object Detection](http://cocodataset.org/#format-data) - -- [Format specification](http://cocodataset.org/#format-data) - -#### COCO export - -Downloaded file: a zip archive with following structure: - -```bash -archive.zip/ -├── images/ -│ ├── -│ ├── -│ └── ... -└── annotations/ -    └── instances_default.json -``` - -- supported annotations: Polygons, Rectangles -- supported attributes: - - `is_crowd` (checkbox or integer with values 0 and 1) - - specifies that the instance (an object group) should have an - RLE-encoded mask in the `segmentation` field. All the grouped shapes - are merged into a single mask, the largest one defines all - the object properties - - `score` (number) - the annotation `score` field - - arbitrary attributes - will be stored in the `attributes` annotation section - - -*Note*: there is also a [support for COCO keypoints over Datumaro](https://github.com/openvinotoolkit/cvat/issues/2910#issuecomment-726077582) - -1. Install [Datumaro](https://github.com/openvinotoolkit/datumaro) - `pip install datumaro` -1. Export the task in the `Datumaro` format, unzip -1. Export the Datumaro project in `coco` / `coco_person_keypoints` formats - `datum export -f coco -p path/to/project [-- --save-images]` - -This way, one can export CVAT points as single keypoints or -keypoint lists (without the `visibility` COCO flag). - -#### COCO import - -Uploaded file: a single unpacked `*.json` or a zip archive with the structure above (without images). - -- supported annotations: Polygons, Rectangles (if the `segmentation` field is empty) - -#### How to create a task from MS COCO dataset - -1. Download the [MS COCO dataset](http://cocodataset.org/#download). - - For example [2017 Val images](http://images.cocodataset.org/zips/val2017.zip) - and [2017 Train/Val annotations](http://images.cocodataset.org/annotations/annotations_trainval2017.zip). - -1. Create a CVAT task with the following labels: - - ```bash - person bicycle car motorcycle airplane bus train truck boat "traffic light" "fire hydrant" "stop sign" "parking meter" bench bird cat dog horse sheep cow elephant bear zebra giraffe backpack umbrella handbag tie suitcase frisbee skis snowboard "sports ball" kite "baseball bat" "baseball glove" skateboard surfboard "tennis racket" bottle "wine glass" cup fork knife spoon bowl banana apple sandwich orange broccoli carrot "hot dog" pizza donut cake chair couch "potted plant" bed "dining table" toilet tv laptop mouse remote keyboard "cell phone" microwave oven toaster sink refrigerator book clock vase scissors "teddy bear" "hair drier" toothbrush - ``` - -1. Select val2017.zip as data - (See [Creating an annotation task](cvat/apps/documentation/user_guide.md#creating-an-annotation-task) - guide for details) - -1. Unpack `annotations_trainval2017.zip` - -1. click `Upload annotation` button, - choose `COCO 1.1` and select `instances_val2017.json.json` - annotation file. It can take some time. - -### [TFRecord](https://www.tensorflow.org/tutorials/load_data/tf_records) - -TFRecord is a very flexible format, but we try to correspond the -format that used in -[TF object detection](https://github.com/tensorflow/models/tree/master/research/object_detection) -with minimal modifications. - -Used feature description: - -```python -image_feature_description = { - 'image/filename': tf.io.FixedLenFeature([], tf.string), - 'image/source_id': tf.io.FixedLenFeature([], tf.string), - 'image/height': tf.io.FixedLenFeature([], tf.int64), - 'image/width': tf.io.FixedLenFeature([], tf.int64), - # Object boxes and classes. - 'image/object/bbox/xmin': tf.io.VarLenFeature(tf.float32), - 'image/object/bbox/xmax': tf.io.VarLenFeature(tf.float32), - 'image/object/bbox/ymin': tf.io.VarLenFeature(tf.float32), - 'image/object/bbox/ymax': tf.io.VarLenFeature(tf.float32), - 'image/object/class/label': tf.io.VarLenFeature(tf.int64), - 'image/object/class/text': tf.io.VarLenFeature(tf.string), -} -``` - -#### TFRecord export - -Downloaded file: a zip archive with following structure: - -```bash -taskname.zip/ -├── default.tfrecord -└── label_map.pbtxt - -# label_map.pbtxt -item { - id: 1 - name: 'label_0' -} -item { - id: 2 - name: 'label_1' -} -... -``` - -- supported annotations: Rectangles, Polygons (as masks, manually over [Datumaro](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/user_manual.md)) - -How to export masks: -1. Export annotations in `Datumaro` format -1. Apply `polygons_to_masks` and `boxes_to_masks` transforms - ```bash - datum transform -t polygons_to_masks -p path/to/proj -o ptm - datum transform -t boxes_to_masks -p ptm -o btm - ``` -1. Export in the `TF Detection API` format - ```bash - datum export -f tf_detection_api -p btm [-- --save-images] - ``` - -#### TFRecord import - -Uploaded file: a zip archive of following structure: - -```bash -taskname.zip/ -└── .tfrecord -``` - -- supported annotations: Rectangles - -#### How to create a task from TFRecord dataset (from VOC2007 for example) - -1. Create `label_map.pbtxt` file with the following content: - -```js -item { - id: 1 - name: 'aeroplane' -} -item { - id: 2 - name: 'bicycle' -} -item { - id: 3 - name: 'bird' -} -item { - id: 4 - name: 'boat' -} -item { - id: 5 - name: 'bottle' -} -item { - id: 6 - name: 'bus' -} -item { - id: 7 - name: 'car' -} -item { - id: 8 - name: 'cat' -} -item { - id: 9 - name: 'chair' -} -item { - id: 10 - name: 'cow' -} -item { - id: 11 - name: 'diningtable' -} -item { - id: 12 - name: 'dog' -} -item { - id: 13 - name: 'horse' -} -item { - id: 14 - name: 'motorbike' -} -item { - id: 15 - name: 'person' -} -item { - id: 16 - name: 'pottedplant' -} -item { - id: 17 - name: 'sheep' -} -item { - id: 18 - name: 'sofa' -} -item { - id: 19 - name: 'train' -} -item { - id: 20 - name: 'tvmonitor' -} -``` - -1. Use [create_pascal_tf_record.py](https://github.com/tensorflow/models/blob/master/research/object_detection/dataset_tools/create_pascal_tf_record.py) - -to convert VOC2007 dataset to TFRecord format. -As example: - -```bash -python create_pascal_tf_record.py --data_dir --set train --year VOC2007 --output_path pascal.tfrecord --label_map_path label_map.pbtxt -``` - -1. Zip train images - - ```bash - cat /VOC2007/ImageSets/Main/train.txt | while read p; do echo /VOC2007/JPEGImages/${p}.jpg ; done | zip images.zip -j -@ - ``` - -1. Create a CVAT task with the following labels: - - ```bash - aeroplane bicycle bird boat bottle bus car cat chair cow diningtable dog horse motorbike person pottedplant sheep sofa train tvmonitor - ``` - - Select images. zip as data. - See [Creating an annotation task](cvat/apps/documentation/user_guide.md#creating-an-annotation-task) - guide for details. - -1. Zip `pascal.tfrecord` and `label_map.pbtxt` files together - - ```bash - zip anno.zip -j - ``` - -1. Click `Upload annotation` button, choose `TFRecord 1.0` and select the zip file - - with labels from the previous step. It may take some time. - -### [MOT sequence](https://arxiv.org/pdf/1906.04567.pdf) - -#### MOT export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── img1/ -| ├── image1.jpg -| └── image2.jpg -└── gt/ - ├── labels.txt - └── gt.txt - -# labels.txt -cat -dog -person -... - -# gt.txt -# frame_id, track_id, x, y, w, h, "not ignored", class_id, visibility, -1,1,1363,569,103,241,1,1,0.86014 -... - -``` - -- supported annotations: Rectangle shapes and tracks -- supported attributes: `visibility` (number), `ignored` (checkbox) - -#### MOT import - -Uploaded file: a zip archive of the structure above or: - -```bash -taskname.zip/ -├── labels.txt # optional, mandatory for non-official labels -└── gt.txt -``` - -- supported annotations: Rectangle tracks - -### [MOTS PNG](https://www.vision.rwth-aachen.de/page/mots) - -#### MOTS PNG export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -└── / - | images/ - | ├── image1.jpg - | └── image2.jpg - └── instances/ - ├── labels.txt - ├── image1.png - └── image2.png - -# labels.txt -cat -dog -person -... -``` - -- supported annotations: Rectangle and Polygon tracks - -#### MOTS PNG import - -Uploaded file: a zip archive of the structure above - -- supported annotations: Polygon tracks - -### [LabelMe](http://labelme.csail.mit.edu/Release3.0) - -#### LabelMe export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── img1.jpg -└── img1.xml -``` - -- supported annotations: Rectangles, Polygons (with attributes) - -#### LabelMe import - -Uploaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── Masks/ -| ├── img1_mask1.png -| └── img1_mask2.png -├── img1.xml -├── img2.xml -└── img3.xml -``` - -- supported annotations: Rectangles, Polygons, Masks (as polygons) - -### [ImageNet](http://www.image-net.org) - -#### ImageNet export - -Downloaded file: a zip archive of the following structure: - -```bash -# if we save images: -taskname.zip/ -├── label1/ -| ├── label1_image1.jpg -| └── label1_image2.jpg -└── label2/ - ├── label2_image1.jpg - ├── label2_image3.jpg - └── label2_image4.jpg - -# if we keep only annotation: -taskname.zip/ -├── .txt -└── synsets.txt - -``` - -- supported annotations: Labels - -#### ImageNet import - -Uploaded file: a zip archive of the structure above - -- supported annotations: Labels - -### [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) - -#### CamVid export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── labelmap.txt # optional, required for non-CamVid labels -├── / -| ├── image1.png -| └── image2.png -├── annot/ -| ├── image1.png -| └── image2.png -└── .txt - -# labelmap.txt -# color (RGB) label -0 0 0 Void -64 128 64 Animal -192 0 128 Archway -0 128 192 Bicyclist -0 128 64 Bridge -``` - -Mask is a `png` image with 1 or 3 channels where each pixel -has own color which corresponds to a label. -`(0, 0, 0)` is used for background by default. - -- supported annotations: Rectangles, Polygons - -#### CamVid import - -Uploaded file: a zip archive of the structure above - -- supported annotations: Polygons - -### [WIDER Face](http://shuoyang1213.me/WIDERFACE/) - -#### WIDER Face export - -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`, `pose`, `invalid` - - `occluded` (both the annotation property & an attribute) - -#### WIDER Face import - -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 export - -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 import - -Uploaded file: a zip archive of the structure above - -- supported annotations: Rectangles, Points (landmarks - groups of 5 points) - -### [Market-1501](https://www.aitribune.com/dataset/2018051063) - -#### Market-1501 export - -Downloaded file: a zip archive of the following structure: - -```bash -taskname.zip/ -├── bounding_box_/ -│ └── image_name_1.jpg -└── query - ├── image_name_2.jpg - └── image_name_3.jpg -# if we keep only annotation: -taskname.zip/ -└── images_.txt -# images_.txt -query/image_name_1.jpg -bounding_box_/image_name_2.jpg -bounding_box_/image_name_3.jpg -# image_name = 0001_c1s1_000015_00.jpg -0001 - person id -c1 - camera id (there are totally 6 cameras) -s1 - sequence -000015 - frame number in sequence -00 - means that this bounding box is the first one among the several -``` - -- supported annotations: Label `market-1501` with atrributes (`query`, `person_id`, `camera_id`) - -#### Market-1501 import - -Uploaded file: a zip archive of the structure above - -- supported annotations: Label `market-1501` with atrributes (`query`, `person_id`, `camera_id`) - -### [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) - -#### ICDAR13/15 export - -Downloaded file: a zip archive of the following structure: - -```bash -# word recognition task -taskname.zip/ -└── word_recognition/ - └── / - ├── images - | ├── word1.png - | └── word2.png - └── gt.txt -# text localization task -taskname.zip/ -└── text_localization/ - └── / - ├── images - | ├── img_1.png - | └── img_2.png - ├── gt_img_1.txt - └── gt_img_1.txt -#text segmentation task -taskname.zip/ -└── text_localization/ - └── / - ├── images - | ├── 1.png - | └── 2.png - ├── 1_GT.bmp - ├── 1_GT.txt - ├── 2_GT.bmp - └── 2_GT.txt -``` - -**Word recognition task**: - -- supported annotations: Label `icdar` with attribute `caption` - -**Text localization task**: - -- supported annotations: Rectangles and Polygons with label `icdar` - and attribute `text` - -**Text segmentation task**: - -- supported annotations: Rectangles and Polygons with label `icdar` - and attributes `index`, `text`, `color`, `center` - -#### ICDAR13/15 import - -Uploaded file: a zip archive of the structure above - -**Word recognition task**: - -- supported annotations: Label `icdar` with attribute `caption` - -**Text localization task**: - -- supported annotations: Rectangles and Polygons with label `icdar` - and attribute `text` - -**Text segmentation task**: - -- supported annotations: Rectangles and Polygons with label `icdar` - and attributes `index`, `text`, `color`, `center` diff --git a/cvat/apps/dataset_manager/formats/datumaro/__init__.py b/cvat/apps/dataset_manager/formats/datumaro/__init__.py index 3e5b1e6c..0f351f83 100644 --- a/cvat/apps/dataset_manager/formats/datumaro/__init__.py +++ b/cvat/apps/dataset_manager/formats/datumaro/__init__.py @@ -41,9 +41,11 @@ class DatumaroProjectExporter: 'height': frame['height'], }) - with open(osp.join(save_dir, 'config.json'), 'w') as config_file: + with open(osp.join(save_dir, 'config.json'), + 'w', encoding='utf-8') as config_file: json.dump(config, config_file) - with open(osp.join(save_dir, 'images_meta.json'), 'w') as images_file: + with open(osp.join(save_dir, 'images_meta.json'), + 'w', encoding='utf-8') as images_file: json.dump(images_meta, images_file) def _export(self, task_data, save_dir, save_images=False): diff --git a/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py b/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py index 9a7a9f06..359209cc 100644 --- a/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py +++ b/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2020 Intel Corporation +# Copyright (C) 2020-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -70,12 +69,14 @@ class cvat_rest_api_task_images(SourceExtractor): self._local_dir = local_dir self._cache_dir = osp.join(local_dir, 'images') - with open(osp.join(url, 'config.json'), 'r') as config_file: + with open(osp.join(url, 'config.json'), + 'r', encoding='utf-8') as config_file: config = json.load(config_file) config = Config(config, schema=CONFIG_SCHEMA) self._config = config - with open(osp.join(url, 'images_meta.json'), 'r') as images_file: + with open(osp.join(url, 'images_meta.json'), + 'r', encoding='utf-8') as images_file: images_meta = json.load(images_file) image_list = images_meta['images'] diff --git a/cvat/apps/dataset_manager/formats/icdar.py b/cvat/apps/dataset_manager/formats/icdar.py index 8df8f3e4..03eda245 100644 --- a/cvat/apps/dataset_manager/formats/icdar.py +++ b/cvat/apps/dataset_manager/formats/icdar.py @@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory from datumaro.components.dataset import Dataset from datumaro.components.extractor import (AnnotationType, Caption, Label, - LabelCategories, Transform) + LabelCategories, ItemTransform) from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, import_dm_annotations) @@ -16,7 +16,7 @@ from cvat.apps.dataset_manager.util import make_zip_archive from .registry import dm_env, exporter, importer -class AddLabelToAnns(Transform): +class AddLabelToAnns(ItemTransform): def __init__(self, extractor, label): super().__init__(extractor) @@ -39,7 +39,7 @@ class AddLabelToAnns(Transform): ann.label = self._label return item.wrap(annotations=annotations) -class CaptionToLabel(Transform): +class CaptionToLabel(ItemTransform): def __init__(self, extractor, label): super().__init__(extractor) @@ -64,7 +64,7 @@ class CaptionToLabel(Transform): annotations.remove(ann) return item.wrap(annotations=annotations) -class LabelToCaption(Transform): +class LabelToCaption(ItemTransform): def transform_item(self, item): annotations = item.annotations anns = [p for p in annotations diff --git a/cvat/apps/dataset_manager/formats/market1501.py b/cvat/apps/dataset_manager/formats/market1501.py index 1b6e24f4..f94d65dc 100644 --- a/cvat/apps/dataset_manager/formats/market1501.py +++ b/cvat/apps/dataset_manager/formats/market1501.py @@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory from datumaro.components.dataset import Dataset from datumaro.components.extractor import (AnnotationType, Label, - LabelCategories, Transform) + LabelCategories, ItemTransform) from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, import_dm_annotations) @@ -15,7 +15,7 @@ from cvat.apps.dataset_manager.util import make_zip_archive from .registry import dm_env, exporter, importer -class AttrToLabelAttr(Transform): +class AttrToLabelAttr(ItemTransform): def __init__(self, extractor, label): super().__init__(extractor) @@ -31,13 +31,14 @@ class AttrToLabelAttr(Transform): return self._categories def transform_item(self, item): - annotations = item.annotations + annotations = list(item.annotations) + attributes = item.attributes if item.attributes: annotations.append(Label(self._label, attributes=item.attributes)) - item.attributes = {} - return item.wrap(annotations=annotations) + attributes = {} + return item.wrap(annotations=annotations, attributes=attributes) -class LabelAttrToAttr(Transform): +class LabelAttrToAttr(ItemTransform): def __init__(self, extractor, label): super().__init__(extractor) @@ -46,8 +47,8 @@ class LabelAttrToAttr(Transform): self._label = label_cat.find(label)[0] def transform_item(self, item): - annotations = item.annotations - attributes = item.attributes + annotations = list(item.annotations) + attributes = dict(item.attributes) if self._label != None: labels = [ann for ann in annotations if ann.type == AnnotationType.label \ diff --git a/cvat/apps/dataset_manager/formats/mots.py b/cvat/apps/dataset_manager/formats/mots.py index 22b9dd08..b8b562ec 100644 --- a/cvat/apps/dataset_manager/formats/mots.py +++ b/cvat/apps/dataset_manager/formats/mots.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory from datumaro.components.dataset import Dataset -from datumaro.components.extractor import AnnotationType, Transform +from datumaro.components.extractor import AnnotationType, ItemTransform from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, @@ -15,7 +15,7 @@ from cvat.apps.dataset_manager.util import make_zip_archive from .registry import dm_env, exporter, importer -class KeepTracks(Transform): +class KeepTracks(ItemTransform): def transform_item(self, item): return item.wrap(annotations=[a for a in item.annotations if 'track_id' in a.attributes]) diff --git a/cvat/apps/dataset_manager/formats/pointcloud.py b/cvat/apps/dataset_manager/formats/pointcloud.py new file mode 100644 index 00000000..1fc31e4a --- /dev/null +++ b/cvat/apps/dataset_manager/formats/pointcloud.py @@ -0,0 +1,42 @@ +# 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 cvat.apps.engine.models import DimensionType + +from .registry import dm_env, exporter, importer + + +@exporter(name='Sly Point Cloud Format', ext='ZIP', version='1.0', dimension=DimensionType.DIM_3D) +def _export_images(dst_file, task_data, save_images=False): + + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images, format_type='sly_pointcloud', dimension=DimensionType.DIM_3D), env=dm_env) + + with TemporaryDirectory() as temp_dir: + dataset.export(temp_dir, 'sly_pointcloud', save_images=save_images) + + make_zip_archive(temp_dir, dst_file) + + +@importer(name='Sly Point Cloud Format', ext='ZIP', version='1.0', dimension=DimensionType.DIM_3D) +def _import(src_file, task_data): + + if zipfile.is_zipfile(src_file): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + + dataset = Dataset.import_from(tmp_dir, 'sly_pointcloud', env=dm_env) + import_dm_annotations(dataset, task_data) + else: + dataset = Dataset.import_from(src_file.name, + 'sly_pointcloud', env=dm_env) + 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 e6624854..959127ca 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -82,6 +82,14 @@ def make_importer(name): def make_exporter(name): return EXPORT_FORMATS[name]() + +# Add checking for TF availability to avoid CVAT sever instance / interpreter +# crash and provide a meaningful diagnistic message in the case of AVX +# instructions unavailability: +# https://github.com/openvinotoolkit/cvat/pull/1567 +import datumaro.util.tf_util as TF +TF.enable_tf_check = True + # pylint: disable=unused-import import cvat.apps.dataset_manager.formats.coco import cvat.apps.dataset_manager.formats.cvat @@ -99,3 +107,6 @@ import cvat.apps.dataset_manager.formats.widerface import cvat.apps.dataset_manager.formats.vggface2 import cvat.apps.dataset_manager.formats.market1501 import cvat.apps.dataset_manager.formats.icdar +import cvat.apps.dataset_manager.formats.velodynepoint +import cvat.apps.dataset_manager.formats.pointcloud + diff --git a/cvat/apps/dataset_manager/formats/velodynepoint.py b/cvat/apps/dataset_manager/formats/velodynepoint.py new file mode 100644 index 00000000..12eafbce --- /dev/null +++ b/cvat/apps/dataset_manager/formats/velodynepoint.py @@ -0,0 +1,45 @@ +# 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 .registry import dm_env + +from cvat.apps.dataset_manager.util import make_zip_archive +from cvat.apps.engine.models import DimensionType + +from .registry import exporter, importer + + +@exporter(name='Kitti Raw Format', ext='ZIP', version='1.0', dimension=DimensionType.DIM_3D) +def _export_images(dst_file, task_data, save_images=False): + + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images, format_type="kitti_raw", dimension=DimensionType.DIM_3D), env=dm_env) + + with TemporaryDirectory() as temp_dir: + dataset.export(temp_dir, 'kitti_raw', save_images=save_images, reindex=True) + + make_zip_archive(temp_dir, dst_file) + + +@importer(name='Kitti Raw Format', ext='ZIP', version='1.0', dimension=DimensionType.DIM_3D) +def _import(src_file, task_data): + if zipfile.is_zipfile(src_file): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + + dataset = Dataset.import_from( + tmp_dir, 'kitti_raw', env=dm_env) + import_dm_annotations(dataset, task_data) + else: + + dataset = Dataset.import_from( + src_file.name, 'kitti_raw', 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 index 528f52c7..0ae6d9a9 100644 --- a/cvat/apps/dataset_manager/formats/vggface2.py +++ b/cvat/apps/dataset_manager/formats/vggface2.py @@ -29,4 +29,5 @@ def _import(src_file, task_data): zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'vgg_face2', env=dm_env) + dataset.transform('rename', r"|([^/]+/)?(.+)|\2|") import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/task.py b/cvat/apps/dataset_manager/task.py index 039548d3..f7422296 100644 --- a/cvat/apps/dataset_manager/task.py +++ b/cvat/apps/dataset_manager/task.py @@ -1,5 +1,5 @@ -# Copyright (C) 2019-2020 Intel Corporation +# Copyright (C) 2019-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -346,7 +346,7 @@ class JobAnnotation: labeledtrack_set = labeledtrack_set.filter(pk__in=labeledtrack_ids) # It is not important for us that data had some "invalid" objects - # which were skipped (not acutally deleted). The main idea is to + # which were skipped (not actually deleted). The main idea is to # say that all requested objects are absent in DB after the method. self.ir_data.tags = data['tags'] self.ir_data.shapes = data['shapes'] diff --git a/cvat/apps/dataset_manager/tests/assets/annotations.json b/cvat/apps/dataset_manager/tests/assets/annotations.json new file mode 100644 index 00000000..2a7a0e21 --- /dev/null +++ b/cvat/apps/dataset_manager/tests/assets/annotations.json @@ -0,0 +1,1200 @@ +{ + "CVAT for images 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [30.99, 31.37, 47.51, 26.94, 50.21, 22.73], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 2, + "points": [59.82, 31.26], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 2, + "source": "manual", + "shapes": [ + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 52.48, + 37.95, + 52.48, + 44.43, + 67.38, + 37.85, + 67.38, + 44.43, + 68.87, + 37.29, + 68.87, + 43.67, + 53.97, + 37.29, + 53.97, + 43.67 + ], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 1, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.073, 78.83, 16.14], + "frame": 2, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CamVid 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45, 47.43, 38.21], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "COCO 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [7.29, 8.58, 18.45, 19.22], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [27.03, 26.07, 63.94, 37.87, 18.34, 34.97], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ICDAR Localization 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 8.79, 20.5, 15.69], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45, 47.43, 38.21], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ICDAR Recognition 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "ICDAR Segmentation 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 12.1, 25.6, 21.9], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ImageNet 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "LabelMe 3.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 8.79, 20.5, 15.69], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Market-1501 1.0": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "MOT 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.5, 27.29, 23.3, 33.49], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "MOTS PNG 1.0": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [19.4, 19.2, 41.7, 22.0, 38.8, 29.5, 21.5, 29.3], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "PASCAL VOC 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Segmentation mask 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [11.9, 21.5, 35.2, 21.9, 33.6, 31.9, 12.4, 30.47], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "TFRecord 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [16.5, 17.2, 38.89, 25.63], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "YOLO 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [8.3, 9.1, 19.2, 14.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "WiderFace 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [7.55, 9.75, 16.44, 15.85], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [3.55, 27.75, 11.33, 33.71], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "VGGFace2 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 0, + "points": [28.05, 18.0, 36.65, 17.7, 36.85, 23.7, 26.95, 23.2, 30.35, 28.9], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [57.15, 20.9, 74.25, 32.0], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Datumaro 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for images 1.1 many jobs": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 11, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1 many jobs": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 10, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 slice track": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 0, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for images 1.1 merge": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.95, 8.09, 18.65, 13.39], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [11.545, 11.7, 19.44, 17.4], + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 6, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 3, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 9, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for images 1.1 tag": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "CVAT for video 1.1 slice track keyframe": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 0, + "outside": false, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "empty annotation": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [] + }, + "CVAT for images 1.1 different types": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [11.545, 11.7, 19.44, 17.4], + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 6, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 polygon": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 polygon": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": true, + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 2, + "outside": false, + "keyframe": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 attributes in tracks": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "outside": true, + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 1, + "outside": false, + "attributes": [], + "keyframe": true + } + ], + "attributes": [] + } + ] + }, + "WiderFace 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [7.55, 9.75, 16.44, 15.85], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [3.55, 27.75, 11.33, 33.71], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + } +} diff --git a/cvat/apps/dataset_manager/tests/assets/tasks.json b/cvat/apps/dataset_manager/tests/assets/tasks.json new file mode 100644 index 00000000..09e2d866 --- /dev/null +++ b/cvat/apps/dataset_manager/tests/assets/tasks.json @@ -0,0 +1,298 @@ +{ + "main": { + "name": "main task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [ + { + "name": "select_name", + "mutable": false, + "input_type": "select", + "default_value": "bmw", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "radio_name", + "mutable": false, + "input_type": "radio", + "default_value": "x1", + "values": ["x1", "x2", "x3"] + }, + { + "name": "check_name", + "mutable": true, + "input_type": "checkbox", + "default_value": "false", + "values": ["false"] + }, + { + "name": "text_name", + "mutable": false, + "input_type": "text", + "default_value": "qwerty", + "values": ["qwerty"] + }, + { + "name": "number_name", + "mutable": false, + "input_type": "number", + "default_value": "-4", + "values": ["-4", "4", "1"] + } + ] + }, + { + "name": "person", + "color": "#c06060", + "attributes": [] + } + ] + }, + "icdar_localization_and_recognition": { + "name": "icdar localization/recogntion task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "icdar", + "attributes": [ + { + "name": "text", + "mutable": false, + "input_type": "text", + "values": ["word_1", "word_2", "word_3"] + } + ] + } + ] + }, + "icdar_segmentation": { + "name": "icdar segmentation task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "icdar", + "attributes": [ + { + "name": "text", + "mutable": false, + "input_type": "text", + "values": ["word_1", "word_2", "word_3"] + }, + { + "name": "index", + "mutable": false, + "input_type": "number", + "values": ["0", "1", "2"] + }, + { + "name": "color", + "mutable": false, + "input_type": "text", + "values": ["100 110 240", "10 15 20", "120 128 64"] + }, + { + "name": "center", + "mutable": false, + "input_type": "text", + "values": ["1 2", "2 4", "10 45"] + } + ] + } + ] + }, + "market1501": { + "name": "market1501 task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "market-1501", + "attributes": [ + { + "name": "query", + "mutable": false, + "input_type": "select", + "values": ["True", "False"] + }, + { + "name": "camera_id", + "mutable": false, + "input_type": "number", + "values": ["1", "5", "2"] + }, + { + "name": "person_id", + "mutable": false, + "input_type": "number", + "values": ["1", "6", "1"] + } + ] + } + ] + }, + "wrong_checkbox_value": { + "name": "wrong checkbox value task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [ + { + "name": "select_name", + "mutable": false, + "input_type": "select", + "default_value": "bmw", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "radio_name", + "mutable": false, + "input_type": "radio", + "default_value": "x1", + "values": ["x1", "x2", "x3"] + }, + { + "name": "check_name", + "mutable": true, + "input_type": "checkbox", + "default_value": "false", + "values": ["false"] + }, + { + "name": "text_name", + "mutable": false, + "input_type": "text", + "default_value": "qwerty", + "values": ["qwerty"] + }, + { + "name": "number_name", + "mutable": false, + "input_type": "number", + "default_value": "-4", + "values": ["-4", "4", "1"] + } + ] + }, + { + "name": "person", + "color": "#c06060", + "attributes": [] + } + ] + }, + "no attributes": { + "name": "no attributes", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "many jobs": { + "name": "many jobs", + "overlap": 0, + "segment_size": 5, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "change overlap and segment size": { + "name": "change overlap and segment size", + "overlap": 3, + "segment_size": 6, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "widerface with all attributes": { + "name": "widerface task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "face", + "attributes": [ + { + "name": "blur", + "mutable": false, + "input_type": "select", + "values": ["0", "1", "2"] + }, + { + "name": "expression", + "mutable": false, + "input_type": "select", + "values": ["0", "1"] + }, + { + "name": "illumination", + "mutable": false, + "input_type": "select", + "values": ["0", "1"] + }, + { + "name": "pose", + "mutable": false, + "input_type": "select", + "values": ["0", "1"] + }, + { + "name": "invalid", + "mutable": false, + "input_type": "select", + "values": ["0", "1"] + } + ] + } + ] + }, + "many jobs": { + "name": "many jobs", + "overlap": 0, + "segment_size": 5, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + } +} diff --git a/cvat/apps/dataset_manager/tests/test_annotation.py b/cvat/apps/dataset_manager/tests/test_annotation.py index 4f4dc84c..b9bbd6f0 100644 --- a/cvat/apps/dataset_manager/tests/test_annotation.py +++ b/cvat/apps/dataset_manager/tests/test_annotation.py @@ -165,4 +165,141 @@ class TrackManagerTest(TestCase): ] } - self._check_interpolation(track) \ No newline at end of file + self._check_interpolation(track) + + def test_outside_bbox_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "source": "manual", + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "points": [3.0, 4.0, 5.0, 6.0], + "type": "rectangle", + "occluded": False, + "outside": True, + "attributes": [], + }, + { + "frame": 4, + "points": [5.0, 6.0, 7.0, 8.0], + "type": "rectangle", + "occluded": False, + "outside": True, + "attributes": [] + } + ] + } + + expected_shapes = [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [], + "keyframe": True + }, + { + "frame": 1, + "points": [2.0, 3.0, 4.0, 5.0], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [], + "keyframe": False + }, + { + "frame": 2, + "points": [3.0, 4.0, 5.0, 6.0], + "type": "rectangle", + "occluded": False, + "outside": True, + "attributes": [], + "keyframe": True + }, + { + "frame": 4, + "points": [5.0, 6.0, 7.0, 8.0], + "type": "rectangle", + "occluded": False, + "outside": True, + "attributes": [], + "keyframe": True + } + ] + + interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 5) + self.assertEqual(expected_shapes, interpolated_shapes) + + def test_outside_polygon_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "source": "manual", + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + "type": "polygon", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "points": [3.0, 4.0, 5.0, 6.0, 7.0, 8.0], + "type": "polygon", + "occluded": False, + "outside": True, + "attributes": [] + } + ] + } + + expected_shapes = [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + "type": "polygon", + "occluded": False, + "outside": False, + "attributes": [], + "keyframe": True + }, + { + "frame": 1, + "points": [2.0, 3.0, 4.0, 5.0, 6.0, 7.0], + "type": "polygon", + "occluded": False, + "outside": False, + "attributes": [], + "keyframe": False + }, + { + "frame": 2, + "points": [3.0, 4.0, 5.0, 6.0, 7.0, 8.0], + "type": "polygon", + "occluded": False, + "outside": True, + "attributes": [], + "keyframe": True + } + ] + + interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 3) + self.assertEqual(expected_shapes, interpolated_shapes) diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index ddae97cf..8f7edd2f 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -288,6 +288,9 @@ class TaskExportTest(_DbTestBase): 'ICDAR Recognition 1.0', 'ICDAR Localization 1.0', 'ICDAR Segmentation 1.0', + 'Kitti Raw Format 1.0', + 'Sly Point Cloud Format 1.0' + }) def test_import_formats_query(self): @@ -312,6 +315,8 @@ class TaskExportTest(_DbTestBase): 'ICDAR Recognition 1.0', 'ICDAR Localization 1.0', 'ICDAR Segmentation 1.0', + 'Kitti Raw Format 1.0', + 'Sly Point Cloud Format 1.0' }) def test_exports(self): @@ -910,4 +915,4 @@ class TaskAnnotationsImportTest(_DbTestBase): if not f.ENABLED: self.skipTest("Format is disabled") - self._test_can_import_annotations(task, format_name) \ No newline at end of file + self._test_can_import_annotations(task, format_name) diff --git a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py new file mode 100644 index 00000000..2d69aee8 --- /dev/null +++ b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py @@ -0,0 +1,1149 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import copy +import json +import os.path as osp +import os +import av +import numpy as np +import random +import xml.etree.ElementTree as ET +import zipfile +from io import BytesIO +import itertools + +from datumaro.components.dataset import Dataset +from datumaro.util.test_utils import compare_datasets, TestDir +from django.contrib.auth.models import Group, User +from PIL import Image +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +import cvat.apps.dataset_manager as dm +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, TaskData +from cvat.apps.dataset_manager.task import TaskAnnotation +from cvat.apps.engine.models import Task + +tasks_path = osp.join(osp.dirname(__file__), 'assets', 'tasks.json') +with open(tasks_path) as file: + tasks = json.load(file) + +annotation_path = osp.join(osp.dirname(__file__), 'assets', 'annotations.json') +with open(annotation_path) as file: + annotations = json.load(file) + + +def generate_image_file(filename, size=(100, 50)): + f = BytesIO() + image = Image.new('RGB', size=size) + image.save(f, 'jpeg') + f.name = filename + f.seek(0) + return f + + +def generate_video_file(filename, width=1280, height=720, duration=1, fps=25, codec_name='mpeg4'): + f = BytesIO() + total_frames = duration * fps + file_ext = os.path.splitext(filename)[1][1:] + container = av.open(f, mode='w', format=file_ext) + + stream = container.add_stream(codec_name=codec_name, rate=fps) + stream.width = width + stream.height = height + stream.pix_fmt = 'yuv420p' + + for frame_i in range(total_frames): + img = np.empty((stream.width, stream.height, 3)) + img[:, :, 0] = 0.5 + 0.5 * np.sin(2 * np.pi * (0 / 3 + frame_i / total_frames)) + img[:, :, 1] = 0.5 + 0.5 * np.sin(2 * np.pi * (1 / 3 + frame_i / total_frames)) + img[:, :, 2] = 0.5 + 0.5 * np.sin(2 * np.pi * (2 / 3 + frame_i / total_frames)) + + img = np.round(255 * img).astype(np.uint8) + img = np.clip(img, 0, 255) + + frame = av.VideoFrame.from_ndarray(img, format='rgb24') + for packet in stream.encode(frame): + container.mux(packet) + + # Flush stream + for packet in stream.encode(): + container.mux(packet) + + # Close the file + container.close() + f.name = filename + f.seek(0) + + return [(width, height)] * total_frames, f + + +class ForceLogin: + def __init__(self, user, client): + self.user = user + self.client = client + + def __enter__(self): + if self.user: + self.client.force_login(self.user, + backend='django.contrib.auth.backends.ModelBackend') + + return self + + def __exit__(self, exception_type, exception_value, traceback): + if self.user: + self.client.logout() + +class _DbTestBase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + cls.create_db_users() + + @classmethod + def create_db_users(cls): + (group_admin, _) = Group.objects.get_or_create(name="admin") + (group_user, _) = Group.objects.get_or_create(name="user") + + user_admin = User.objects.create_superuser(username="admin", email="", + password="admin") + user_admin.groups.add(group_admin) + user_dummy = User.objects.create_user(username="user", password="user") + user_dummy.groups.add(group_user) + + cls.admin = user_admin + cls.user = user_dummy + + def _put_api_v1_task_id_annotations(self, tid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/tasks/%s/annotations" % tid, + data=data, format="json") + + return response + + def _put_api_v1_job_id_annotations(self, jid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/jobs/%s/annotations" % jid, + data=data, format="json") + + return response + + @staticmethod + def _generate_task_images(count): # pylint: disable=no-self-use + images = {"client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) for i in range(count)} + images["image_quality"] = 75 + return images + + @staticmethod + def _generate_task_videos(count): # pylint: disable=no-self-use + videos = {"client_files[%d]" % i: generate_video_file("video_%d.mp4" % i) for i in range(count)} + videos["image_quality"] = 75 + return videos + + def _create_task(self, data, image_data): + with ForceLogin(self.user, self.client): + response = self.client.post('/api/v1/tasks', data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + def _get_jobs(self, task_id): + with ForceLogin(self.admin, self.client): + response = self.client.get("/api/v1/tasks/{}/jobs".format(task_id)) + return response.data + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + return response + + def _get_data_from_task(self, task_id, include_images): + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data, include_images=include_images) + return Dataset.from_extractors(extractor) + + def _get_request_with_data(self, path, data, user): + with ForceLogin(user, self.client): + response = self.client.get(path, data) + return response + + def _put_request_with_data(self, path, data, user): + with ForceLogin(user, self.client): + response = self.client.put(path, data) + return response + + def _delete_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.delete(path) + return response + + def _create_annotations(self, task, name_ann, key_get_values): + tmp_annotations = copy.deepcopy(annotations[name_ann]) + + # change attributes in all annotations + for item in tmp_annotations: + if item in ["tags", "shapes", "tracks"]: + for index_elem, _ in enumerate(tmp_annotations[item]): + tmp_annotations[item][index_elem]["label_id"] = task["labels"][0]["id"] + + for index_attribute, attribute in enumerate(task["labels"][0]["attributes"]): + spec_id = task["labels"][0]["attributes"][index_attribute]["id"] + + if key_get_values == "random": + if attribute["input_type"] == "number": + start = int(attribute["values"][0]) + stop = int(attribute["values"][1]) + 1 + step = int(attribute["values"][2]) + value = str(random.randrange(start, stop, step)) + else: + value = random.choice(task["labels"][0]["attributes"][index_attribute]["values"]) + elif key_get_values == "default": + value = attribute["default_value"] + + if item == "tracks" and attribute["mutable"]: + for index_shape, _ in enumerate(tmp_annotations[item][index_elem]["shapes"]): + tmp_annotations[item][index_elem]["shapes"][index_shape]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + else: + tmp_annotations[item][index_elem]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + response = self._put_api_v1_task_id_annotations(task["id"], tmp_annotations) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def _create_annotations_in_job(self, task, job_id, name_ann, key_get_values): + tmp_annotations = copy.deepcopy(annotations[name_ann]) + + # change attributes in all annotations + for item in tmp_annotations: + if item in ["tags", "shapes", "tracks"]: + for index_elem, _ in enumerate(tmp_annotations[item]): + tmp_annotations[item][index_elem]["label_id"] = task["labels"][0]["id"] + + for index_attribute, attribute in enumerate(task["labels"][0]["attributes"]): + spec_id = task["labels"][0]["attributes"][index_attribute]["id"] + + if key_get_values == "random": + if attribute["input_type"] == "number": + start = int(attribute["values"][0]) + stop = int(attribute["values"][1]) + 1 + step = int(attribute["values"][2]) + value = str(random.randrange(start, stop, step)) + else: + value = random.choice(task["labels"][0]["attributes"][index_attribute]["values"]) + elif key_get_values == "default": + value = attribute["default_value"] + + if item == "tracks" and attribute["mutable"]: + for index_shape, _ in enumerate(tmp_annotations[item][index_elem]["shapes"]): + tmp_annotations[item][index_elem]["shapes"][index_shape]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + else: + tmp_annotations[item][index_elem]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + response = self._put_api_v1_job_id_annotations(job_id, tmp_annotations) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def _download_file(self, url, data, user, file_name): + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + content = BytesIO(b"".join(response.streaming_content)) + with open(file_name, "wb") as f: + f.write(content.getvalue()) + + def _upload_file(self, url, data, user): + response = self._put_request_with_data(url, {"annotation_file": data}, user) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def _check_downloaded_file(self, file_name): + if not osp.exists(file_name): + raise FileNotFoundError(f"File '{file_name}' was not downloaded") + + def _generate_url_dump_tasks_annotations(self, task_id): + return f"/api/v1/tasks/{task_id}/annotations" + + def _generate_url_upload_tasks_annotations(self, task_id, upload_format_name): + return f"/api/v1/tasks/{task_id}/annotations?format={upload_format_name}" + + def _generate_url_dump_job_annotations(self, job_id): + return f"/api/v1/jobs/{job_id}/annotations" + + def _generate_url_upload_job_annotations(self, job_id, upload_format_name): + return f"/api/v1/jobs/{job_id}/annotations?format={upload_format_name}" + + def _generate_url_dump_dataset(self, task_id): + return f"/api/v1/tasks/{task_id}/dataset" + + def _remove_annotations(self, url, user): + response = self._delete_request(url, user) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + return response + + +class TaskDumpUploadTest(_DbTestBase): + def test_api_v1_dump_and_upload_annotations_with_objects_type_is_shape(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED,'file_exists': True, 'annotation_loaded': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False, 'annotation_loaded': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is shape + for dump_format in dump_formats: + if not dump_format.ENABLED or dump_format.DISPLAY_NAME in [ + 'Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0' + + ]: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + # create task with annotations + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + # Upload annotations with objects type is shape + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, f'{test_name}_admin_CVAT for images 1.1.zip') + else: + + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{upload_format_name}.zip') + if not upload_format.ENABLED or not osp.exists(file_zip_name): + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + if osp.exists(file_zip_name): + for user, edata in list(expected.items()): + # remove all annotations from task (create new task without annotation) + images = self._generate_task_images(3) + if upload_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif upload_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif upload_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, edata['create code']) + + def test_api_v1_dump_annotations_with_objects_type_is_track(self): + test_name = self._testMethodName + + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False, 'annotation_loaded': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is track + for dump_format in dump_formats: + if not dump_format.ENABLED or dump_format.DISPLAY_NAME in [ + 'Kitti Raw Format 1.0','Sly Point Cloud Format 1.0' + + ]: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + # create task with annotations + video = self._generate_task_videos(1) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], video) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], video) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], video) + else: + task = self._create_task(tasks["main"], video) + task_id = task["id"] + + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + # Upload annotations with objects type is track + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, f'{test_name}_admin_CVAT for video 1.1.zip') + else: + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{upload_format_name}.zip') + if not upload_format.ENABLED or not osp.exists(file_zip_name): + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + if osp.exists(file_zip_name): + for user, edata in list(expected.items()): + # remove all annotations from task (create new task without annotation) + video = self._generate_task_videos(1) + if upload_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], video) + elif upload_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], video) + elif upload_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], video) + else: + task = self._create_task(tasks["main"], video) + task_id = task["id"] + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, edata['create code']) + + def test_api_v1_dump_tag_annotations(self): + dump_format_name = "CVAT for images 1.1" + data = { + "format": dump_format_name, + "action": "download", + } + test_cases = ['all' 'first'] + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False}, + } + for test_case in test_cases: + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + jobs = self._get_jobs(task_id) + + if test_case == "all": + for job in jobs: + self._create_annotations_in_job(task, job["id"], "CVAT for images 1.1 tag", "default") + else: + self._create_annotations_in_job(task, jobs[0]["id"], "CVAT for images 1.1 tag", "default") + + for user, edata in list(expected.items()): + with self.subTest(format=f"{edata['name']}"): + with TestDir() as test_dir: + user_name = edata['name'] + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{user_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + def test_api_v1_dump_and_upload_annotations_with_objects_are_different_images(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_types = ["task", "job"] + + images = self._generate_task_images(2) + task = self._create_task(tasks["main"], images) + task_id = task["id"] + + for upload_type in upload_types: + with self.subTest(format=type): + with TestDir() as test_dir: + if upload_type == "task": + self._create_annotations(task, "CVAT for images 1.1 different types", "random") + else: + jobs = self._get_jobs(task_id) + job_id = jobs[0]["id"] + self._create_annotations_in_job(task, job_id, "CVAT for images 1.1 different types", "random") + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}_{upload_type}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + self._remove_annotations(url, self.admin) + if upload_type == "task": + url_upload = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + else: + jobs = self._get_jobs(task_id) + url_upload = self._generate_url_upload_job_annotations(jobs[0]["id"], "CVAT 1.1") + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url_upload, binary_file, self.admin) + + response = self._get_request(f"/api/v1/tasks/{task_id}/annotations", self.admin) + self.assertEqual(len(response.data["shapes"]), 2) + self.assertEqual(len(response.data["tracks"]), 0) + + def test_api_v1_dump_and_upload_annotations_with_objects_are_different_video(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + upload_types = ["task", "job"] + + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + task_id = task["id"] + + for upload_type in upload_types: + with self.subTest(format=type): + with TestDir() as test_dir: + if upload_type == "task": + self._create_annotations(task, "CVAT for images 1.1 different types", "random") + else: + jobs = self._get_jobs(task_id) + job_id = jobs[0]["id"] + self._create_annotations_in_job(task, job_id, "CVAT for images 1.1 different types", "random") + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}_{upload_type}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + self._remove_annotations(url, self.admin) + if upload_type == "task": + url_upload = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + else: + jobs = self._get_jobs(task_id) + url_upload = self._generate_url_upload_job_annotations(jobs[0]["id"], "CVAT 1.1") + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url_upload, binary_file, self.admin) + self.assertEqual(osp.exists(file_zip_name), True) + + response = self._get_request(f"/api/v1/tasks/{task_id}/annotations", self.admin) + self.assertEqual(len(response.data["shapes"]), 0) + self.assertEqual(len(response.data["tracks"]), 2) + + def test_api_v1_dump_and_upload_with_objects_type_is_track_and_outside_property(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + self._create_annotations(task, "CVAT for video 1.1 slice track", "random") + task_id = task["id"] + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + with open(file_zip_name, 'rb') as binary_file: + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_and_upload_with_objects_type_is_track_and_keyframe_property(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + self._create_annotations(task, "CVAT for video 1.1 slice track keyframe", "random") + task_id = task["id"] + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + with open(file_zip_name, 'rb') as binary_file: + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_upload_annotations_from_several_jobs(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + jobs = self._get_jobs(task_id) + for job in jobs: + self._create_annotations_in_job(task, job["id"], "CVAT for images 1.1 merge", "random") + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + # remove annotations + self._remove_annotations(url, self.admin) + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_annotations_with_objects_type_is_shape_from_several_jobs(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + test_cases = ['all', 'first'] + + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + + for test_case in test_cases: + with TestDir() as test_dir: + jobs = self._get_jobs(task_id) + if test_case == "all": + for job in jobs: + self._create_annotations_in_job(task, job["id"], dump_format_name, "default") + else: + self._create_annotations_in_job(task, jobs[0]["id"], dump_format_name, "default") + + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + # remove annotations + self._remove_annotations(url, self.admin) + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_export_dataset(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is shape + for dump_format in dump_formats: + if not dump_format.ENABLED or dump_format.DISPLAY_NAME != "CVAT for images 1.1": + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + # create task with annotations + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + # dump annotations + url = self._generate_url_dump_dataset(task_id) + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["accept code"]) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["create code"]) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["code"]) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(response.status_code, edata['code']) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + def test_api_v1_dump_empty_frames(self): + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + + with TestDir() as test_dir: + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + task = self._create_task(tasks["no attributes"], images) + task_id = task["id"] + self._create_annotations(task, "empty annotation", "default") + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'empty_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, 'empty_CVAT for images 1.1.zip') + else: + file_zip_name = osp.join(test_dir, f'empty_{upload_format_name}.zip') + if not osp.exists(file_zip_name) or not upload_format.ENABLED: + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + images = self._generate_task_images(3) + task = self._create_task(tasks["no attributes"], images) + task_id = task["id"] + + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._put_request_with_data(url, {}, self.admin) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertIsNone(response.data) + + def test_api_v1_rewriting_annotations(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + with TestDir() as test_dir: + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + if dump_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + "Datumaro 1.0", # Datumaro 1.0 is not in the list of import format + 'Kitti Raw Format 1.0', + 'Sly Point Cloud Format 1.0' + + ]: + self.skipTest("Format is fail") + images = self._generate_task_images(3) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_ann_prev_data = task_ann.data + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + self._remove_annotations(url, self.admin) + + self._create_annotations(task, "CVAT for images 1.1 many jobs", "default") + + if dump_format_name == "CVAT for images 1.1" or dump_format_name == "CVAT for video 1.1": + dump_format_name = "CVAT 1.1" + url = self._generate_url_upload_tasks_annotations(task_id, dump_format_name) + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_ann_data = task_ann.data + self.assertEqual(len(task_ann_data["shapes"]), len(task_ann_prev_data["shapes"])) + + def test_api_v1_tasks_annotations_dump_and_upload_many_jobs_with_datumaro(self): + test_name = self._testMethodName + upload_format_name = "CVAT 1.1" + include_images_params = (False, True) + dump_format_names = ("CVAT for images 1.1", "CVAT for video 1.1") + + for dump_format_name, include_images in itertools.product(dump_format_names, include_images_params): + with self.subTest(f"{dump_format_name}_include_images_{include_images}"): + # create task with annotations + images = self._generate_task_images(13) + task = self._create_task(tasks["many jobs"], images) + self._create_annotations(task, f'{dump_format_name} many jobs', "default") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_tasks_annotations_dump_and_upload_with_datumaro(self): + test_name = self._testMethodName + # get formats + dump_formats = dm.views.get_export_formats() + include_images_params = (False, True) + for dump_format, include_images in itertools.product(dump_formats, include_images_params): + if dump_format.ENABLED: + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(dump_format_name): + if dump_format_name in [ + "MOT 1.1", + "Datumaro 1.0", # not uploaded + "CamVid 1.0", # issue #2840 and changed points values + "MOTS PNG 1.0", # changed points values + "Segmentation mask 1.1", # changed points values + "ICDAR Segmentation 1.0", # changed points values + 'Kitti Raw Format 1.0', + 'Sly Point Cloud Format 1.0' + ]: + self.skipTest("Format is fail") + + # create task + images = self._generate_task_images(3) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", + "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + + # create annotations + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + if dump_format_name in ["CVAT for images 1.1", "CVAT for video 1.1"]: + upload_format_name = "CVAT 1.1" + else: + upload_format_name = dump_format_name + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_check_duplicated_polygon_points(self): + test_name = self._testMethodName + images = self._generate_task_images(10) + task = self._create_task(tasks["main"], images) + task_id = task["id"] + data = { + "format": "CVAT for video 1.1", + "action": "download", + } + annotation_name = "CVAT for video 1.1 polygon" + self._create_annotations(task, annotation_name, "default") + annotation_points = annotations[annotation_name]["tracks"][0]["shapes"][0]['points'] + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + folder_name = osp.join(test_dir, f'{test_name}') + with zipfile.ZipFile(file_zip_name, 'r') as zip_ref: + zip_ref.extractall(folder_name) + + tree = ET.parse(osp.join(folder_name, 'annotations.xml')) + root = tree.getroot() + for polygon in root.findall("./track[@id='0']/polygon"): + polygon_points = polygon.attrib["points"].replace(",", ";") + polygon_points = [float(p) for p in polygon_points.split(";")] + self.assertEqual(polygon_points, annotation_points) + + def test_api_v1_check_widerface_with_all_attributes(self): + test_name = self._testMethodName + dump_format_name = "WiderFace 1.0" + upload_format_name = "WiderFace 1.0" + + for include_images in (False, True): + with self.subTest(): + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["widerface with all attributes"], images) + self._create_annotations(task, f'{dump_format_name}', "random") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload)\ + + def test_api_v1_check_attribute_import_in_tracks(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + upload_format_name = "CVAT 1.1" + + for include_images in (False, True): + with self.subTest(): + # create task with annotations + images = self._generate_task_images(13) + task = self._create_task(tasks["many jobs"], images) + self._create_annotations(task, f'{dump_format_name} attributes in tracks', "default") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) diff --git a/cvat/apps/dataset_manager/views.py b/cvat/apps/dataset_manager/views.py index b622eaa6..36fcea63 100644 --- a/cvat/apps/dataset_manager/views.py +++ b/cvat/apps/dataset_manager/views.py @@ -8,18 +8,18 @@ import tempfile from datetime import timedelta import django_rq +from datumaro.cli.util import make_file_name +from datumaro.util import to_snake_case from django.utils import timezone import cvat.apps.dataset_manager.task as task +from cvat.apps.engine.backup import TaskExporter from cvat.apps.engine.log import slogger from cvat.apps.engine.models import Task -from datumaro.cli.util import make_file_name -from datumaro.util import to_snake_case from .formats.registry import EXPORT_FORMATS, IMPORT_FORMATS from .util import current_function_name - _MODULE_NAME = __package__ + '.' + osp.splitext(osp.basename(__file__))[0] def log_exception(logger=None, exc_info=True): if logger is None: @@ -97,6 +97,40 @@ def clear_export_cache(task_id, file_path, file_ctime): log_exception(slogger.task[task_id]) raise +def backup_task(task_id, output_path): + try: + db_task = Task.objects.get(pk=task_id) + + cache_dir = get_export_cache_dir(db_task) + output_path = osp.join(cache_dir, output_path) + + task_time = timezone.localtime(db_task.updated_date).timestamp() + if not (osp.exists(output_path) and \ + task_time <= osp.getmtime(output_path)): + os.makedirs(cache_dir, exist_ok=True) + with tempfile.TemporaryDirectory(dir=cache_dir) as temp_dir: + temp_file = osp.join(temp_dir, 'dump') + task_exporter = TaskExporter(task_id) + task_exporter.export_to(temp_file) + os.replace(temp_file, output_path) + + archive_ctime = osp.getctime(output_path) + scheduler = django_rq.get_scheduler() + cleaning_job = scheduler.enqueue_in(time_delta=CACHE_TTL, + func=clear_export_cache, + task_id=task_id, + file_path=output_path, file_ctime=archive_ctime) + slogger.task[task_id].info( + "The task '{}' is backuped at '{}' " + "and available for downloading for the next {}. " + "Export cache cleaning job is enqueued, id '{}'".format( + db_task.name, output_path, CACHE_TTL, + cleaning_job.id)) + + return output_path + except Exception: + log_exception(slogger.task[task_id]) + raise def get_export_formats(): return list(EXPORT_FORMATS.values()) @@ -108,4 +142,4 @@ def get_all_formats(): return { 'importers': get_import_formats(), 'exporters': get_export_formats(), - } \ No newline at end of file + } diff --git a/cvat/apps/dataset_repo/README.md b/cvat/apps/dataset_repo/README.md index 05022e88..06456969 100644 --- a/cvat/apps/dataset_repo/README.md +++ b/cvat/apps/dataset_repo/README.md @@ -8,10 +8,12 @@ The SSH protocol is used for an authorization. ### Using -- Put a private SSH key into the `ssh` directory. The public key corresponding to this private key should be attached to an github user. +- Put a private SSH key into the `ssh` directory. + The public key corresponding to this private key should be attached to an github user. - If you don't put any custom key, it will generated automatically. - Setup a repository URL and a path (which is relative for a repository) in the create task dialog. - Annotate a task. - Press the button "Git Repository Sync" on the dashboard. - In the dialog window press the button "Sync" and waiting for some time. -- An annotation will be dumped, archived and pushed to the attached remote repository. You can do a pull request manually. +- An annotation will be dumped, archived and pushed to the attached remote repository. + You can do a pull request manually. diff --git a/cvat/apps/dataset_repo/dataset_repo.py b/cvat/apps/dataset_repo/dataset_repo.py index 4b2790b4..d86553f5 100644 --- a/cvat/apps/dataset_repo/dataset_repo.py +++ b/cvat/apps/dataset_repo/dataset_repo.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -163,13 +163,13 @@ class Git: slogger.task[self._tid].info("Cloning remote repository from {}..".format(ssh_url)) self._rep = git.Repo.clone_from(ssh_url, self._cwd) - # Intitialization + # Initialization self._configurate() # Method is some wrapper for clone - # It restores state if any errors have occured - # It useful if merge conflicts have occured during pull + # It restores state if any errors have occurred + # It useful if merge conflicts have occurred during pull def _reclone(self): if os.path.exists(self._cwd): if not os.path.isdir(self._cwd): @@ -184,7 +184,7 @@ class Git: self._clone() shutil.rmtree(tmp_repo, True) except Exception as ex: - # Restore state if any errors have occured + # Restore state if any errors have occurred if os.path.isdir(self._cwd): shutil.rmtree(self._cwd, True) os.rename(tmp_repo, self._cwd) @@ -386,7 +386,7 @@ def initial_create(tid, git_path, lfs, user): except git.exc.GitCommandError as ex: _have_no_access_exception(ex) except Exception as ex: - slogger.task[tid].exception('exception occured during git initial_create', exc_info = True) + slogger.task[tid].exception('exception occurred during git initial_create', exc_info = True) raise ex @@ -455,7 +455,7 @@ def update_states(): try: get(db_git.task_id, db_user) except Exception: - slogger.glob("Exception occured during a status updating for db_git with tid: {}".format(db_git.task_id)) + slogger.glob("Exception occurred during a status updating for db_git with tid: {}".format(db_git.task_id)) @transaction.atomic def _onsave(jid, data, action): diff --git a/cvat/apps/dataset_repo/management/commands/update_git_states.py b/cvat/apps/dataset_repo/management/commands/update_git_states.py index 939d60a4..e11bd1b1 100644 --- a/cvat/apps/dataset_repo/management/commands/update_git_states.py +++ b/cvat/apps/dataset_repo/management/commands/update_git_states.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -16,6 +16,6 @@ class Command(BaseCommand): try: update_states() except Exception as ex: - print("An error occured during update task statuses: {}".format(str(ex))) + print("An error occurred during update task statuses: {}".format(str(ex))) time.sleep(INTERVAL_SEC) diff --git a/cvat/apps/dataset_repo/tests.py b/cvat/apps/dataset_repo/tests.py index 8c5b1bef..4a397f44 100644 --- a/cvat/apps/dataset_repo/tests.py +++ b/cvat/apps/dataset_repo/tests.py @@ -3,27 +3,256 @@ # SPDX-License-Identifier: MIT from itertools import product +from io import BytesIO +from PIL import Image +import os +from os import path as osp +import git +import io +from unittest import mock -from django.test import TestCase -# Create your tests here. +from rest_framework.test import APIClient, APITestCase +from rest_framework import status +from django.utils import timezone +from django.contrib.auth.models import Group, User +from cvat.apps.engine.models import Task +from cvat.apps.dataset_repo.dataset_repo import (Git, initial_create, push, get) +from cvat.apps.dataset_repo.models import GitData, GitStatusChoice -from cvat.apps.dataset_repo.dataset_repo import Git +orig_execute = git.cmd.Git.execute +GIT_URL = "https://1.2.3.4/repo/exist.git" +PARSE_GIT_URL = "git@1.2.3.4:repo/exist.git" -class GitUrlTest(TestCase): +def generate_image_file(filename, size=(100, 50)): + f = BytesIO() + image = Image.new('RGB', size=size) + image.save(f, 'jpeg') + f.name = filename + f.seek(0) + return f + + +class ForceLogin: + def __init__(self, user, client): + self.user = user + self.client = client + + def __enter__(self): + if self.user: + self.client.force_login(self.user, + backend='django.contrib.auth.backends.ModelBackend') + + return self + + def __exit__(self, exception_type, exception_value, traceback): + if self.user: + self.client.logout() + + +class GitRemote: + def pull(self, refspec=None, progress=None, **kwargs): + _ = (refspec, progress, *kwargs) + return 0 + + +class FakeHexShaObject: + def __init__(self, hexsha): + self.hexsha = hexsha + + +class GitRepo(git.Repo): + def clone(self, path, progress=None, multi_options=None, **kwargs): + _ = (progress, multi_options, *kwargs) + if osp.isfile(osp.join(path, '.git')): + return self + else: + return git.Repo.init(path=path) + + def merge_base(self, *rev, **kwargs): + _ = (rev, *kwargs) + hexsha = self.git.show_ref('refs/heads/{}'.format(rev[1])).split(" ") + return [FakeHexShaObject(hexsha[0])] + + +class GitCmd: + def execute(self, command, + istream=None, + with_extended_output=False, + with_exceptions=True, + as_process=False, + output_stream=None, + stdout_as_string=True, + kill_after_timeout=None, + with_stdout=True, + universal_newlines=False, + shell=None, + env=None, + max_chunk_size=io.DEFAULT_BUFFER_SIZE, + **subprocess_kwargs + ): + _ = subprocess_kwargs + if command[1] == "push": + return 0 + elif command[1] == "remote" and command[2] == "get-url": + return PARSE_GIT_URL + else: + return orig_execute(self, command, istream, with_extended_output, + with_exceptions, as_process, output_stream, + stdout_as_string, kill_after_timeout, with_stdout, + universal_newlines, shell, env, max_chunk_size) + + +class TestGit(Git): + def set_rep(self): + self._rep = git.Repo(self._cwd) # pylint: disable=W0201 + + def pull(self): + self._pull() + + def create_master_branch(self): + self._create_master_branch() + + def to_task_branch(self): + self._to_task_branch() + + def get_cwd(self): + return self._cwd + + def parse_url(self): + return Git._parse_url(self) + + def get_rep(self): + return self._rep + + def update_config(self): + self._update_config() + + def get_working_dir(self): + return self._rep.git.working_dir + + def configurate(self): + self._configurate() + + def clone(self): + self._clone() + + def reclone(self): + self._reclone() + + +class GitDatasetRepoTest(APITestCase): class FakeGit: def __init__(self, url): self._url = url + def setUp(self): + self.client = APIClient() + db_git = GitData() + db_git.url = GIT_URL + + def add_file(self, path, filename): + readme_md_name = os.path.join(path, filename) + with open(readme_md_name, "w"): + pass + + @classmethod + def setUpTestData(cls): + (group_admin, _) = Group.objects.get_or_create(name="admin") + (group_user, _) = Group.objects.get_or_create(name="user") + Group.objects.get_or_create(name="annotator") + Group.objects.get_or_create(name="observer") + + user_admin = User.objects.create_superuser(username="admin", email="admin_example@cvat.com") + user_admin.groups.add(group_admin) + user = User.objects.create_user(username="user", email="user_example@cvat.com") + user.groups.add(group_user) + + cls.admin = user_admin + cls.owner = user + cls.assignee = user + cls.annotator = user + cls.observer = user + cls.user = user + cls.empty_task_example = { + "name": "task", + "owner_id": cls.owner.id, + "assignee_id": cls.assignee.id, + "overlap": 0, + "segment_size": 100, + "labels": [ + { + "label_id": 1, + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + } + + def _run_api_v1_job_id_annotation(self, jid, data, user): + with ForceLogin(user, self.client): + response = self.client.patch('/api/v1/jobs/{}/annotations?action=create'.format(jid), + data=data, format="json") + + return response + + def _get_jobs(self, task_id): + with ForceLogin(self.admin, self.client): + response = self.client.get("/api/v1/tasks/{}/jobs".format(task_id)) + return response.data + + def _create_task(self, init_repos=False): + data = { + "name": "task_example", + "owner_id": self.owner.id, + "assignee_id": self.assignee.id, + "overlap": 0, + "segment_size": 100, + "image_quality": 75, + "size": 100, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + } + images = {"client_files[0]": generate_image_file("image_0.jpg")} + images["image_quality"] = 75 + + with ForceLogin(self.user, self.client): + response = self.client.post('/api/v1/tasks', data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=images) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + db_task = Task.objects.get(pk=task["id"]) + repos_dir = os.path.join(db_task.get_task_artifacts_dirname(), "repos") + task["repos_path"] = repos_dir + + if init_repos: + os.makedirs(repos_dir) + git.Repo.init(path=repos_dir) + + return task + def _check_correct_urls(self, samples): for i, (expected, url) in enumerate(samples): - git = GitUrlTest.FakeGit(url) + fake_git = GitDatasetRepoTest.FakeGit(url) try: - actual = Git._parse_url(git) + actual = TestGit.parse_url(fake_git) self.assertEqual(expected, actual, "URL #%s: '%s'" % (i, url)) - except Exception: - self.assertFalse(True, "URL #%s: '%s'" % (i, url)) + except Exception: # pylint: disable=broad-except + self.fail("URL #%s: '%s'" % (i, url)) def test_correct_urls_can_be_parsed(self): hosts = ['host.zone', '1.2.3.4'] @@ -36,24 +265,249 @@ class GitUrlTest(TestCase): # http samples protocols = ['', 'http://', 'https://'] - for protocol, host, port, repo_group, repo, git in product( + for protocol, host, port, repo_group, repo, git_suffix in product( protocols, hosts, ports, repo_groups, repo_repos, git_suffixes): url = '{protocol}{host}{port}/{repo_group}/{repo}{git}'.format( protocol=protocol, host=host, port=port, - repo_group=repo_group, repo=repo, git=git + repo_group=repo_group, repo=repo, git=git_suffix ) expected = ('git', host + port, '%s/%s.git' % (repo_group, repo)) samples.append((expected, url)) # git samples users = ['user', 'u123_.'] - for user, host, port, repo_group, repo, git in product( + for user, host, port, repo_group, repo, git_suffix in product( users, hosts, ports, repo_groups, repo_repos, git_suffixes): url = '{user}@{host}{port}:{repo_group}/{repo}{git}'.format( user=user, host=host, port=port, - repo_group=repo_group, repo=repo, git=git + repo_group=repo_group, repo=repo, git=git_suffix ) expected = (user, host + port, '%s/%s.git' % (repo_group, repo)) samples.append((expected, url)) - self._check_correct_urls(samples) \ No newline at end of file + self._check_correct_urls(samples) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_init_repos(self): + for git_rep_already_init in [True, False]: + task = self._create_task(init_repos=git_rep_already_init) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + db_git.url = GIT_URL + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.init_repos() + self.assertTrue(osp.isdir(osp.join(cvat_git.get_working_dir(), '.git'))) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_git_create_master_branch(self): + task = self._create_task(init_repos=True) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + db_git.url = GIT_URL + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.set_rep() + cvat_git.create_master_branch() + cwd = cvat_git.get_cwd() + self.assertTrue(osp.isfile(osp.join(cwd, "README.md"))) + + repo = cvat_git.get_rep() + self.assertFalse(repo.is_dirty()) + self.assertTrue(len(repo.heads) == 1) + self.assertTrue(repo.heads[0].name == "master") + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_to_task_branch(self): + task = self._create_task(init_repos=True) + tid = task["id"] + task_name = task["name"] + db_task = Task.objects.get(pk=tid) + db_git = GitData() + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.set_rep() + cvat_git.create_master_branch() + cvat_git.to_task_branch() + + repo = cvat_git.get_rep() + heads = [head.name for head in repo.heads] + self.assertTrue('cvat_{}_{}'.format(tid, task_name) in heads) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_update_config(self): + for user in [self.admin, self.user]: + task = self._create_task(init_repos=True) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + + cvat_git = TestGit(db_git, db_task, user) + cvat_git.set_rep() + cvat_git.create_master_branch() + + cvat_git.update_config() + repo = cvat_git.get_rep() + with repo.config_reader() as cw: + self.assertEqual(user.username, cw.get("user", "name")) + self.assertEqual(user.email, cw.get("user", "email")) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_configurate(self): + task = self._create_task(init_repos=True) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.set_rep() + cvat_git.configurate() + + repo = cvat_git.get_rep() + self.assertTrue(len(repo.heads)) + self.assertTrue(osp.isdir(osp.join(db_task.get_task_artifacts_dirname(), "repos_diffs_v2"))) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_clone(self): + task = self._create_task(init_repos=False) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + db_git.url = GIT_URL + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.clone() + repo = cvat_git.get_rep() + self.assertTrue(osp.isdir(osp.join(cvat_git.get_working_dir(), '.git'))) + self.assertTrue(len(repo.heads)) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_reclone(self): + for git_rep_already_init in [True, False]: + task = self._create_task(init_repos=git_rep_already_init) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + db_git.url = GIT_URL + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.reclone() + self.assertTrue(osp.isdir(osp.join(cvat_git.get_working_dir(), '.git'))) + self.assertTrue(len(cvat_git.get_rep().heads)) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.remote.Remote.pull', new=GitRemote.pull) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_push(self): + task = self._create_task(init_repos=True) + db_task = Task.objects.get(pk=task["id"]) + db_git = GitData() + db_git.url = GIT_URL + db_git.path = "annotation.zip" + db_git.sync_date = timezone.now() + + cvat_git = TestGit(db_git, db_task, self.user) + cvat_git.set_rep() + cvat_git.create_master_branch() + self.add_file(cvat_git.get_cwd(), "file.txt") + cvat_git.push(self.user, "", "", db_task, db_task.updated_date) + self.assertFalse(cvat_git.get_rep().is_dirty()) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_request_initial_create(self): + task = self._create_task(init_repos=False) + initial_create(task["id"], GIT_URL, 1, self.user) + self.assertTrue(osp.isdir(osp.join(task["repos_path"], '.git'))) + git_repo = git.Repo(task["repos_path"]) + with git_repo.config_reader() as cw: + self.assertEqual(self.user.username, cw.get("user", "name")) + self.assertEqual(self.user.email, cw.get("user", "email")) + self.assertTrue(len(git_repo.heads)) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.remote.Remote.pull', new=GitRemote.pull) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_request_push(self): + task = self._create_task(init_repos=False) + tid = task["id"] + initial_create(tid, GIT_URL, 1, self.user) + self.add_file(task["repos_path"], "file.txt") + push(tid, self.user, "", "") + + git_repo = git.Repo(task["repos_path"]) + self.assertFalse(git_repo.is_dirty()) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.remote.Remote.pull', new=GitRemote.pull) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + @mock.patch('git.Repo.merge_base', new=GitRepo.merge_base) + def test_push_and_request_get(self): + task = self._create_task(init_repos=False) + tid = task["id"] + initial_create(tid, GIT_URL, 1, self.user) + self.add_file(task["repos_path"], "file.txt") + push(tid, self.user, "", "") + response = get(tid, self.user) + self.assertTrue(response["status"]["value"], "merged") + self.assertIsNone(response["status"]["error"]) + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.remote.Remote.pull', new=GitRemote.pull) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + @mock.patch('git.Repo.merge_base', new=GitRepo.merge_base) + def test_request_get(self): + task = self._create_task(init_repos=False) + tid = task["id"] + initial_create(tid, GIT_URL, 1, self.user) + response = get(tid, self.user) + + self.assertTrue(response["status"]["value"], "not sync") + + + @mock.patch('git.cmd.Git.execute', new=GitCmd.execute) + @mock.patch('git.remote.Remote.pull', new=GitRemote.pull) + @mock.patch('git.Repo.clone', new=GitRepo.clone) + @mock.patch('git.Repo.clone_from', new=GitRepo.clone) + def test_request_on_save(self): + task = self._create_task(init_repos=False) + tid = task["id"] + initial_create(tid, GIT_URL, 1, self.user) + + jobs = self._get_jobs(tid) + ann = { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "points", + "occluded": False, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": 1, + "group": 0, + "source": "manual", + "attributes": [] + }, + ], + "tracks": [] + } + self._run_api_v1_job_id_annotation(jobs[0]["id"], ann, self.user) + db_git = GitData() + self.assertEqual(db_git.status, GitStatusChoice.NON_SYNCED) diff --git a/cvat/apps/dataset_repo/views.py b/cvat/apps/dataset_repo/views.py index 39ced8c4..dd50268c 100644 --- a/cvat/apps/dataset_repo/views.py +++ b/cvat/apps/dataset_repo/views.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -30,7 +30,7 @@ def check_process(request, rq_id): else: return JsonResponse({"status": "unknown"}) except Exception as ex: - slogger.glob.error("error occured during checking repository request with rq id {}".format(rq_id), exc_info=True) + slogger.glob.error("error occurred during checking repository request with rq id {}".format(rq_id), exc_info=True) return HttpResponseBadRequest(str(ex)) @@ -50,7 +50,7 @@ def create(request, tid): queue.enqueue_call(func = CVATGit.initial_create, args = (tid, path, lfs, request.user), job_id = rq_id) return JsonResponse({ "rq_id": rq_id }) except Exception as ex: - slogger.glob.error("error occured during initial cloning repository request with rq id {}".format(rq_id), exc_info=True) + slogger.glob.error("error occurred during initial cloning repository request with rq id {}".format(rq_id), exc_info=True) return HttpResponseBadRequest(str(ex)) @@ -68,7 +68,7 @@ def push_repository(request, tid): return JsonResponse({ "rq_id": rq_id }) except Exception as ex: try: - slogger.task[tid].error("error occured during pushing repository request", exc_info=True) + slogger.task[tid].error("error occurred during pushing repository request", exc_info=True) except Exception: pass return HttpResponseBadRequest(str(ex)) @@ -83,7 +83,7 @@ def get_repository(request, tid): return JsonResponse(CVATGit.get(tid, request.user)) except Exception as ex: try: - slogger.task[tid].error("error occured during getting repository info request", exc_info=True) + slogger.task[tid].error("error occurred during getting repository info request", exc_info=True) except Exception: pass return HttpResponseBadRequest(str(ex)) @@ -99,5 +99,5 @@ def get_meta_info(request): return JsonResponse(response, safe = False) except Exception as ex: - slogger.glob.exception("error occured during get meta request", exc_info = True) + slogger.glob.exception("error occurred during get meta request", exc_info = True) return HttpResponseBadRequest(str(ex)) diff --git a/cvat/apps/documentation/AWS-Deployment-Guide.md b/cvat/apps/documentation/AWS-Deployment-Guide.md deleted file mode 100644 index 6e0a03d3..00000000 --- a/cvat/apps/documentation/AWS-Deployment-Guide.md +++ /dev/null @@ -1,22 +0,0 @@ -### AWS-Deployment Guide - -There are two ways of deploying the CVAT. - -1. **On Nvidia GPU Machine:** Tensorflow annotation feature is dependent on GPU hardware. One of the easy ways to launch CVAT with the tf-annotation app is to use AWS P3 instances, which provides the NVIDIA GPU. Read more about [P3 instances here.](https://aws.amazon.com/about-aws/whats-new/2017/10/introducing-amazon-ec2-p3-instances/) - Overall setup instruction is explained in [main readme file](https://github.com/opencv/cvat/), except Installing Nvidia drivers. So we need to download the drivers and install it. For Amazon P3 instances, download the Nvidia Drivers from Nvidia website. For more check [Installing the NVIDIA Driver on Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html) link. - -2. **On Any other AWS Machine:** We can follow the same instruction guide mentioned in the - [installation instructions](https://github.com/opencv/cvat/blob/master/cvat/apps/documentation/installation.md). - The additional step is to add a [security group and rule to allow incoming connections](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html). - -For any of above, don't forget to add exposed AWS public IP address or hostname to `docker-compose.override.yml`: - -``` -version: "2.3" -services: - cvat_proxy: - environment: - CVAT_HOST: your-instance.amazonaws.com -``` - -In case of problems with using hostname, you can also use the public IPV4 instead of hostname. For AWS or any cloud based machines where the instances need to be terminated or stopped, the public IPV4 and hostname changes with every stop and reboot. To address this efficiently, avoid using spot instances that cannot be stopped, since copying the EBS to an AMI and restarting it throws problems. On the other hand, when a regular instance is stopped and restarted, the new hostname/IPV4 can be used in the `CVAT_HOST` variable in the `docker-compose.override.yml` and the build can happen instantly with CVAT tasks being available through the new IPV4. diff --git a/cvat/apps/documentation/__init__.py b/cvat/apps/documentation/__init__.py deleted file mode 100644 index a0fca4cb..00000000 --- a/cvat/apps/documentation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/documentation/apps.py b/cvat/apps/documentation/apps.py deleted file mode 100644 index bf1519ae..00000000 --- a/cvat/apps/documentation/apps.py +++ /dev/null @@ -1,11 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class DocumentationConfig(AppConfig): - name = 'cvat.apps.documentation' - diff --git a/cvat/apps/documentation/mounting_cloud_storages.md b/cvat/apps/documentation/mounting_cloud_storages.md deleted file mode 100644 index f8d1de55..00000000 --- a/cvat/apps/documentation/mounting_cloud_storages.md +++ /dev/null @@ -1,385 +0,0 @@ -- [Mounting cloud storage](#mounting-cloud-storage) - - [AWS S3 bucket](#aws-s3-bucket-as-filesystem) - - [Ubuntu 20.04](#aws_s3_ubuntu_2004) - - [Mount](#aws_s3_mount) - - [Automatically mount](#aws_s3_automatically_mount) - - [Using /etc/fstab](#aws_s3_using_fstab) - - [Using systemd](#aws_s3_using_systemd) - - [Check](#aws_s3_check) - - [Unmount](#aws_s3_unmount_filesystem) - - [Azure container](#microsoft-azure-container-as-filesystem) - - [Ubuntu 20.04](#azure_ubuntu_2004) - - [Mount](#azure_mount) - - [Automatically mount](#azure_automatically_mount) - - [Using /etc/fstab](#azure_using_fstab) - - [Using systemd](#azure_using_systemd) - - [Check](#azure_check) - - [Unmount](#azure_unmount_filesystem) - - [Google Drive](#google-drive-as-filesystem) - - [Ubuntu 20.04](#google_drive_ubuntu_2004) - - [Mount](#google_drive_mount) - - [Automatically mount](#google_drive_automatically_mount) - - [Using /etc/fstab](#google_drive_using_fstab) - - [Using systemd](#google_drive_using_systemd) - - [Check](#google_drive_check) - - [Unmount](#google_drive_unmount_filesystem) - -# Mounting cloud storage -## AWS S3 bucket as filesystem -### Ubuntu 20.04 -#### Mount - -1. Install s3fs: - - ```bash - sudo apt install s3fs - ``` - -1. Enter your credentials in a file `${HOME}/.passwd-s3fs` and set owner-only permissions: - - ```bash - echo ACCESS_KEY_ID:SECRET_ACCESS_KEY > ${HOME}/.passwd-s3fs - chmod 600 ${HOME}/.passwd-s3fs - ``` - -1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` -1. Run s3fs, replace `bucket_name`, `mount_point`: - - ```bash - s3fs -o allow_other - ``` - -For more details see [here](https://github.com/s3fs-fuse/s3fs-fuse). - -#### Automatically mount -Follow the first 3 mounting steps above. - -##### Using fstab - -1. Create a bash script named aws_s3_fuse(e.g in /usr/bin, as root) with this content - (replace `user_name` on whose behalf the disk will be mounted, `backet_name`, `mount_point`, `/path/to/.passwd-s3fs`): - - ```bash - #!/bin/bash - sudo -u s3fs -o passwd_file=/path/to/.passwd-s3fs -o allow_other - exit 0 - ``` - -1. Give it the execution permission: - - ```bash - sudo chmod +x /usr/bin/aws_s3_fuse - ``` - -1. Edit `/etc/fstab` adding a line like this, replace `mount_point`): - - ```bash - /absolute/path/to/aws_s3_fuse fuse allow_other,user,_netdev 0 0 - ``` - -##### Using systemd - -1. Create unit file `sudo nano /etc/systemd/system/s3fs.service` - (replace `user_name`, `bucket_name`, `mount_point`, `/path/to/.passwd-s3fs`): - - ```bash - [Unit] - Description=FUSE filesystem over AWS S3 bucket - After=network.target - - [Service] - Environment="MOUNT_POINT=" - User= - Group= - ExecStart=s3fs ${MOUNT_POINT} -o passwd_file=/path/to/.passwd-s3fs -o allow_other - ExecStop=fusermount -u ${MOUNT_POINT} - Restart=always - Type=forking - - [Install] - WantedBy=multi-user.target - ``` - -1. Update the system configurations, enable unit autorun when the system boots, mount the bucket: - - ```bash - sudo systemctl daemon-reload - sudo systemctl enable s3fs.service - sudo systemctl start s3fs.service - ``` - -#### Check -A file `/etc/mtab` contains records of currently mounted filesystems. -```bash -cat /etc/mtab | grep 's3fs' -``` - -#### Unmount filesystem -```bash -fusermount -u -``` - -If you used [systemd](#aws_s3_using_systemd) to mount a bucket: - -```bash -sudo systemctl stop s3fs.service -sudo systemctl disable s3fs.service -``` - -## Microsoft Azure container as filesystem -### Ubuntu 20.04 -#### Mount -1. Set up the Microsoft package repository.(More [here](https://docs.microsoft.com/en-us/windows-server/administration/Linux-Package-Repository-for-Microsoft-Software#configuring-the-repositories)) - - ```bash - wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb - sudo dpkg -i packages-microsoft-prod.deb - sudo apt-get update - ``` - -1. Install `blobfuse` and `fuse`: - - ```bash - sudo apt-get install blobfuse fuse - ``` - For more details see [here](https://github.com/Azure/azure-storage-fuse/wiki/1.-Installation) - -1. Create enviroments(replace `account_name`, `account_key`, `mount_point`): - - ```bash - export AZURE_STORAGE_ACCOUNT= - export AZURE_STORAGE_ACCESS_KEY= - MOUNT_POINT= - ``` - -1. Create a folder for cache: - ```bash - sudo mkdir -p /mnt/blobfusetmp - ``` - -1. Make sure the file must be owned by the user who mounts the container: - ```bash - sudo chown /mnt/blobfusetmp - ``` - -1. Create the mount point, if it doesn't exists: - ```bash - mkdir -p ${MOUNT_POINT} - ``` - -1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` -1. Mount container(replace `your_container`): - - ```bash - blobfuse ${MOUNT_POINT} --container-name= --tmp-path=/mnt/blobfusetmp -o allow_other - ``` - -#### Automatically mount -Follow the first 7 mounting steps above. -##### Using fstab - -1. Create configuration file `connection.cfg` with same content, change accountName, - select one from accountKey or sasToken and replace with your value: - - ```bash - accountName - # Please provide either an account key or a SAS token, and delete the other line. - accountKey - #change authType to specify only 1 - sasToken - authType - containerName - ``` - -1. Create a bash script named `azure_fuse`(e.g in /usr/bin, as root) with content below - (replace `user_name` on whose behalf the disk will be mounted, `mount_point`, `/path/to/blobfusetmp`,`/path/to/connection.cfg`): - - ```bash - #!/bin/bash - sudo -u blobfuse --tmp-path=/path/to/blobfusetmp --config-file=/path/to/connection.cfg -o allow_other - exit 0 - ``` - -1. Give it the execution permission: - ```bash - sudo chmod +x /usr/bin/azure_fuse - ``` - -1. Edit `/etc/fstab` with the blobfuse script. Add the following line(replace paths): -```bash -/absolute/path/to/azure_fuse fuse allow_other,user,_netdev -``` - -##### Using systemd - -1. Create unit file `sudo nano /etc/systemd/system/blobfuse.service`. - (replace `user_name`, `mount_point`, `container_name`,`/path/to/connection.cfg`): - - ```bash - [Unit] - Description=FUSE filesystem over Azure container - After=network.target - - [Service] - Environment="MOUNT_POINT=" - User= - Group= - ExecStart=blobfuse ${MOUNT_POINT} --container-name= --tmp-path=/mnt/blobfusetmp --config-file=/path/to/connection.cfg -o allow_other - ExecStop=fusermount -u ${MOUNT_POINT} - Restart=always - Type=forking - - [Install] - WantedBy=multi-user.target - ``` - -1. Update the system configurations, enable unit autorun when the system boots, mount the container: - - ```bash - sudo systemctl daemon-reload - sudo systemctl enable blobfuse.service - sudo systemctl start blobfuse.service - ``` - Or for more detail [see here](https://github.com/Azure/azure-storage-fuse/tree/master/systemd) - -#### Check -A file `/etc/mtab` contains records of currently mounted filesystems. -```bash -cat /etc/mtab | grep 'blobfuse' -``` - -#### Unmount filesystem -```bash -fusermount -u -``` - -If you used [systemd](#azure_using_systemd) to mount a container: - -```bash -sudo systemctl stop blobfuse.service -sudo systemctl disable blobfuse.service -``` - -If you have any mounting problems, check out the [answers](https://github.com/Azure/azure-storage-fuse/wiki/3.-Troubleshoot-FAQ) -to common problems - -## Google Drive as filesystem -### Ubuntu 20.04 -#### Mount -To mount a google drive as a filesystem in user space(FUSE) -you can use [google-drive-ocamlfuse](https://github.com/astrada/google-drive-ocamlfuse) -To do this follow the instructions below: - -1. Install google-drive-ocamlfuse: - - ```bash - sudo add-apt-repository ppa:alessandro-strada/ppa - sudo apt-get update - sudo apt-get install google-drive-ocamlfuse - ``` - -1. Run `google-drive-ocamlfuse` without parameters: - - ```bash - google-drive-ocamlfuse - ``` - - This command will create the default application directory (~/.gdfuse/default), - containing the configuration file config (see the [wiki](https://github.com/astrada/google-drive-ocamlfuse/wiki) - page for more details about configuration). - And it will start a web browser to obtain authorization to access your Google Drive. - This will let you modify default configuration before mounting the filesystem. - - Then you can choose a local directory to mount your Google Drive (e.g.: ~/GoogleDrive). - -1. Create the mount point, if it doesn't exist(replace mount_point): - - ```bash - mountpoint="" - mkdir -p $mountpoint - ``` - -1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` -1. Mount the filesystem: - - ```bash - google-drive-ocamlfuse -o allow_other $mountpoint - ``` - -#### Automatically mount -Follow the first 4 mounting steps above. -##### Using fstab - -1. Create a bash script named gdfuse(e.g in /usr/bin, as root) with this content - (replace `user_name` on whose behalf the disk will be mounted, `label`, `mount_point`): - - ```bash - #!/bin/bash - sudo -u google-drive-ocamlfuse -o allow_other -label
    \n\n\n', - tblLgn = headers.length; - - for (var i = 0; i < tblLgn; ++i) { - tb += headers[i]; - } - tb += '\n\n\n'; - - for (i = 0; i < cells.length; ++i) { - tb += '\n'; - for (var ii = 0; ii < tblLgn; ++ii) { - tb += cells[i][ii]; - } - tb += '\n'; - } - tb += '\n
    \n'; - return tb; - } - - function parseTable (rawTable) { - var i, tableLines = rawTable.split('\n'); - - for (i = 0; i < tableLines.length; ++i) { - // strip wrong first and last column if wrapped tables are used - if (/^ {0,3}\|/.test(tableLines[i])) { - tableLines[i] = tableLines[i].replace(/^ {0,3}\|/, ''); - } - if (/\|[ \t]*$/.test(tableLines[i])) { - tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, ''); - } - // parse code spans first, but we only support one line code spans - tableLines[i] = showdown.subParser('codeSpans')(tableLines[i], options, globals); - } - - var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}), - rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}), - rawCells = [], - headers = [], - styles = [], - cells = []; - - tableLines.shift(); - tableLines.shift(); - - for (i = 0; i < tableLines.length; ++i) { - if (tableLines[i].trim() === '') { - continue; - } - rawCells.push( - tableLines[i] - .split('|') - .map(function (s) { - return s.trim(); - }) - ); - } - - if (rawHeaders.length < rawStyles.length) { - return rawTable; - } - - for (i = 0; i < rawStyles.length; ++i) { - styles.push(parseStyles(rawStyles[i])); - } - - for (i = 0; i < rawHeaders.length; ++i) { - if (showdown.helper.isUndefined(styles[i])) { - styles[i] = ''; - } - headers.push(parseHeaders(rawHeaders[i], styles[i])); - } - - for (i = 0; i < rawCells.length; ++i) { - var row = []; - for (var ii = 0; ii < headers.length; ++ii) { - if (showdown.helper.isUndefined(rawCells[i][ii])) { - - } - row.push(parseCells(rawCells[i][ii], styles[ii])); - } - cells.push(row); - } - - return buildTable(headers, cells); - } - - text = globals.converter._dispatch('tables.before', text, options, globals); - - // find escaped pipe characters - text = text.replace(/\\(\|)/g, showdown.helper.escapeCharactersCallback); - - // parse multi column tables - text = text.replace(tableRgx, parseTable); - - // parse one column tables - text = text.replace(singeColTblRgx, parseTable); - - text = globals.converter._dispatch('tables.after', text, options, globals); - - return text; -}); - -showdown.subParser('underline', function (text, options, globals) { - 'use strict'; - - if (!options.underline) { - return text; - } - - text = globals.converter._dispatch('underline.before', text, options, globals); - - if (options.literalMidWordUnderscores) { - text = text.replace(/\b_?__(\S[\s\S]*)___?\b/g, function (wm, txt) { - return '' + txt + ''; - }); - } else { - text = text.replace(/_?__(\S[\s\S]*?)___?/g, function (wm, m) { - return (/\S$/.test(m)) ? '' + m + '' : wm; - }); - } - - // escape remaining underscores to prevent them being parsed by italic and bold - text = text.replace(/(_)/g, showdown.helper.escapeCharactersCallback); - - text = globals.converter._dispatch('underline.after', text, options, globals); - - return text; -}); - -/** - * Swap back in all the special characters we've hidden. - */ -showdown.subParser('unescapeSpecialChars', function (text, options, globals) { - 'use strict'; - text = globals.converter._dispatch('unescapeSpecialChars.before', text, options, globals); - - text = text.replace(/¨E(\d+)E/g, function (wholeMatch, m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - }); - - text = globals.converter._dispatch('unescapeSpecialChars.after', text, options, globals); - return text; -}); - -var root = this; - -// AMD Loader -if (typeof define === 'function' && define.amd) { - define(function () { - 'use strict'; - return showdown; - }); - -// CommonJS/nodeJS Loader -} else if (typeof module !== 'undefined' && module.exports) { - module.exports = showdown; - -// Regular Browser loader -} else { - root.showdown = showdown; -} -}).call(this); - -//# sourceMappingURL=showdown.js.map diff --git a/cvat/apps/documentation/static/documentation/js/3rdparty/showdown.js.map b/cvat/apps/documentation/static/documentation/js/3rdparty/showdown.js.map deleted file mode 100644 index 8909ebb3..00000000 --- a/cvat/apps/documentation/static/documentation/js/3rdparty/showdown.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/options.js","../src/showdown.js","../src/helpers.js","../src/converter.js","../src/subParsers/anchors.js","../src/subParsers/autoLinks.js","../src/subParsers/blockGamut.js","../src/subParsers/blockQuotes.js","../src/subParsers/codeBlocks.js","../src/subParsers/codeSpans.js","../src/subParsers/completeHTMLDocument.js","../src/subParsers/detab.js","../src/subParsers/ellipsis.js","../src/subParsers/emoji.js","../src/subParsers/encodeAmpsAndAngles.js","../src/subParsers/encodeBackslashEscapes.js","../src/subParsers/encodeCode.js","../src/subParsers/escapeSpecialCharsWithinTagAttributes.js","../src/subParsers/githubCodeBlocks.js","../src/subParsers/hashBlock.js","../src/subParsers/hashCodeTags.js","../src/subParsers/hashElement.js","../src/subParsers/hashHTMLBlocks.js","../src/subParsers/hashHTMLSpans.js","../src/subParsers/hashPreCodeTags.js","../src/subParsers/headers.js","../src/subParsers/horizontalRule.js","../src/subParsers/images.js","../src/subParsers/italicsAndBold.js","../src/subParsers/lists.js","../src/subParsers/metadata.js","../src/subParsers/outdent.js","../src/subParsers/paragraphs.js","../src/subParsers/runExtension.js","../src/subParsers/spanGamut.js","../src/subParsers/strikethrough.js","../src/subParsers/stripLinkDefinitions.js","../src/subParsers/tables.js","../src/subParsers/underline.js","../src/subParsers/unescapeSpecialChars.js","../src/loader.js"],"names":[],"mappings":"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","file":"showdown.js","sourcesContent":["/**\n * Created by Tivie on 13-07-2015.\n */\n\nfunction getDefaultOpts (simple) {\n 'use strict';\n\n var defaultOptions = {\n omitExtraWLInCodeBlocks: {\n defaultValue: false,\n describe: 'Omit the default extra whiteline added to code blocks',\n type: 'boolean'\n },\n noHeaderId: {\n defaultValue: false,\n describe: 'Turn on/off generated header id',\n type: 'boolean'\n },\n prefixHeaderId: {\n defaultValue: false,\n describe: 'Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic \\'section-\\' prefix',\n type: 'string'\n },\n rawPrefixHeaderId: {\n defaultValue: false,\n describe: 'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the \" char is used in the prefix)',\n type: 'boolean'\n },\n ghCompatibleHeaderId: {\n defaultValue: false,\n describe: 'Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)',\n type: 'boolean'\n },\n rawHeaderId: {\n defaultValue: false,\n describe: 'Remove only spaces, \\' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids',\n type: 'boolean'\n },\n headerLevelStart: {\n defaultValue: false,\n describe: 'The header blocks level start',\n type: 'integer'\n },\n parseImgDimensions: {\n defaultValue: false,\n describe: 'Turn on/off image dimension parsing',\n type: 'boolean'\n },\n simplifiedAutoLink: {\n defaultValue: false,\n describe: 'Turn on/off GFM autolink style',\n type: 'boolean'\n },\n excludeTrailingPunctuationFromURLs: {\n defaultValue: false,\n describe: 'Excludes trailing punctuation from links generated with autoLinking',\n type: 'boolean'\n },\n literalMidWordUnderscores: {\n defaultValue: false,\n describe: 'Parse midword underscores as literal underscores',\n type: 'boolean'\n },\n literalMidWordAsterisks: {\n defaultValue: false,\n describe: 'Parse midword asterisks as literal asterisks',\n type: 'boolean'\n },\n strikethrough: {\n defaultValue: false,\n describe: 'Turn on/off strikethrough support',\n type: 'boolean'\n },\n tables: {\n defaultValue: false,\n describe: 'Turn on/off tables support',\n type: 'boolean'\n },\n tablesHeaderId: {\n defaultValue: false,\n describe: 'Add an id to table headers',\n type: 'boolean'\n },\n ghCodeBlocks: {\n defaultValue: true,\n describe: 'Turn on/off GFM fenced code blocks support',\n type: 'boolean'\n },\n tasklists: {\n defaultValue: false,\n describe: 'Turn on/off GFM tasklist support',\n type: 'boolean'\n },\n smoothLivePreview: {\n defaultValue: false,\n describe: 'Prevents weird effects in live previews due to incomplete input',\n type: 'boolean'\n },\n smartIndentationFix: {\n defaultValue: false,\n description: 'Tries to smartly fix indentation in es6 strings',\n type: 'boolean'\n },\n disableForced4SpacesIndentedSublists: {\n defaultValue: false,\n description: 'Disables the requirement of indenting nested sublists by 4 spaces',\n type: 'boolean'\n },\n simpleLineBreaks: {\n defaultValue: false,\n description: 'Parses simple line breaks as
    (GFM Style)',\n type: 'boolean'\n },\n requireSpaceBeforeHeadingText: {\n defaultValue: false,\n description: 'Makes adding a space between `#` and the header text mandatory (GFM Style)',\n type: 'boolean'\n },\n ghMentions: {\n defaultValue: false,\n description: 'Enables github @mentions',\n type: 'boolean'\n },\n ghMentionsLink: {\n defaultValue: 'https://github.com/{u}',\n description: 'Changes the link generated by @mentions. Only applies if ghMentions option is enabled.',\n type: 'string'\n },\n encodeEmails: {\n defaultValue: true,\n description: 'Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities',\n type: 'boolean'\n },\n openLinksInNewWindow: {\n defaultValue: false,\n description: 'Open all links in new windows',\n type: 'boolean'\n },\n backslashEscapesHTMLTags: {\n defaultValue: false,\n description: 'Support for HTML Tag escaping. ex: \\
    foo\\
    ',\n type: 'boolean'\n },\n emoji: {\n defaultValue: false,\n description: 'Enable emoji support. Ex: `this is a :smile: emoji`',\n type: 'boolean'\n },\n underline: {\n defaultValue: false,\n description: 'Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `` and ``',\n type: 'boolean'\n },\n completeHTMLDocument: {\n defaultValue: false,\n description: 'Outputs a complete html document, including ``, `` and `` tags',\n type: 'boolean'\n },\n metadata: {\n defaultValue: false,\n description: 'Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).',\n type: 'boolean'\n },\n splitAdjacentBlockquotes: {\n defaultValue: false,\n description: 'Split adjacent blockquote blocks',\n type: 'boolean'\n }\n };\n if (simple === false) {\n return JSON.parse(JSON.stringify(defaultOptions));\n }\n var ret = {};\n for (var opt in defaultOptions) {\n if (defaultOptions.hasOwnProperty(opt)) {\n ret[opt] = defaultOptions[opt].defaultValue;\n }\n }\n return ret;\n}\n\nfunction allOptionsOn () {\n 'use strict';\n var options = getDefaultOpts(true),\n ret = {};\n for (var opt in options) {\n if (options.hasOwnProperty(opt)) {\n ret[opt] = true;\n }\n }\n return ret;\n}\n","/**\n * Created by Tivie on 06-01-2015.\n */\n\n// Private properties\nvar showdown = {},\n parsers = {},\n extensions = {},\n globalOptions = getDefaultOpts(true),\n setFlavor = 'vanilla',\n flavor = {\n github: {\n omitExtraWLInCodeBlocks: true,\n simplifiedAutoLink: true,\n excludeTrailingPunctuationFromURLs: true,\n literalMidWordUnderscores: true,\n strikethrough: true,\n tables: true,\n tablesHeaderId: true,\n ghCodeBlocks: true,\n tasklists: true,\n disableForced4SpacesIndentedSublists: true,\n simpleLineBreaks: true,\n requireSpaceBeforeHeadingText: true,\n ghCompatibleHeaderId: true,\n ghMentions: true,\n backslashEscapesHTMLTags: true,\n emoji: true,\n splitAdjacentBlockquotes: true\n },\n original: {\n noHeaderId: true,\n ghCodeBlocks: false\n },\n ghost: {\n omitExtraWLInCodeBlocks: true,\n parseImgDimensions: true,\n simplifiedAutoLink: true,\n excludeTrailingPunctuationFromURLs: true,\n literalMidWordUnderscores: true,\n strikethrough: true,\n tables: true,\n tablesHeaderId: true,\n ghCodeBlocks: true,\n tasklists: true,\n smoothLivePreview: true,\n simpleLineBreaks: true,\n requireSpaceBeforeHeadingText: true,\n ghMentions: false,\n encodeEmails: true\n },\n vanilla: getDefaultOpts(true),\n allOn: allOptionsOn()\n };\n\n/**\n * helper namespace\n * @type {{}}\n */\nshowdown.helper = {};\n\n/**\n * TODO LEGACY SUPPORT CODE\n * @type {{}}\n */\nshowdown.extensions = {};\n\n/**\n * Set a global option\n * @static\n * @param {string} key\n * @param {*} value\n * @returns {showdown}\n */\nshowdown.setOption = function (key, value) {\n 'use strict';\n globalOptions[key] = value;\n return this;\n};\n\n/**\n * Get a global option\n * @static\n * @param {string} key\n * @returns {*}\n */\nshowdown.getOption = function (key) {\n 'use strict';\n return globalOptions[key];\n};\n\n/**\n * Get the global options\n * @static\n * @returns {{}}\n */\nshowdown.getOptions = function () {\n 'use strict';\n return globalOptions;\n};\n\n/**\n * Reset global options to the default values\n * @static\n */\nshowdown.resetOptions = function () {\n 'use strict';\n globalOptions = getDefaultOpts(true);\n};\n\n/**\n * Set the flavor showdown should use as default\n * @param {string} name\n */\nshowdown.setFlavor = function (name) {\n 'use strict';\n if (!flavor.hasOwnProperty(name)) {\n throw Error(name + ' flavor was not found');\n }\n showdown.resetOptions();\n var preset = flavor[name];\n setFlavor = name;\n for (var option in preset) {\n if (preset.hasOwnProperty(option)) {\n globalOptions[option] = preset[option];\n }\n }\n};\n\n/**\n * Get the currently set flavor\n * @returns {string}\n */\nshowdown.getFlavor = function () {\n 'use strict';\n return setFlavor;\n};\n\n/**\n * Get the options of a specified flavor. Returns undefined if the flavor was not found\n * @param {string} name Name of the flavor\n * @returns {{}|undefined}\n */\nshowdown.getFlavorOptions = function (name) {\n 'use strict';\n if (flavor.hasOwnProperty(name)) {\n return flavor[name];\n }\n};\n\n/**\n * Get the default options\n * @static\n * @param {boolean} [simple=true]\n * @returns {{}}\n */\nshowdown.getDefaultOptions = function (simple) {\n 'use strict';\n return getDefaultOpts(simple);\n};\n\n/**\n * Get or set a subParser\n *\n * subParser(name) - Get a registered subParser\n * subParser(name, func) - Register a subParser\n * @static\n * @param {string} name\n * @param {function} [func]\n * @returns {*}\n */\nshowdown.subParser = function (name, func) {\n 'use strict';\n if (showdown.helper.isString(name)) {\n if (typeof func !== 'undefined') {\n parsers[name] = func;\n } else {\n if (parsers.hasOwnProperty(name)) {\n return parsers[name];\n } else {\n throw Error('SubParser named ' + name + ' not registered!');\n }\n }\n }\n};\n\n/**\n * Gets or registers an extension\n * @static\n * @param {string} name\n * @param {object|function=} ext\n * @returns {*}\n */\nshowdown.extension = function (name, ext) {\n 'use strict';\n\n if (!showdown.helper.isString(name)) {\n throw Error('Extension \\'name\\' must be a string');\n }\n\n name = showdown.helper.stdExtName(name);\n\n // Getter\n if (showdown.helper.isUndefined(ext)) {\n if (!extensions.hasOwnProperty(name)) {\n throw Error('Extension named ' + name + ' is not registered!');\n }\n return extensions[name];\n\n // Setter\n } else {\n // Expand extension if it's wrapped in a function\n if (typeof ext === 'function') {\n ext = ext();\n }\n\n // Ensure extension is an array\n if (!showdown.helper.isArray(ext)) {\n ext = [ext];\n }\n\n var validExtension = validate(ext, name);\n\n if (validExtension.valid) {\n extensions[name] = ext;\n } else {\n throw Error(validExtension.error);\n }\n }\n};\n\n/**\n * Gets all extensions registered\n * @returns {{}}\n */\nshowdown.getAllExtensions = function () {\n 'use strict';\n return extensions;\n};\n\n/**\n * Remove an extension\n * @param {string} name\n */\nshowdown.removeExtension = function (name) {\n 'use strict';\n delete extensions[name];\n};\n\n/**\n * Removes all extensions\n */\nshowdown.resetExtensions = function () {\n 'use strict';\n extensions = {};\n};\n\n/**\n * Validate extension\n * @param {array} extension\n * @param {string} name\n * @returns {{valid: boolean, error: string}}\n */\nfunction validate (extension, name) {\n 'use strict';\n\n var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',\n ret = {\n valid: true,\n error: ''\n };\n\n if (!showdown.helper.isArray(extension)) {\n extension = [extension];\n }\n\n for (var i = 0; i < extension.length; ++i) {\n var baseMsg = errMsg + ' sub-extension ' + i + ': ',\n ext = extension[i];\n if (typeof ext !== 'object') {\n ret.valid = false;\n ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';\n return ret;\n }\n\n if (!showdown.helper.isString(ext.type)) {\n ret.valid = false;\n ret.error = baseMsg + 'property \"type\" must be a string, but ' + typeof ext.type + ' given';\n return ret;\n }\n\n var type = ext.type = ext.type.toLowerCase();\n\n // normalize extension type\n if (type === 'language') {\n type = ext.type = 'lang';\n }\n\n if (type === 'html') {\n type = ext.type = 'output';\n }\n\n if (type !== 'lang' && type !== 'output' && type !== 'listener') {\n ret.valid = false;\n ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: \"lang/language\", \"output/html\" or \"listener\"';\n return ret;\n }\n\n if (type === 'listener') {\n if (showdown.helper.isUndefined(ext.listeners)) {\n ret.valid = false;\n ret.error = baseMsg + '. Extensions of type \"listener\" must have a property called \"listeners\"';\n return ret;\n }\n } else {\n if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {\n ret.valid = false;\n ret.error = baseMsg + type + ' extensions must define either a \"regex\" property or a \"filter\" method';\n return ret;\n }\n }\n\n if (ext.listeners) {\n if (typeof ext.listeners !== 'object') {\n ret.valid = false;\n ret.error = baseMsg + '\"listeners\" property must be an object but ' + typeof ext.listeners + ' given';\n return ret;\n }\n for (var ln in ext.listeners) {\n if (ext.listeners.hasOwnProperty(ln)) {\n if (typeof ext.listeners[ln] !== 'function') {\n ret.valid = false;\n ret.error = baseMsg + '\"listeners\" property must be an hash of [event name]: [callback]. listeners.' + ln +\n ' must be a function but ' + typeof ext.listeners[ln] + ' given';\n return ret;\n }\n }\n }\n }\n\n if (ext.filter) {\n if (typeof ext.filter !== 'function') {\n ret.valid = false;\n ret.error = baseMsg + '\"filter\" must be a function, but ' + typeof ext.filter + ' given';\n return ret;\n }\n } else if (ext.regex) {\n if (showdown.helper.isString(ext.regex)) {\n ext.regex = new RegExp(ext.regex, 'g');\n }\n if (!(ext.regex instanceof RegExp)) {\n ret.valid = false;\n ret.error = baseMsg + '\"regex\" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';\n return ret;\n }\n if (showdown.helper.isUndefined(ext.replace)) {\n ret.valid = false;\n ret.error = baseMsg + '\"regex\" extensions must implement a replace string or function';\n return ret;\n }\n }\n }\n return ret;\n}\n\n/**\n * Validate extension\n * @param {object} ext\n * @returns {boolean}\n */\nshowdown.validateExtension = function (ext) {\n 'use strict';\n\n var validateExtension = validate(ext, null);\n if (!validateExtension.valid) {\n console.warn(validateExtension.error);\n return false;\n }\n return true;\n};\n","/**\n * showdownjs helper functions\n */\n\nif (!showdown.hasOwnProperty('helper')) {\n showdown.helper = {};\n}\n\n/**\n * Check if var is string\n * @static\n * @param {string} a\n * @returns {boolean}\n */\nshowdown.helper.isString = function (a) {\n 'use strict';\n return (typeof a === 'string' || a instanceof String);\n};\n\n/**\n * Check if var is a function\n * @static\n * @param {*} a\n * @returns {boolean}\n */\nshowdown.helper.isFunction = function (a) {\n 'use strict';\n var getType = {};\n return a && getType.toString.call(a) === '[object Function]';\n};\n\n/**\n * isArray helper function\n * @static\n * @param {*} a\n * @returns {boolean}\n */\nshowdown.helper.isArray = function (a) {\n 'use strict';\n return Array.isArray(a);\n};\n\n/**\n * Check if value is undefined\n * @static\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.\n */\nshowdown.helper.isUndefined = function (value) {\n 'use strict';\n return typeof value === 'undefined';\n};\n\n/**\n * ForEach helper function\n * Iterates over Arrays and Objects (own properties only)\n * @static\n * @param {*} obj\n * @param {function} callback Accepts 3 params: 1. value, 2. key, 3. the original array/object\n */\nshowdown.helper.forEach = function (obj, callback) {\n 'use strict';\n // check if obj is defined\n if (showdown.helper.isUndefined(obj)) {\n throw new Error('obj param is required');\n }\n\n if (showdown.helper.isUndefined(callback)) {\n throw new Error('callback param is required');\n }\n\n if (!showdown.helper.isFunction(callback)) {\n throw new Error('callback param must be a function/closure');\n }\n\n if (typeof obj.forEach === 'function') {\n obj.forEach(callback);\n } else if (showdown.helper.isArray(obj)) {\n for (var i = 0; i < obj.length; i++) {\n callback(obj[i], i, obj);\n }\n } else if (typeof (obj) === 'object') {\n for (var prop in obj) {\n if (obj.hasOwnProperty(prop)) {\n callback(obj[prop], prop, obj);\n }\n }\n } else {\n throw new Error('obj does not seem to be an array or an iterable object');\n }\n};\n\n/**\n * Standardidize extension name\n * @static\n * @param {string} s extension name\n * @returns {string}\n */\nshowdown.helper.stdExtName = function (s) {\n 'use strict';\n return s.replace(/[_?*+\\/\\\\.^-]/g, '').replace(/\\s/g, '').toLowerCase();\n};\n\nfunction escapeCharactersCallback (wholeMatch, m1) {\n 'use strict';\n var charCodeToEscape = m1.charCodeAt(0);\n return '¨E' + charCodeToEscape + 'E';\n}\n\n/**\n * Callback used to escape characters when passing through String.replace\n * @static\n * @param {string} wholeMatch\n * @param {string} m1\n * @returns {string}\n */\nshowdown.helper.escapeCharactersCallback = escapeCharactersCallback;\n\n/**\n * Escape characters in a string\n * @static\n * @param {string} text\n * @param {string} charsToEscape\n * @param {boolean} afterBackslash\n * @returns {XML|string|void|*}\n */\nshowdown.helper.escapeCharacters = function (text, charsToEscape, afterBackslash) {\n 'use strict';\n // First we have to escape the escape characters so that\n // we can build a character class out of them\n var regexString = '([' + charsToEscape.replace(/([\\[\\]\\\\])/g, '\\\\$1') + '])';\n\n if (afterBackslash) {\n regexString = '\\\\\\\\' + regexString;\n }\n\n var regex = new RegExp(regexString, 'g');\n text = text.replace(regex, escapeCharactersCallback);\n\n return text;\n};\n\nvar rgxFindMatchPos = function (str, left, right, flags) {\n 'use strict';\n var f = flags || '',\n g = f.indexOf('g') > -1,\n x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),\n l = new RegExp(left, f.replace(/g/g, '')),\n pos = [],\n t, s, m, start, end;\n\n do {\n t = 0;\n while ((m = x.exec(str))) {\n if (l.test(m[0])) {\n if (!(t++)) {\n s = x.lastIndex;\n start = s - m[0].length;\n }\n } else if (t) {\n if (!--t) {\n end = m.index + m[0].length;\n var obj = {\n left: {start: start, end: s},\n match: {start: s, end: m.index},\n right: {start: m.index, end: end},\n wholeMatch: {start: start, end: end}\n };\n pos.push(obj);\n if (!g) {\n return pos;\n }\n }\n }\n }\n } while (t && (x.lastIndex = s));\n\n return pos;\n};\n\n/**\n * matchRecursiveRegExp\n *\n * (c) 2007 Steven Levithan \n * MIT License\n *\n * Accepts a string to search, a left and right format delimiter\n * as regex patterns, and optional regex flags. Returns an array\n * of matches, allowing nested instances of left/right delimiters.\n * Use the \"g\" flag to return all matches, otherwise only the\n * first is returned. Be careful to ensure that the left and\n * right format delimiters produce mutually exclusive matches.\n * Backreferences are not supported within the right delimiter\n * due to how it is internally combined with the left delimiter.\n * When matching strings whose format delimiters are unbalanced\n * to the left or right, the output is intentionally as a\n * conventional regex library with recursion support would\n * produce, e.g. \"<\" and \">\" both produce [\"x\"] when using\n * \"<\" and \">\" as the delimiters (both strings contain a single,\n * balanced instance of \"\").\n *\n * examples:\n * matchRecursiveRegExp(\"test\", \"\\\\(\", \"\\\\)\")\n * returns: []\n * matchRecursiveRegExp(\">>t<>\", \"<\", \">\", \"g\")\n * returns: [\"t<>\", \"\"]\n * matchRecursiveRegExp(\"
    test
    \", \"]*>\", \"
    \", \"gi\")\n * returns: [\"test\"]\n */\nshowdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {\n 'use strict';\n\n var matchPos = rgxFindMatchPos (str, left, right, flags),\n results = [];\n\n for (var i = 0; i < matchPos.length; ++i) {\n results.push([\n str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),\n str.slice(matchPos[i].match.start, matchPos[i].match.end),\n str.slice(matchPos[i].left.start, matchPos[i].left.end),\n str.slice(matchPos[i].right.start, matchPos[i].right.end)\n ]);\n }\n return results;\n};\n\n/**\n *\n * @param {string} str\n * @param {string|function} replacement\n * @param {string} left\n * @param {string} right\n * @param {string} flags\n * @returns {string}\n */\nshowdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {\n 'use strict';\n\n if (!showdown.helper.isFunction(replacement)) {\n var repStr = replacement;\n replacement = function () {\n return repStr;\n };\n }\n\n var matchPos = rgxFindMatchPos(str, left, right, flags),\n finalStr = str,\n lng = matchPos.length;\n\n if (lng > 0) {\n var bits = [];\n if (matchPos[0].wholeMatch.start !== 0) {\n bits.push(str.slice(0, matchPos[0].wholeMatch.start));\n }\n for (var i = 0; i < lng; ++i) {\n bits.push(\n replacement(\n str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),\n str.slice(matchPos[i].match.start, matchPos[i].match.end),\n str.slice(matchPos[i].left.start, matchPos[i].left.end),\n str.slice(matchPos[i].right.start, matchPos[i].right.end)\n )\n );\n if (i < lng - 1) {\n bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));\n }\n }\n if (matchPos[lng - 1].wholeMatch.end < str.length) {\n bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));\n }\n finalStr = bits.join('');\n }\n return finalStr;\n};\n\n/**\n * Returns the index within the passed String object of the first occurrence of the specified regex,\n * starting the search at fromIndex. Returns -1 if the value is not found.\n *\n * @param {string} str string to search\n * @param {RegExp} regex Regular expression to search\n * @param {int} [fromIndex = 0] Index to start the search\n * @returns {Number}\n * @throws InvalidArgumentError\n */\nshowdown.helper.regexIndexOf = function (str, regex, fromIndex) {\n 'use strict';\n if (!showdown.helper.isString(str)) {\n throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';\n }\n if (regex instanceof RegExp === false) {\n throw 'InvalidArgumentError: second parameter of showdown.helper.regexIndexOf function must be an instance of RegExp';\n }\n var indexOf = str.substring(fromIndex || 0).search(regex);\n return (indexOf >= 0) ? (indexOf + (fromIndex || 0)) : indexOf;\n};\n\n/**\n * Splits the passed string object at the defined index, and returns an array composed of the two substrings\n * @param {string} str string to split\n * @param {int} index index to split string at\n * @returns {[string,string]}\n * @throws InvalidArgumentError\n */\nshowdown.helper.splitAtIndex = function (str, index) {\n 'use strict';\n if (!showdown.helper.isString(str)) {\n throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';\n }\n return [str.substring(0, index), str.substring(index)];\n};\n\n/**\n * Obfuscate an e-mail address through the use of Character Entities,\n * transforming ASCII characters into their equivalent decimal or hex entities.\n *\n * Since it has a random component, subsequent calls to this function produce different results\n *\n * @param {string} mail\n * @returns {string}\n */\nshowdown.helper.encodeEmailAddress = function (mail) {\n 'use strict';\n var encode = [\n function (ch) {\n return '&#' + ch.charCodeAt(0) + ';';\n },\n function (ch) {\n return '&#x' + ch.charCodeAt(0).toString(16) + ';';\n },\n function (ch) {\n return ch;\n }\n ];\n\n mail = mail.replace(/./g, function (ch) {\n if (ch === '@') {\n // this *must* be encoded. I insist.\n ch = encode[Math.floor(Math.random() * 2)](ch);\n } else {\n var r = Math.random();\n // roughly 10% raw, 45% hex, 45% dec\n ch = (\n r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)\n );\n }\n return ch;\n });\n\n return mail;\n};\n\n/**\n * POLYFILLS\n */\n// use this instead of builtin is undefined for IE8 compatibility\nif (typeof(console) === 'undefined') {\n console = {\n warn: function (msg) {\n 'use strict';\n alert(msg);\n },\n log: function (msg) {\n 'use strict';\n alert(msg);\n },\n error: function (msg) {\n 'use strict';\n throw msg;\n }\n };\n}\n\n/**\n * Common regexes.\n * We declare some common regexes to improve performance\n */\nshowdown.helper.regexes = {\n asteriskDashAndColon: /([*_:~])/g\n};\n\n/**\n * EMOJIS LIST\n */\nshowdown.helper.emojis = {\n '+1':'\\ud83d\\udc4d',\n '-1':'\\ud83d\\udc4e',\n '100':'\\ud83d\\udcaf',\n '1234':'\\ud83d\\udd22',\n '1st_place_medal':'\\ud83e\\udd47',\n '2nd_place_medal':'\\ud83e\\udd48',\n '3rd_place_medal':'\\ud83e\\udd49',\n '8ball':'\\ud83c\\udfb1',\n 'a':'\\ud83c\\udd70\\ufe0f',\n 'ab':'\\ud83c\\udd8e',\n 'abc':'\\ud83d\\udd24',\n 'abcd':'\\ud83d\\udd21',\n 'accept':'\\ud83c\\ude51',\n 'aerial_tramway':'\\ud83d\\udea1',\n 'airplane':'\\u2708\\ufe0f',\n 'alarm_clock':'\\u23f0',\n 'alembic':'\\u2697\\ufe0f',\n 'alien':'\\ud83d\\udc7d',\n 'ambulance':'\\ud83d\\ude91',\n 'amphora':'\\ud83c\\udffa',\n 'anchor':'\\u2693\\ufe0f',\n 'angel':'\\ud83d\\udc7c',\n 'anger':'\\ud83d\\udca2',\n 'angry':'\\ud83d\\ude20',\n 'anguished':'\\ud83d\\ude27',\n 'ant':'\\ud83d\\udc1c',\n 'apple':'\\ud83c\\udf4e',\n 'aquarius':'\\u2652\\ufe0f',\n 'aries':'\\u2648\\ufe0f',\n 'arrow_backward':'\\u25c0\\ufe0f',\n 'arrow_double_down':'\\u23ec',\n 'arrow_double_up':'\\u23eb',\n 'arrow_down':'\\u2b07\\ufe0f',\n 'arrow_down_small':'\\ud83d\\udd3d',\n 'arrow_forward':'\\u25b6\\ufe0f',\n 'arrow_heading_down':'\\u2935\\ufe0f',\n 'arrow_heading_up':'\\u2934\\ufe0f',\n 'arrow_left':'\\u2b05\\ufe0f',\n 'arrow_lower_left':'\\u2199\\ufe0f',\n 'arrow_lower_right':'\\u2198\\ufe0f',\n 'arrow_right':'\\u27a1\\ufe0f',\n 'arrow_right_hook':'\\u21aa\\ufe0f',\n 'arrow_up':'\\u2b06\\ufe0f',\n 'arrow_up_down':'\\u2195\\ufe0f',\n 'arrow_up_small':'\\ud83d\\udd3c',\n 'arrow_upper_left':'\\u2196\\ufe0f',\n 'arrow_upper_right':'\\u2197\\ufe0f',\n 'arrows_clockwise':'\\ud83d\\udd03',\n 'arrows_counterclockwise':'\\ud83d\\udd04',\n 'art':'\\ud83c\\udfa8',\n 'articulated_lorry':'\\ud83d\\ude9b',\n 'artificial_satellite':'\\ud83d\\udef0',\n 'astonished':'\\ud83d\\ude32',\n 'athletic_shoe':'\\ud83d\\udc5f',\n 'atm':'\\ud83c\\udfe7',\n 'atom_symbol':'\\u269b\\ufe0f',\n 'avocado':'\\ud83e\\udd51',\n 'b':'\\ud83c\\udd71\\ufe0f',\n 'baby':'\\ud83d\\udc76',\n 'baby_bottle':'\\ud83c\\udf7c',\n 'baby_chick':'\\ud83d\\udc24',\n 'baby_symbol':'\\ud83d\\udebc',\n 'back':'\\ud83d\\udd19',\n 'bacon':'\\ud83e\\udd53',\n 'badminton':'\\ud83c\\udff8',\n 'baggage_claim':'\\ud83d\\udec4',\n 'baguette_bread':'\\ud83e\\udd56',\n 'balance_scale':'\\u2696\\ufe0f',\n 'balloon':'\\ud83c\\udf88',\n 'ballot_box':'\\ud83d\\uddf3',\n 'ballot_box_with_check':'\\u2611\\ufe0f',\n 'bamboo':'\\ud83c\\udf8d',\n 'banana':'\\ud83c\\udf4c',\n 'bangbang':'\\u203c\\ufe0f',\n 'bank':'\\ud83c\\udfe6',\n 'bar_chart':'\\ud83d\\udcca',\n 'barber':'\\ud83d\\udc88',\n 'baseball':'\\u26be\\ufe0f',\n 'basketball':'\\ud83c\\udfc0',\n 'basketball_man':'\\u26f9\\ufe0f',\n 'basketball_woman':'\\u26f9\\ufe0f‍\\u2640\\ufe0f',\n 'bat':'\\ud83e\\udd87',\n 'bath':'\\ud83d\\udec0',\n 'bathtub':'\\ud83d\\udec1',\n 'battery':'\\ud83d\\udd0b',\n 'beach_umbrella':'\\ud83c\\udfd6',\n 'bear':'\\ud83d\\udc3b',\n 'bed':'\\ud83d\\udecf',\n 'bee':'\\ud83d\\udc1d',\n 'beer':'\\ud83c\\udf7a',\n 'beers':'\\ud83c\\udf7b',\n 'beetle':'\\ud83d\\udc1e',\n 'beginner':'\\ud83d\\udd30',\n 'bell':'\\ud83d\\udd14',\n 'bellhop_bell':'\\ud83d\\udece',\n 'bento':'\\ud83c\\udf71',\n 'biking_man':'\\ud83d\\udeb4',\n 'bike':'\\ud83d\\udeb2',\n 'biking_woman':'\\ud83d\\udeb4‍\\u2640\\ufe0f',\n 'bikini':'\\ud83d\\udc59',\n 'biohazard':'\\u2623\\ufe0f',\n 'bird':'\\ud83d\\udc26',\n 'birthday':'\\ud83c\\udf82',\n 'black_circle':'\\u26ab\\ufe0f',\n 'black_flag':'\\ud83c\\udff4',\n 'black_heart':'\\ud83d\\udda4',\n 'black_joker':'\\ud83c\\udccf',\n 'black_large_square':'\\u2b1b\\ufe0f',\n 'black_medium_small_square':'\\u25fe\\ufe0f',\n 'black_medium_square':'\\u25fc\\ufe0f',\n 'black_nib':'\\u2712\\ufe0f',\n 'black_small_square':'\\u25aa\\ufe0f',\n 'black_square_button':'\\ud83d\\udd32',\n 'blonde_man':'\\ud83d\\udc71',\n 'blonde_woman':'\\ud83d\\udc71‍\\u2640\\ufe0f',\n 'blossom':'\\ud83c\\udf3c',\n 'blowfish':'\\ud83d\\udc21',\n 'blue_book':'\\ud83d\\udcd8',\n 'blue_car':'\\ud83d\\ude99',\n 'blue_heart':'\\ud83d\\udc99',\n 'blush':'\\ud83d\\ude0a',\n 'boar':'\\ud83d\\udc17',\n 'boat':'\\u26f5\\ufe0f',\n 'bomb':'\\ud83d\\udca3',\n 'book':'\\ud83d\\udcd6',\n 'bookmark':'\\ud83d\\udd16',\n 'bookmark_tabs':'\\ud83d\\udcd1',\n 'books':'\\ud83d\\udcda',\n 'boom':'\\ud83d\\udca5',\n 'boot':'\\ud83d\\udc62',\n 'bouquet':'\\ud83d\\udc90',\n 'bowing_man':'\\ud83d\\ude47',\n 'bow_and_arrow':'\\ud83c\\udff9',\n 'bowing_woman':'\\ud83d\\ude47‍\\u2640\\ufe0f',\n 'bowling':'\\ud83c\\udfb3',\n 'boxing_glove':'\\ud83e\\udd4a',\n 'boy':'\\ud83d\\udc66',\n 'bread':'\\ud83c\\udf5e',\n 'bride_with_veil':'\\ud83d\\udc70',\n 'bridge_at_night':'\\ud83c\\udf09',\n 'briefcase':'\\ud83d\\udcbc',\n 'broken_heart':'\\ud83d\\udc94',\n 'bug':'\\ud83d\\udc1b',\n 'building_construction':'\\ud83c\\udfd7',\n 'bulb':'\\ud83d\\udca1',\n 'bullettrain_front':'\\ud83d\\ude85',\n 'bullettrain_side':'\\ud83d\\ude84',\n 'burrito':'\\ud83c\\udf2f',\n 'bus':'\\ud83d\\ude8c',\n 'business_suit_levitating':'\\ud83d\\udd74',\n 'busstop':'\\ud83d\\ude8f',\n 'bust_in_silhouette':'\\ud83d\\udc64',\n 'busts_in_silhouette':'\\ud83d\\udc65',\n 'butterfly':'\\ud83e\\udd8b',\n 'cactus':'\\ud83c\\udf35',\n 'cake':'\\ud83c\\udf70',\n 'calendar':'\\ud83d\\udcc6',\n 'call_me_hand':'\\ud83e\\udd19',\n 'calling':'\\ud83d\\udcf2',\n 'camel':'\\ud83d\\udc2b',\n 'camera':'\\ud83d\\udcf7',\n 'camera_flash':'\\ud83d\\udcf8',\n 'camping':'\\ud83c\\udfd5',\n 'cancer':'\\u264b\\ufe0f',\n 'candle':'\\ud83d\\udd6f',\n 'candy':'\\ud83c\\udf6c',\n 'canoe':'\\ud83d\\udef6',\n 'capital_abcd':'\\ud83d\\udd20',\n 'capricorn':'\\u2651\\ufe0f',\n 'car':'\\ud83d\\ude97',\n 'card_file_box':'\\ud83d\\uddc3',\n 'card_index':'\\ud83d\\udcc7',\n 'card_index_dividers':'\\ud83d\\uddc2',\n 'carousel_horse':'\\ud83c\\udfa0',\n 'carrot':'\\ud83e\\udd55',\n 'cat':'\\ud83d\\udc31',\n 'cat2':'\\ud83d\\udc08',\n 'cd':'\\ud83d\\udcbf',\n 'chains':'\\u26d3',\n 'champagne':'\\ud83c\\udf7e',\n 'chart':'\\ud83d\\udcb9',\n 'chart_with_downwards_trend':'\\ud83d\\udcc9',\n 'chart_with_upwards_trend':'\\ud83d\\udcc8',\n 'checkered_flag':'\\ud83c\\udfc1',\n 'cheese':'\\ud83e\\uddc0',\n 'cherries':'\\ud83c\\udf52',\n 'cherry_blossom':'\\ud83c\\udf38',\n 'chestnut':'\\ud83c\\udf30',\n 'chicken':'\\ud83d\\udc14',\n 'children_crossing':'\\ud83d\\udeb8',\n 'chipmunk':'\\ud83d\\udc3f',\n 'chocolate_bar':'\\ud83c\\udf6b',\n 'christmas_tree':'\\ud83c\\udf84',\n 'church':'\\u26ea\\ufe0f',\n 'cinema':'\\ud83c\\udfa6',\n 'circus_tent':'\\ud83c\\udfaa',\n 'city_sunrise':'\\ud83c\\udf07',\n 'city_sunset':'\\ud83c\\udf06',\n 'cityscape':'\\ud83c\\udfd9',\n 'cl':'\\ud83c\\udd91',\n 'clamp':'\\ud83d\\udddc',\n 'clap':'\\ud83d\\udc4f',\n 'clapper':'\\ud83c\\udfac',\n 'classical_building':'\\ud83c\\udfdb',\n 'clinking_glasses':'\\ud83e\\udd42',\n 'clipboard':'\\ud83d\\udccb',\n 'clock1':'\\ud83d\\udd50',\n 'clock10':'\\ud83d\\udd59',\n 'clock1030':'\\ud83d\\udd65',\n 'clock11':'\\ud83d\\udd5a',\n 'clock1130':'\\ud83d\\udd66',\n 'clock12':'\\ud83d\\udd5b',\n 'clock1230':'\\ud83d\\udd67',\n 'clock130':'\\ud83d\\udd5c',\n 'clock2':'\\ud83d\\udd51',\n 'clock230':'\\ud83d\\udd5d',\n 'clock3':'\\ud83d\\udd52',\n 'clock330':'\\ud83d\\udd5e',\n 'clock4':'\\ud83d\\udd53',\n 'clock430':'\\ud83d\\udd5f',\n 'clock5':'\\ud83d\\udd54',\n 'clock530':'\\ud83d\\udd60',\n 'clock6':'\\ud83d\\udd55',\n 'clock630':'\\ud83d\\udd61',\n 'clock7':'\\ud83d\\udd56',\n 'clock730':'\\ud83d\\udd62',\n 'clock8':'\\ud83d\\udd57',\n 'clock830':'\\ud83d\\udd63',\n 'clock9':'\\ud83d\\udd58',\n 'clock930':'\\ud83d\\udd64',\n 'closed_book':'\\ud83d\\udcd5',\n 'closed_lock_with_key':'\\ud83d\\udd10',\n 'closed_umbrella':'\\ud83c\\udf02',\n 'cloud':'\\u2601\\ufe0f',\n 'cloud_with_lightning':'\\ud83c\\udf29',\n 'cloud_with_lightning_and_rain':'\\u26c8',\n 'cloud_with_rain':'\\ud83c\\udf27',\n 'cloud_with_snow':'\\ud83c\\udf28',\n 'clown_face':'\\ud83e\\udd21',\n 'clubs':'\\u2663\\ufe0f',\n 'cocktail':'\\ud83c\\udf78',\n 'coffee':'\\u2615\\ufe0f',\n 'coffin':'\\u26b0\\ufe0f',\n 'cold_sweat':'\\ud83d\\ude30',\n 'comet':'\\u2604\\ufe0f',\n 'computer':'\\ud83d\\udcbb',\n 'computer_mouse':'\\ud83d\\uddb1',\n 'confetti_ball':'\\ud83c\\udf8a',\n 'confounded':'\\ud83d\\ude16',\n 'confused':'\\ud83d\\ude15',\n 'congratulations':'\\u3297\\ufe0f',\n 'construction':'\\ud83d\\udea7',\n 'construction_worker_man':'\\ud83d\\udc77',\n 'construction_worker_woman':'\\ud83d\\udc77‍\\u2640\\ufe0f',\n 'control_knobs':'\\ud83c\\udf9b',\n 'convenience_store':'\\ud83c\\udfea',\n 'cookie':'\\ud83c\\udf6a',\n 'cool':'\\ud83c\\udd92',\n 'policeman':'\\ud83d\\udc6e',\n 'copyright':'\\u00a9\\ufe0f',\n 'corn':'\\ud83c\\udf3d',\n 'couch_and_lamp':'\\ud83d\\udecb',\n 'couple':'\\ud83d\\udc6b',\n 'couple_with_heart_woman_man':'\\ud83d\\udc91',\n 'couple_with_heart_man_man':'\\ud83d\\udc68‍\\u2764\\ufe0f‍\\ud83d\\udc68',\n 'couple_with_heart_woman_woman':'\\ud83d\\udc69‍\\u2764\\ufe0f‍\\ud83d\\udc69',\n 'couplekiss_man_man':'\\ud83d\\udc68‍\\u2764\\ufe0f‍\\ud83d\\udc8b‍\\ud83d\\udc68',\n 'couplekiss_man_woman':'\\ud83d\\udc8f',\n 'couplekiss_woman_woman':'\\ud83d\\udc69‍\\u2764\\ufe0f‍\\ud83d\\udc8b‍\\ud83d\\udc69',\n 'cow':'\\ud83d\\udc2e',\n 'cow2':'\\ud83d\\udc04',\n 'cowboy_hat_face':'\\ud83e\\udd20',\n 'crab':'\\ud83e\\udd80',\n 'crayon':'\\ud83d\\udd8d',\n 'credit_card':'\\ud83d\\udcb3',\n 'crescent_moon':'\\ud83c\\udf19',\n 'cricket':'\\ud83c\\udfcf',\n 'crocodile':'\\ud83d\\udc0a',\n 'croissant':'\\ud83e\\udd50',\n 'crossed_fingers':'\\ud83e\\udd1e',\n 'crossed_flags':'\\ud83c\\udf8c',\n 'crossed_swords':'\\u2694\\ufe0f',\n 'crown':'\\ud83d\\udc51',\n 'cry':'\\ud83d\\ude22',\n 'crying_cat_face':'\\ud83d\\ude3f',\n 'crystal_ball':'\\ud83d\\udd2e',\n 'cucumber':'\\ud83e\\udd52',\n 'cupid':'\\ud83d\\udc98',\n 'curly_loop':'\\u27b0',\n 'currency_exchange':'\\ud83d\\udcb1',\n 'curry':'\\ud83c\\udf5b',\n 'custard':'\\ud83c\\udf6e',\n 'customs':'\\ud83d\\udec3',\n 'cyclone':'\\ud83c\\udf00',\n 'dagger':'\\ud83d\\udde1',\n 'dancer':'\\ud83d\\udc83',\n 'dancing_women':'\\ud83d\\udc6f',\n 'dancing_men':'\\ud83d\\udc6f‍\\u2642\\ufe0f',\n 'dango':'\\ud83c\\udf61',\n 'dark_sunglasses':'\\ud83d\\udd76',\n 'dart':'\\ud83c\\udfaf',\n 'dash':'\\ud83d\\udca8',\n 'date':'\\ud83d\\udcc5',\n 'deciduous_tree':'\\ud83c\\udf33',\n 'deer':'\\ud83e\\udd8c',\n 'department_store':'\\ud83c\\udfec',\n 'derelict_house':'\\ud83c\\udfda',\n 'desert':'\\ud83c\\udfdc',\n 'desert_island':'\\ud83c\\udfdd',\n 'desktop_computer':'\\ud83d\\udda5',\n 'male_detective':'\\ud83d\\udd75\\ufe0f',\n 'diamond_shape_with_a_dot_inside':'\\ud83d\\udca0',\n 'diamonds':'\\u2666\\ufe0f',\n 'disappointed':'\\ud83d\\ude1e',\n 'disappointed_relieved':'\\ud83d\\ude25',\n 'dizzy':'\\ud83d\\udcab',\n 'dizzy_face':'\\ud83d\\ude35',\n 'do_not_litter':'\\ud83d\\udeaf',\n 'dog':'\\ud83d\\udc36',\n 'dog2':'\\ud83d\\udc15',\n 'dollar':'\\ud83d\\udcb5',\n 'dolls':'\\ud83c\\udf8e',\n 'dolphin':'\\ud83d\\udc2c',\n 'door':'\\ud83d\\udeaa',\n 'doughnut':'\\ud83c\\udf69',\n 'dove':'\\ud83d\\udd4a',\n 'dragon':'\\ud83d\\udc09',\n 'dragon_face':'\\ud83d\\udc32',\n 'dress':'\\ud83d\\udc57',\n 'dromedary_camel':'\\ud83d\\udc2a',\n 'drooling_face':'\\ud83e\\udd24',\n 'droplet':'\\ud83d\\udca7',\n 'drum':'\\ud83e\\udd41',\n 'duck':'\\ud83e\\udd86',\n 'dvd':'\\ud83d\\udcc0',\n 'e-mail':'\\ud83d\\udce7',\n 'eagle':'\\ud83e\\udd85',\n 'ear':'\\ud83d\\udc42',\n 'ear_of_rice':'\\ud83c\\udf3e',\n 'earth_africa':'\\ud83c\\udf0d',\n 'earth_americas':'\\ud83c\\udf0e',\n 'earth_asia':'\\ud83c\\udf0f',\n 'egg':'\\ud83e\\udd5a',\n 'eggplant':'\\ud83c\\udf46',\n 'eight_pointed_black_star':'\\u2734\\ufe0f',\n 'eight_spoked_asterisk':'\\u2733\\ufe0f',\n 'electric_plug':'\\ud83d\\udd0c',\n 'elephant':'\\ud83d\\udc18',\n 'email':'\\u2709\\ufe0f',\n 'end':'\\ud83d\\udd1a',\n 'envelope_with_arrow':'\\ud83d\\udce9',\n 'euro':'\\ud83d\\udcb6',\n 'european_castle':'\\ud83c\\udff0',\n 'european_post_office':'\\ud83c\\udfe4',\n 'evergreen_tree':'\\ud83c\\udf32',\n 'exclamation':'\\u2757\\ufe0f',\n 'expressionless':'\\ud83d\\ude11',\n 'eye':'\\ud83d\\udc41',\n 'eye_speech_bubble':'\\ud83d\\udc41‍\\ud83d\\udde8',\n 'eyeglasses':'\\ud83d\\udc53',\n 'eyes':'\\ud83d\\udc40',\n 'face_with_head_bandage':'\\ud83e\\udd15',\n 'face_with_thermometer':'\\ud83e\\udd12',\n 'fist_oncoming':'\\ud83d\\udc4a',\n 'factory':'\\ud83c\\udfed',\n 'fallen_leaf':'\\ud83c\\udf42',\n 'family_man_woman_boy':'\\ud83d\\udc6a',\n 'family_man_boy':'\\ud83d\\udc68‍\\ud83d\\udc66',\n 'family_man_boy_boy':'\\ud83d\\udc68‍\\ud83d\\udc66‍\\ud83d\\udc66',\n 'family_man_girl':'\\ud83d\\udc68‍\\ud83d\\udc67',\n 'family_man_girl_boy':'\\ud83d\\udc68‍\\ud83d\\udc67‍\\ud83d\\udc66',\n 'family_man_girl_girl':'\\ud83d\\udc68‍\\ud83d\\udc67‍\\ud83d\\udc67',\n 'family_man_man_boy':'\\ud83d\\udc68‍\\ud83d\\udc68‍\\ud83d\\udc66',\n 'family_man_man_boy_boy':'\\ud83d\\udc68‍\\ud83d\\udc68‍\\ud83d\\udc66‍\\ud83d\\udc66',\n 'family_man_man_girl':'\\ud83d\\udc68‍\\ud83d\\udc68‍\\ud83d\\udc67',\n 'family_man_man_girl_boy':'\\ud83d\\udc68‍\\ud83d\\udc68‍\\ud83d\\udc67‍\\ud83d\\udc66',\n 'family_man_man_girl_girl':'\\ud83d\\udc68‍\\ud83d\\udc68‍\\ud83d\\udc67‍\\ud83d\\udc67',\n 'family_man_woman_boy_boy':'\\ud83d\\udc68‍\\ud83d\\udc69‍\\ud83d\\udc66‍\\ud83d\\udc66',\n 'family_man_woman_girl':'\\ud83d\\udc68‍\\ud83d\\udc69‍\\ud83d\\udc67',\n 'family_man_woman_girl_boy':'\\ud83d\\udc68‍\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc66',\n 'family_man_woman_girl_girl':'\\ud83d\\udc68‍\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc67',\n 'family_woman_boy':'\\ud83d\\udc69‍\\ud83d\\udc66',\n 'family_woman_boy_boy':'\\ud83d\\udc69‍\\ud83d\\udc66‍\\ud83d\\udc66',\n 'family_woman_girl':'\\ud83d\\udc69‍\\ud83d\\udc67',\n 'family_woman_girl_boy':'\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc66',\n 'family_woman_girl_girl':'\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc67',\n 'family_woman_woman_boy':'\\ud83d\\udc69‍\\ud83d\\udc69‍\\ud83d\\udc66',\n 'family_woman_woman_boy_boy':'\\ud83d\\udc69‍\\ud83d\\udc69‍\\ud83d\\udc66‍\\ud83d\\udc66',\n 'family_woman_woman_girl':'\\ud83d\\udc69‍\\ud83d\\udc69‍\\ud83d\\udc67',\n 'family_woman_woman_girl_boy':'\\ud83d\\udc69‍\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc66',\n 'family_woman_woman_girl_girl':'\\ud83d\\udc69‍\\ud83d\\udc69‍\\ud83d\\udc67‍\\ud83d\\udc67',\n 'fast_forward':'\\u23e9',\n 'fax':'\\ud83d\\udce0',\n 'fearful':'\\ud83d\\ude28',\n 'feet':'\\ud83d\\udc3e',\n 'female_detective':'\\ud83d\\udd75\\ufe0f‍\\u2640\\ufe0f',\n 'ferris_wheel':'\\ud83c\\udfa1',\n 'ferry':'\\u26f4',\n 'field_hockey':'\\ud83c\\udfd1',\n 'file_cabinet':'\\ud83d\\uddc4',\n 'file_folder':'\\ud83d\\udcc1',\n 'film_projector':'\\ud83d\\udcfd',\n 'film_strip':'\\ud83c\\udf9e',\n 'fire':'\\ud83d\\udd25',\n 'fire_engine':'\\ud83d\\ude92',\n 'fireworks':'\\ud83c\\udf86',\n 'first_quarter_moon':'\\ud83c\\udf13',\n 'first_quarter_moon_with_face':'\\ud83c\\udf1b',\n 'fish':'\\ud83d\\udc1f',\n 'fish_cake':'\\ud83c\\udf65',\n 'fishing_pole_and_fish':'\\ud83c\\udfa3',\n 'fist_raised':'\\u270a',\n 'fist_left':'\\ud83e\\udd1b',\n 'fist_right':'\\ud83e\\udd1c',\n 'flags':'\\ud83c\\udf8f',\n 'flashlight':'\\ud83d\\udd26',\n 'fleur_de_lis':'\\u269c\\ufe0f',\n 'flight_arrival':'\\ud83d\\udeec',\n 'flight_departure':'\\ud83d\\udeeb',\n 'floppy_disk':'\\ud83d\\udcbe',\n 'flower_playing_cards':'\\ud83c\\udfb4',\n 'flushed':'\\ud83d\\ude33',\n 'fog':'\\ud83c\\udf2b',\n 'foggy':'\\ud83c\\udf01',\n 'football':'\\ud83c\\udfc8',\n 'footprints':'\\ud83d\\udc63',\n 'fork_and_knife':'\\ud83c\\udf74',\n 'fountain':'\\u26f2\\ufe0f',\n 'fountain_pen':'\\ud83d\\udd8b',\n 'four_leaf_clover':'\\ud83c\\udf40',\n 'fox_face':'\\ud83e\\udd8a',\n 'framed_picture':'\\ud83d\\uddbc',\n 'free':'\\ud83c\\udd93',\n 'fried_egg':'\\ud83c\\udf73',\n 'fried_shrimp':'\\ud83c\\udf64',\n 'fries':'\\ud83c\\udf5f',\n 'frog':'\\ud83d\\udc38',\n 'frowning':'\\ud83d\\ude26',\n 'frowning_face':'\\u2639\\ufe0f',\n 'frowning_man':'\\ud83d\\ude4d‍\\u2642\\ufe0f',\n 'frowning_woman':'\\ud83d\\ude4d',\n 'middle_finger':'\\ud83d\\udd95',\n 'fuelpump':'\\u26fd\\ufe0f',\n 'full_moon':'\\ud83c\\udf15',\n 'full_moon_with_face':'\\ud83c\\udf1d',\n 'funeral_urn':'\\u26b1\\ufe0f',\n 'game_die':'\\ud83c\\udfb2',\n 'gear':'\\u2699\\ufe0f',\n 'gem':'\\ud83d\\udc8e',\n 'gemini':'\\u264a\\ufe0f',\n 'ghost':'\\ud83d\\udc7b',\n 'gift':'\\ud83c\\udf81',\n 'gift_heart':'\\ud83d\\udc9d',\n 'girl':'\\ud83d\\udc67',\n 'globe_with_meridians':'\\ud83c\\udf10',\n 'goal_net':'\\ud83e\\udd45',\n 'goat':'\\ud83d\\udc10',\n 'golf':'\\u26f3\\ufe0f',\n 'golfing_man':'\\ud83c\\udfcc\\ufe0f',\n 'golfing_woman':'\\ud83c\\udfcc\\ufe0f‍\\u2640\\ufe0f',\n 'gorilla':'\\ud83e\\udd8d',\n 'grapes':'\\ud83c\\udf47',\n 'green_apple':'\\ud83c\\udf4f',\n 'green_book':'\\ud83d\\udcd7',\n 'green_heart':'\\ud83d\\udc9a',\n 'green_salad':'\\ud83e\\udd57',\n 'grey_exclamation':'\\u2755',\n 'grey_question':'\\u2754',\n 'grimacing':'\\ud83d\\ude2c',\n 'grin':'\\ud83d\\ude01',\n 'grinning':'\\ud83d\\ude00',\n 'guardsman':'\\ud83d\\udc82',\n 'guardswoman':'\\ud83d\\udc82‍\\u2640\\ufe0f',\n 'guitar':'\\ud83c\\udfb8',\n 'gun':'\\ud83d\\udd2b',\n 'haircut_woman':'\\ud83d\\udc87',\n 'haircut_man':'\\ud83d\\udc87‍\\u2642\\ufe0f',\n 'hamburger':'\\ud83c\\udf54',\n 'hammer':'\\ud83d\\udd28',\n 'hammer_and_pick':'\\u2692',\n 'hammer_and_wrench':'\\ud83d\\udee0',\n 'hamster':'\\ud83d\\udc39',\n 'hand':'\\u270b',\n 'handbag':'\\ud83d\\udc5c',\n 'handshake':'\\ud83e\\udd1d',\n 'hankey':'\\ud83d\\udca9',\n 'hatched_chick':'\\ud83d\\udc25',\n 'hatching_chick':'\\ud83d\\udc23',\n 'headphones':'\\ud83c\\udfa7',\n 'hear_no_evil':'\\ud83d\\ude49',\n 'heart':'\\u2764\\ufe0f',\n 'heart_decoration':'\\ud83d\\udc9f',\n 'heart_eyes':'\\ud83d\\ude0d',\n 'heart_eyes_cat':'\\ud83d\\ude3b',\n 'heartbeat':'\\ud83d\\udc93',\n 'heartpulse':'\\ud83d\\udc97',\n 'hearts':'\\u2665\\ufe0f',\n 'heavy_check_mark':'\\u2714\\ufe0f',\n 'heavy_division_sign':'\\u2797',\n 'heavy_dollar_sign':'\\ud83d\\udcb2',\n 'heavy_heart_exclamation':'\\u2763\\ufe0f',\n 'heavy_minus_sign':'\\u2796',\n 'heavy_multiplication_x':'\\u2716\\ufe0f',\n 'heavy_plus_sign':'\\u2795',\n 'helicopter':'\\ud83d\\ude81',\n 'herb':'\\ud83c\\udf3f',\n 'hibiscus':'\\ud83c\\udf3a',\n 'high_brightness':'\\ud83d\\udd06',\n 'high_heel':'\\ud83d\\udc60',\n 'hocho':'\\ud83d\\udd2a',\n 'hole':'\\ud83d\\udd73',\n 'honey_pot':'\\ud83c\\udf6f',\n 'horse':'\\ud83d\\udc34',\n 'horse_racing':'\\ud83c\\udfc7',\n 'hospital':'\\ud83c\\udfe5',\n 'hot_pepper':'\\ud83c\\udf36',\n 'hotdog':'\\ud83c\\udf2d',\n 'hotel':'\\ud83c\\udfe8',\n 'hotsprings':'\\u2668\\ufe0f',\n 'hourglass':'\\u231b\\ufe0f',\n 'hourglass_flowing_sand':'\\u23f3',\n 'house':'\\ud83c\\udfe0',\n 'house_with_garden':'\\ud83c\\udfe1',\n 'houses':'\\ud83c\\udfd8',\n 'hugs':'\\ud83e\\udd17',\n 'hushed':'\\ud83d\\ude2f',\n 'ice_cream':'\\ud83c\\udf68',\n 'ice_hockey':'\\ud83c\\udfd2',\n 'ice_skate':'\\u26f8',\n 'icecream':'\\ud83c\\udf66',\n 'id':'\\ud83c\\udd94',\n 'ideograph_advantage':'\\ud83c\\ude50',\n 'imp':'\\ud83d\\udc7f',\n 'inbox_tray':'\\ud83d\\udce5',\n 'incoming_envelope':'\\ud83d\\udce8',\n 'tipping_hand_woman':'\\ud83d\\udc81',\n 'information_source':'\\u2139\\ufe0f',\n 'innocent':'\\ud83d\\ude07',\n 'interrobang':'\\u2049\\ufe0f',\n 'iphone':'\\ud83d\\udcf1',\n 'izakaya_lantern':'\\ud83c\\udfee',\n 'jack_o_lantern':'\\ud83c\\udf83',\n 'japan':'\\ud83d\\uddfe',\n 'japanese_castle':'\\ud83c\\udfef',\n 'japanese_goblin':'\\ud83d\\udc7a',\n 'japanese_ogre':'\\ud83d\\udc79',\n 'jeans':'\\ud83d\\udc56',\n 'joy':'\\ud83d\\ude02',\n 'joy_cat':'\\ud83d\\ude39',\n 'joystick':'\\ud83d\\udd79',\n 'kaaba':'\\ud83d\\udd4b',\n 'key':'\\ud83d\\udd11',\n 'keyboard':'\\u2328\\ufe0f',\n 'keycap_ten':'\\ud83d\\udd1f',\n 'kick_scooter':'\\ud83d\\udef4',\n 'kimono':'\\ud83d\\udc58',\n 'kiss':'\\ud83d\\udc8b',\n 'kissing':'\\ud83d\\ude17',\n 'kissing_cat':'\\ud83d\\ude3d',\n 'kissing_closed_eyes':'\\ud83d\\ude1a',\n 'kissing_heart':'\\ud83d\\ude18',\n 'kissing_smiling_eyes':'\\ud83d\\ude19',\n 'kiwi_fruit':'\\ud83e\\udd5d',\n 'koala':'\\ud83d\\udc28',\n 'koko':'\\ud83c\\ude01',\n 'label':'\\ud83c\\udff7',\n 'large_blue_circle':'\\ud83d\\udd35',\n 'large_blue_diamond':'\\ud83d\\udd37',\n 'large_orange_diamond':'\\ud83d\\udd36',\n 'last_quarter_moon':'\\ud83c\\udf17',\n 'last_quarter_moon_with_face':'\\ud83c\\udf1c',\n 'latin_cross':'\\u271d\\ufe0f',\n 'laughing':'\\ud83d\\ude06',\n 'leaves':'\\ud83c\\udf43',\n 'ledger':'\\ud83d\\udcd2',\n 'left_luggage':'\\ud83d\\udec5',\n 'left_right_arrow':'\\u2194\\ufe0f',\n 'leftwards_arrow_with_hook':'\\u21a9\\ufe0f',\n 'lemon':'\\ud83c\\udf4b',\n 'leo':'\\u264c\\ufe0f',\n 'leopard':'\\ud83d\\udc06',\n 'level_slider':'\\ud83c\\udf9a',\n 'libra':'\\u264e\\ufe0f',\n 'light_rail':'\\ud83d\\ude88',\n 'link':'\\ud83d\\udd17',\n 'lion':'\\ud83e\\udd81',\n 'lips':'\\ud83d\\udc44',\n 'lipstick':'\\ud83d\\udc84',\n 'lizard':'\\ud83e\\udd8e',\n 'lock':'\\ud83d\\udd12',\n 'lock_with_ink_pen':'\\ud83d\\udd0f',\n 'lollipop':'\\ud83c\\udf6d',\n 'loop':'\\u27bf',\n 'loud_sound':'\\ud83d\\udd0a',\n 'loudspeaker':'\\ud83d\\udce2',\n 'love_hotel':'\\ud83c\\udfe9',\n 'love_letter':'\\ud83d\\udc8c',\n 'low_brightness':'\\ud83d\\udd05',\n 'lying_face':'\\ud83e\\udd25',\n 'm':'\\u24c2\\ufe0f',\n 'mag':'\\ud83d\\udd0d',\n 'mag_right':'\\ud83d\\udd0e',\n 'mahjong':'\\ud83c\\udc04\\ufe0f',\n 'mailbox':'\\ud83d\\udceb',\n 'mailbox_closed':'\\ud83d\\udcea',\n 'mailbox_with_mail':'\\ud83d\\udcec',\n 'mailbox_with_no_mail':'\\ud83d\\udced',\n 'man':'\\ud83d\\udc68',\n 'man_artist':'\\ud83d\\udc68‍\\ud83c\\udfa8',\n 'man_astronaut':'\\ud83d\\udc68‍\\ud83d\\ude80',\n 'man_cartwheeling':'\\ud83e\\udd38‍\\u2642\\ufe0f',\n 'man_cook':'\\ud83d\\udc68‍\\ud83c\\udf73',\n 'man_dancing':'\\ud83d\\udd7a',\n 'man_facepalming':'\\ud83e\\udd26‍\\u2642\\ufe0f',\n 'man_factory_worker':'\\ud83d\\udc68‍\\ud83c\\udfed',\n 'man_farmer':'\\ud83d\\udc68‍\\ud83c\\udf3e',\n 'man_firefighter':'\\ud83d\\udc68‍\\ud83d\\ude92',\n 'man_health_worker':'\\ud83d\\udc68‍\\u2695\\ufe0f',\n 'man_in_tuxedo':'\\ud83e\\udd35',\n 'man_judge':'\\ud83d\\udc68‍\\u2696\\ufe0f',\n 'man_juggling':'\\ud83e\\udd39‍\\u2642\\ufe0f',\n 'man_mechanic':'\\ud83d\\udc68‍\\ud83d\\udd27',\n 'man_office_worker':'\\ud83d\\udc68‍\\ud83d\\udcbc',\n 'man_pilot':'\\ud83d\\udc68‍\\u2708\\ufe0f',\n 'man_playing_handball':'\\ud83e\\udd3e‍\\u2642\\ufe0f',\n 'man_playing_water_polo':'\\ud83e\\udd3d‍\\u2642\\ufe0f',\n 'man_scientist':'\\ud83d\\udc68‍\\ud83d\\udd2c',\n 'man_shrugging':'\\ud83e\\udd37‍\\u2642\\ufe0f',\n 'man_singer':'\\ud83d\\udc68‍\\ud83c\\udfa4',\n 'man_student':'\\ud83d\\udc68‍\\ud83c\\udf93',\n 'man_teacher':'\\ud83d\\udc68‍\\ud83c\\udfeb',\n 'man_technologist':'\\ud83d\\udc68‍\\ud83d\\udcbb',\n 'man_with_gua_pi_mao':'\\ud83d\\udc72',\n 'man_with_turban':'\\ud83d\\udc73',\n 'tangerine':'\\ud83c\\udf4a',\n 'mans_shoe':'\\ud83d\\udc5e',\n 'mantelpiece_clock':'\\ud83d\\udd70',\n 'maple_leaf':'\\ud83c\\udf41',\n 'martial_arts_uniform':'\\ud83e\\udd4b',\n 'mask':'\\ud83d\\ude37',\n 'massage_woman':'\\ud83d\\udc86',\n 'massage_man':'\\ud83d\\udc86‍\\u2642\\ufe0f',\n 'meat_on_bone':'\\ud83c\\udf56',\n 'medal_military':'\\ud83c\\udf96',\n 'medal_sports':'\\ud83c\\udfc5',\n 'mega':'\\ud83d\\udce3',\n 'melon':'\\ud83c\\udf48',\n 'memo':'\\ud83d\\udcdd',\n 'men_wrestling':'\\ud83e\\udd3c‍\\u2642\\ufe0f',\n 'menorah':'\\ud83d\\udd4e',\n 'mens':'\\ud83d\\udeb9',\n 'metal':'\\ud83e\\udd18',\n 'metro':'\\ud83d\\ude87',\n 'microphone':'\\ud83c\\udfa4',\n 'microscope':'\\ud83d\\udd2c',\n 'milk_glass':'\\ud83e\\udd5b',\n 'milky_way':'\\ud83c\\udf0c',\n 'minibus':'\\ud83d\\ude90',\n 'minidisc':'\\ud83d\\udcbd',\n 'mobile_phone_off':'\\ud83d\\udcf4',\n 'money_mouth_face':'\\ud83e\\udd11',\n 'money_with_wings':'\\ud83d\\udcb8',\n 'moneybag':'\\ud83d\\udcb0',\n 'monkey':'\\ud83d\\udc12',\n 'monkey_face':'\\ud83d\\udc35',\n 'monorail':'\\ud83d\\ude9d',\n 'moon':'\\ud83c\\udf14',\n 'mortar_board':'\\ud83c\\udf93',\n 'mosque':'\\ud83d\\udd4c',\n 'motor_boat':'\\ud83d\\udee5',\n 'motor_scooter':'\\ud83d\\udef5',\n 'motorcycle':'\\ud83c\\udfcd',\n 'motorway':'\\ud83d\\udee3',\n 'mount_fuji':'\\ud83d\\uddfb',\n 'mountain':'\\u26f0',\n 'mountain_biking_man':'\\ud83d\\udeb5',\n 'mountain_biking_woman':'\\ud83d\\udeb5‍\\u2640\\ufe0f',\n 'mountain_cableway':'\\ud83d\\udea0',\n 'mountain_railway':'\\ud83d\\ude9e',\n 'mountain_snow':'\\ud83c\\udfd4',\n 'mouse':'\\ud83d\\udc2d',\n 'mouse2':'\\ud83d\\udc01',\n 'movie_camera':'\\ud83c\\udfa5',\n 'moyai':'\\ud83d\\uddff',\n 'mrs_claus':'\\ud83e\\udd36',\n 'muscle':'\\ud83d\\udcaa',\n 'mushroom':'\\ud83c\\udf44',\n 'musical_keyboard':'\\ud83c\\udfb9',\n 'musical_note':'\\ud83c\\udfb5',\n 'musical_score':'\\ud83c\\udfbc',\n 'mute':'\\ud83d\\udd07',\n 'nail_care':'\\ud83d\\udc85',\n 'name_badge':'\\ud83d\\udcdb',\n 'national_park':'\\ud83c\\udfde',\n 'nauseated_face':'\\ud83e\\udd22',\n 'necktie':'\\ud83d\\udc54',\n 'negative_squared_cross_mark':'\\u274e',\n 'nerd_face':'\\ud83e\\udd13',\n 'neutral_face':'\\ud83d\\ude10',\n 'new':'\\ud83c\\udd95',\n 'new_moon':'\\ud83c\\udf11',\n 'new_moon_with_face':'\\ud83c\\udf1a',\n 'newspaper':'\\ud83d\\udcf0',\n 'newspaper_roll':'\\ud83d\\uddde',\n 'next_track_button':'\\u23ed',\n 'ng':'\\ud83c\\udd96',\n 'no_good_man':'\\ud83d\\ude45‍\\u2642\\ufe0f',\n 'no_good_woman':'\\ud83d\\ude45',\n 'night_with_stars':'\\ud83c\\udf03',\n 'no_bell':'\\ud83d\\udd15',\n 'no_bicycles':'\\ud83d\\udeb3',\n 'no_entry':'\\u26d4\\ufe0f',\n 'no_entry_sign':'\\ud83d\\udeab',\n 'no_mobile_phones':'\\ud83d\\udcf5',\n 'no_mouth':'\\ud83d\\ude36',\n 'no_pedestrians':'\\ud83d\\udeb7',\n 'no_smoking':'\\ud83d\\udead',\n 'non-potable_water':'\\ud83d\\udeb1',\n 'nose':'\\ud83d\\udc43',\n 'notebook':'\\ud83d\\udcd3',\n 'notebook_with_decorative_cover':'\\ud83d\\udcd4',\n 'notes':'\\ud83c\\udfb6',\n 'nut_and_bolt':'\\ud83d\\udd29',\n 'o':'\\u2b55\\ufe0f',\n 'o2':'\\ud83c\\udd7e\\ufe0f',\n 'ocean':'\\ud83c\\udf0a',\n 'octopus':'\\ud83d\\udc19',\n 'oden':'\\ud83c\\udf62',\n 'office':'\\ud83c\\udfe2',\n 'oil_drum':'\\ud83d\\udee2',\n 'ok':'\\ud83c\\udd97',\n 'ok_hand':'\\ud83d\\udc4c',\n 'ok_man':'\\ud83d\\ude46‍\\u2642\\ufe0f',\n 'ok_woman':'\\ud83d\\ude46',\n 'old_key':'\\ud83d\\udddd',\n 'older_man':'\\ud83d\\udc74',\n 'older_woman':'\\ud83d\\udc75',\n 'om':'\\ud83d\\udd49',\n 'on':'\\ud83d\\udd1b',\n 'oncoming_automobile':'\\ud83d\\ude98',\n 'oncoming_bus':'\\ud83d\\ude8d',\n 'oncoming_police_car':'\\ud83d\\ude94',\n 'oncoming_taxi':'\\ud83d\\ude96',\n 'open_file_folder':'\\ud83d\\udcc2',\n 'open_hands':'\\ud83d\\udc50',\n 'open_mouth':'\\ud83d\\ude2e',\n 'open_umbrella':'\\u2602\\ufe0f',\n 'ophiuchus':'\\u26ce',\n 'orange_book':'\\ud83d\\udcd9',\n 'orthodox_cross':'\\u2626\\ufe0f',\n 'outbox_tray':'\\ud83d\\udce4',\n 'owl':'\\ud83e\\udd89',\n 'ox':'\\ud83d\\udc02',\n 'package':'\\ud83d\\udce6',\n 'page_facing_up':'\\ud83d\\udcc4',\n 'page_with_curl':'\\ud83d\\udcc3',\n 'pager':'\\ud83d\\udcdf',\n 'paintbrush':'\\ud83d\\udd8c',\n 'palm_tree':'\\ud83c\\udf34',\n 'pancakes':'\\ud83e\\udd5e',\n 'panda_face':'\\ud83d\\udc3c',\n 'paperclip':'\\ud83d\\udcce',\n 'paperclips':'\\ud83d\\udd87',\n 'parasol_on_ground':'\\u26f1',\n 'parking':'\\ud83c\\udd7f\\ufe0f',\n 'part_alternation_mark':'\\u303d\\ufe0f',\n 'partly_sunny':'\\u26c5\\ufe0f',\n 'passenger_ship':'\\ud83d\\udef3',\n 'passport_control':'\\ud83d\\udec2',\n 'pause_button':'\\u23f8',\n 'peace_symbol':'\\u262e\\ufe0f',\n 'peach':'\\ud83c\\udf51',\n 'peanuts':'\\ud83e\\udd5c',\n 'pear':'\\ud83c\\udf50',\n 'pen':'\\ud83d\\udd8a',\n 'pencil2':'\\u270f\\ufe0f',\n 'penguin':'\\ud83d\\udc27',\n 'pensive':'\\ud83d\\ude14',\n 'performing_arts':'\\ud83c\\udfad',\n 'persevere':'\\ud83d\\ude23',\n 'person_fencing':'\\ud83e\\udd3a',\n 'pouting_woman':'\\ud83d\\ude4e',\n 'phone':'\\u260e\\ufe0f',\n 'pick':'\\u26cf',\n 'pig':'\\ud83d\\udc37',\n 'pig2':'\\ud83d\\udc16',\n 'pig_nose':'\\ud83d\\udc3d',\n 'pill':'\\ud83d\\udc8a',\n 'pineapple':'\\ud83c\\udf4d',\n 'ping_pong':'\\ud83c\\udfd3',\n 'pisces':'\\u2653\\ufe0f',\n 'pizza':'\\ud83c\\udf55',\n 'place_of_worship':'\\ud83d\\uded0',\n 'plate_with_cutlery':'\\ud83c\\udf7d',\n 'play_or_pause_button':'\\u23ef',\n 'point_down':'\\ud83d\\udc47',\n 'point_left':'\\ud83d\\udc48',\n 'point_right':'\\ud83d\\udc49',\n 'point_up':'\\u261d\\ufe0f',\n 'point_up_2':'\\ud83d\\udc46',\n 'police_car':'\\ud83d\\ude93',\n 'policewoman':'\\ud83d\\udc6e‍\\u2640\\ufe0f',\n 'poodle':'\\ud83d\\udc29',\n 'popcorn':'\\ud83c\\udf7f',\n 'post_office':'\\ud83c\\udfe3',\n 'postal_horn':'\\ud83d\\udcef',\n 'postbox':'\\ud83d\\udcee',\n 'potable_water':'\\ud83d\\udeb0',\n 'potato':'\\ud83e\\udd54',\n 'pouch':'\\ud83d\\udc5d',\n 'poultry_leg':'\\ud83c\\udf57',\n 'pound':'\\ud83d\\udcb7',\n 'rage':'\\ud83d\\ude21',\n 'pouting_cat':'\\ud83d\\ude3e',\n 'pouting_man':'\\ud83d\\ude4e‍\\u2642\\ufe0f',\n 'pray':'\\ud83d\\ude4f',\n 'prayer_beads':'\\ud83d\\udcff',\n 'pregnant_woman':'\\ud83e\\udd30',\n 'previous_track_button':'\\u23ee',\n 'prince':'\\ud83e\\udd34',\n 'princess':'\\ud83d\\udc78',\n 'printer':'\\ud83d\\udda8',\n 'purple_heart':'\\ud83d\\udc9c',\n 'purse':'\\ud83d\\udc5b',\n 'pushpin':'\\ud83d\\udccc',\n 'put_litter_in_its_place':'\\ud83d\\udeae',\n 'question':'\\u2753',\n 'rabbit':'\\ud83d\\udc30',\n 'rabbit2':'\\ud83d\\udc07',\n 'racehorse':'\\ud83d\\udc0e',\n 'racing_car':'\\ud83c\\udfce',\n 'radio':'\\ud83d\\udcfb',\n 'radio_button':'\\ud83d\\udd18',\n 'radioactive':'\\u2622\\ufe0f',\n 'railway_car':'\\ud83d\\ude83',\n 'railway_track':'\\ud83d\\udee4',\n 'rainbow':'\\ud83c\\udf08',\n 'rainbow_flag':'\\ud83c\\udff3\\ufe0f‍\\ud83c\\udf08',\n 'raised_back_of_hand':'\\ud83e\\udd1a',\n 'raised_hand_with_fingers_splayed':'\\ud83d\\udd90',\n 'raised_hands':'\\ud83d\\ude4c',\n 'raising_hand_woman':'\\ud83d\\ude4b',\n 'raising_hand_man':'\\ud83d\\ude4b‍\\u2642\\ufe0f',\n 'ram':'\\ud83d\\udc0f',\n 'ramen':'\\ud83c\\udf5c',\n 'rat':'\\ud83d\\udc00',\n 'record_button':'\\u23fa',\n 'recycle':'\\u267b\\ufe0f',\n 'red_circle':'\\ud83d\\udd34',\n 'registered':'\\u00ae\\ufe0f',\n 'relaxed':'\\u263a\\ufe0f',\n 'relieved':'\\ud83d\\ude0c',\n 'reminder_ribbon':'\\ud83c\\udf97',\n 'repeat':'\\ud83d\\udd01',\n 'repeat_one':'\\ud83d\\udd02',\n 'rescue_worker_helmet':'\\u26d1',\n 'restroom':'\\ud83d\\udebb',\n 'revolving_hearts':'\\ud83d\\udc9e',\n 'rewind':'\\u23ea',\n 'rhinoceros':'\\ud83e\\udd8f',\n 'ribbon':'\\ud83c\\udf80',\n 'rice':'\\ud83c\\udf5a',\n 'rice_ball':'\\ud83c\\udf59',\n 'rice_cracker':'\\ud83c\\udf58',\n 'rice_scene':'\\ud83c\\udf91',\n 'right_anger_bubble':'\\ud83d\\uddef',\n 'ring':'\\ud83d\\udc8d',\n 'robot':'\\ud83e\\udd16',\n 'rocket':'\\ud83d\\ude80',\n 'rofl':'\\ud83e\\udd23',\n 'roll_eyes':'\\ud83d\\ude44',\n 'roller_coaster':'\\ud83c\\udfa2',\n 'rooster':'\\ud83d\\udc13',\n 'rose':'\\ud83c\\udf39',\n 'rosette':'\\ud83c\\udff5',\n 'rotating_light':'\\ud83d\\udea8',\n 'round_pushpin':'\\ud83d\\udccd',\n 'rowing_man':'\\ud83d\\udea3',\n 'rowing_woman':'\\ud83d\\udea3‍\\u2640\\ufe0f',\n 'rugby_football':'\\ud83c\\udfc9',\n 'running_man':'\\ud83c\\udfc3',\n 'running_shirt_with_sash':'\\ud83c\\udfbd',\n 'running_woman':'\\ud83c\\udfc3‍\\u2640\\ufe0f',\n 'sa':'\\ud83c\\ude02\\ufe0f',\n 'sagittarius':'\\u2650\\ufe0f',\n 'sake':'\\ud83c\\udf76',\n 'sandal':'\\ud83d\\udc61',\n 'santa':'\\ud83c\\udf85',\n 'satellite':'\\ud83d\\udce1',\n 'saxophone':'\\ud83c\\udfb7',\n 'school':'\\ud83c\\udfeb',\n 'school_satchel':'\\ud83c\\udf92',\n 'scissors':'\\u2702\\ufe0f',\n 'scorpion':'\\ud83e\\udd82',\n 'scorpius':'\\u264f\\ufe0f',\n 'scream':'\\ud83d\\ude31',\n 'scream_cat':'\\ud83d\\ude40',\n 'scroll':'\\ud83d\\udcdc',\n 'seat':'\\ud83d\\udcba',\n 'secret':'\\u3299\\ufe0f',\n 'see_no_evil':'\\ud83d\\ude48',\n 'seedling':'\\ud83c\\udf31',\n 'selfie':'\\ud83e\\udd33',\n 'shallow_pan_of_food':'\\ud83e\\udd58',\n 'shamrock':'\\u2618\\ufe0f',\n 'shark':'\\ud83e\\udd88',\n 'shaved_ice':'\\ud83c\\udf67',\n 'sheep':'\\ud83d\\udc11',\n 'shell':'\\ud83d\\udc1a',\n 'shield':'\\ud83d\\udee1',\n 'shinto_shrine':'\\u26e9',\n 'ship':'\\ud83d\\udea2',\n 'shirt':'\\ud83d\\udc55',\n 'shopping':'\\ud83d\\udecd',\n 'shopping_cart':'\\ud83d\\uded2',\n 'shower':'\\ud83d\\udebf',\n 'shrimp':'\\ud83e\\udd90',\n 'signal_strength':'\\ud83d\\udcf6',\n 'six_pointed_star':'\\ud83d\\udd2f',\n 'ski':'\\ud83c\\udfbf',\n 'skier':'\\u26f7',\n 'skull':'\\ud83d\\udc80',\n 'skull_and_crossbones':'\\u2620\\ufe0f',\n 'sleeping':'\\ud83d\\ude34',\n 'sleeping_bed':'\\ud83d\\udecc',\n 'sleepy':'\\ud83d\\ude2a',\n 'slightly_frowning_face':'\\ud83d\\ude41',\n 'slightly_smiling_face':'\\ud83d\\ude42',\n 'slot_machine':'\\ud83c\\udfb0',\n 'small_airplane':'\\ud83d\\udee9',\n 'small_blue_diamond':'\\ud83d\\udd39',\n 'small_orange_diamond':'\\ud83d\\udd38',\n 'small_red_triangle':'\\ud83d\\udd3a',\n 'small_red_triangle_down':'\\ud83d\\udd3b',\n 'smile':'\\ud83d\\ude04',\n 'smile_cat':'\\ud83d\\ude38',\n 'smiley':'\\ud83d\\ude03',\n 'smiley_cat':'\\ud83d\\ude3a',\n 'smiling_imp':'\\ud83d\\ude08',\n 'smirk':'\\ud83d\\ude0f',\n 'smirk_cat':'\\ud83d\\ude3c',\n 'smoking':'\\ud83d\\udeac',\n 'snail':'\\ud83d\\udc0c',\n 'snake':'\\ud83d\\udc0d',\n 'sneezing_face':'\\ud83e\\udd27',\n 'snowboarder':'\\ud83c\\udfc2',\n 'snowflake':'\\u2744\\ufe0f',\n 'snowman':'\\u26c4\\ufe0f',\n 'snowman_with_snow':'\\u2603\\ufe0f',\n 'sob':'\\ud83d\\ude2d',\n 'soccer':'\\u26bd\\ufe0f',\n 'soon':'\\ud83d\\udd1c',\n 'sos':'\\ud83c\\udd98',\n 'sound':'\\ud83d\\udd09',\n 'space_invader':'\\ud83d\\udc7e',\n 'spades':'\\u2660\\ufe0f',\n 'spaghetti':'\\ud83c\\udf5d',\n 'sparkle':'\\u2747\\ufe0f',\n 'sparkler':'\\ud83c\\udf87',\n 'sparkles':'\\u2728',\n 'sparkling_heart':'\\ud83d\\udc96',\n 'speak_no_evil':'\\ud83d\\ude4a',\n 'speaker':'\\ud83d\\udd08',\n 'speaking_head':'\\ud83d\\udde3',\n 'speech_balloon':'\\ud83d\\udcac',\n 'speedboat':'\\ud83d\\udea4',\n 'spider':'\\ud83d\\udd77',\n 'spider_web':'\\ud83d\\udd78',\n 'spiral_calendar':'\\ud83d\\uddd3',\n 'spiral_notepad':'\\ud83d\\uddd2',\n 'spoon':'\\ud83e\\udd44',\n 'squid':'\\ud83e\\udd91',\n 'stadium':'\\ud83c\\udfdf',\n 'star':'\\u2b50\\ufe0f',\n 'star2':'\\ud83c\\udf1f',\n 'star_and_crescent':'\\u262a\\ufe0f',\n 'star_of_david':'\\u2721\\ufe0f',\n 'stars':'\\ud83c\\udf20',\n 'station':'\\ud83d\\ude89',\n 'statue_of_liberty':'\\ud83d\\uddfd',\n 'steam_locomotive':'\\ud83d\\ude82',\n 'stew':'\\ud83c\\udf72',\n 'stop_button':'\\u23f9',\n 'stop_sign':'\\ud83d\\uded1',\n 'stopwatch':'\\u23f1',\n 'straight_ruler':'\\ud83d\\udccf',\n 'strawberry':'\\ud83c\\udf53',\n 'stuck_out_tongue':'\\ud83d\\ude1b',\n 'stuck_out_tongue_closed_eyes':'\\ud83d\\ude1d',\n 'stuck_out_tongue_winking_eye':'\\ud83d\\ude1c',\n 'studio_microphone':'\\ud83c\\udf99',\n 'stuffed_flatbread':'\\ud83e\\udd59',\n 'sun_behind_large_cloud':'\\ud83c\\udf25',\n 'sun_behind_rain_cloud':'\\ud83c\\udf26',\n 'sun_behind_small_cloud':'\\ud83c\\udf24',\n 'sun_with_face':'\\ud83c\\udf1e',\n 'sunflower':'\\ud83c\\udf3b',\n 'sunglasses':'\\ud83d\\ude0e',\n 'sunny':'\\u2600\\ufe0f',\n 'sunrise':'\\ud83c\\udf05',\n 'sunrise_over_mountains':'\\ud83c\\udf04',\n 'surfing_man':'\\ud83c\\udfc4',\n 'surfing_woman':'\\ud83c\\udfc4‍\\u2640\\ufe0f',\n 'sushi':'\\ud83c\\udf63',\n 'suspension_railway':'\\ud83d\\ude9f',\n 'sweat':'\\ud83d\\ude13',\n 'sweat_drops':'\\ud83d\\udca6',\n 'sweat_smile':'\\ud83d\\ude05',\n 'sweet_potato':'\\ud83c\\udf60',\n 'swimming_man':'\\ud83c\\udfca',\n 'swimming_woman':'\\ud83c\\udfca‍\\u2640\\ufe0f',\n 'symbols':'\\ud83d\\udd23',\n 'synagogue':'\\ud83d\\udd4d',\n 'syringe':'\\ud83d\\udc89',\n 'taco':'\\ud83c\\udf2e',\n 'tada':'\\ud83c\\udf89',\n 'tanabata_tree':'\\ud83c\\udf8b',\n 'taurus':'\\u2649\\ufe0f',\n 'taxi':'\\ud83d\\ude95',\n 'tea':'\\ud83c\\udf75',\n 'telephone_receiver':'\\ud83d\\udcde',\n 'telescope':'\\ud83d\\udd2d',\n 'tennis':'\\ud83c\\udfbe',\n 'tent':'\\u26fa\\ufe0f',\n 'thermometer':'\\ud83c\\udf21',\n 'thinking':'\\ud83e\\udd14',\n 'thought_balloon':'\\ud83d\\udcad',\n 'ticket':'\\ud83c\\udfab',\n 'tickets':'\\ud83c\\udf9f',\n 'tiger':'\\ud83d\\udc2f',\n 'tiger2':'\\ud83d\\udc05',\n 'timer_clock':'\\u23f2',\n 'tipping_hand_man':'\\ud83d\\udc81‍\\u2642\\ufe0f',\n 'tired_face':'\\ud83d\\ude2b',\n 'tm':'\\u2122\\ufe0f',\n 'toilet':'\\ud83d\\udebd',\n 'tokyo_tower':'\\ud83d\\uddfc',\n 'tomato':'\\ud83c\\udf45',\n 'tongue':'\\ud83d\\udc45',\n 'top':'\\ud83d\\udd1d',\n 'tophat':'\\ud83c\\udfa9',\n 'tornado':'\\ud83c\\udf2a',\n 'trackball':'\\ud83d\\uddb2',\n 'tractor':'\\ud83d\\ude9c',\n 'traffic_light':'\\ud83d\\udea5',\n 'train':'\\ud83d\\ude8b',\n 'train2':'\\ud83d\\ude86',\n 'tram':'\\ud83d\\ude8a',\n 'triangular_flag_on_post':'\\ud83d\\udea9',\n 'triangular_ruler':'\\ud83d\\udcd0',\n 'trident':'\\ud83d\\udd31',\n 'triumph':'\\ud83d\\ude24',\n 'trolleybus':'\\ud83d\\ude8e',\n 'trophy':'\\ud83c\\udfc6',\n 'tropical_drink':'\\ud83c\\udf79',\n 'tropical_fish':'\\ud83d\\udc20',\n 'truck':'\\ud83d\\ude9a',\n 'trumpet':'\\ud83c\\udfba',\n 'tulip':'\\ud83c\\udf37',\n 'tumbler_glass':'\\ud83e\\udd43',\n 'turkey':'\\ud83e\\udd83',\n 'turtle':'\\ud83d\\udc22',\n 'tv':'\\ud83d\\udcfa',\n 'twisted_rightwards_arrows':'\\ud83d\\udd00',\n 'two_hearts':'\\ud83d\\udc95',\n 'two_men_holding_hands':'\\ud83d\\udc6c',\n 'two_women_holding_hands':'\\ud83d\\udc6d',\n 'u5272':'\\ud83c\\ude39',\n 'u5408':'\\ud83c\\ude34',\n 'u55b6':'\\ud83c\\ude3a',\n 'u6307':'\\ud83c\\ude2f\\ufe0f',\n 'u6708':'\\ud83c\\ude37\\ufe0f',\n 'u6709':'\\ud83c\\ude36',\n 'u6e80':'\\ud83c\\ude35',\n 'u7121':'\\ud83c\\ude1a\\ufe0f',\n 'u7533':'\\ud83c\\ude38',\n 'u7981':'\\ud83c\\ude32',\n 'u7a7a':'\\ud83c\\ude33',\n 'umbrella':'\\u2614\\ufe0f',\n 'unamused':'\\ud83d\\ude12',\n 'underage':'\\ud83d\\udd1e',\n 'unicorn':'\\ud83e\\udd84',\n 'unlock':'\\ud83d\\udd13',\n 'up':'\\ud83c\\udd99',\n 'upside_down_face':'\\ud83d\\ude43',\n 'v':'\\u270c\\ufe0f',\n 'vertical_traffic_light':'\\ud83d\\udea6',\n 'vhs':'\\ud83d\\udcfc',\n 'vibration_mode':'\\ud83d\\udcf3',\n 'video_camera':'\\ud83d\\udcf9',\n 'video_game':'\\ud83c\\udfae',\n 'violin':'\\ud83c\\udfbb',\n 'virgo':'\\u264d\\ufe0f',\n 'volcano':'\\ud83c\\udf0b',\n 'volleyball':'\\ud83c\\udfd0',\n 'vs':'\\ud83c\\udd9a',\n 'vulcan_salute':'\\ud83d\\udd96',\n 'walking_man':'\\ud83d\\udeb6',\n 'walking_woman':'\\ud83d\\udeb6‍\\u2640\\ufe0f',\n 'waning_crescent_moon':'\\ud83c\\udf18',\n 'waning_gibbous_moon':'\\ud83c\\udf16',\n 'warning':'\\u26a0\\ufe0f',\n 'wastebasket':'\\ud83d\\uddd1',\n 'watch':'\\u231a\\ufe0f',\n 'water_buffalo':'\\ud83d\\udc03',\n 'watermelon':'\\ud83c\\udf49',\n 'wave':'\\ud83d\\udc4b',\n 'wavy_dash':'\\u3030\\ufe0f',\n 'waxing_crescent_moon':'\\ud83c\\udf12',\n 'wc':'\\ud83d\\udebe',\n 'weary':'\\ud83d\\ude29',\n 'wedding':'\\ud83d\\udc92',\n 'weight_lifting_man':'\\ud83c\\udfcb\\ufe0f',\n 'weight_lifting_woman':'\\ud83c\\udfcb\\ufe0f‍\\u2640\\ufe0f',\n 'whale':'\\ud83d\\udc33',\n 'whale2':'\\ud83d\\udc0b',\n 'wheel_of_dharma':'\\u2638\\ufe0f',\n 'wheelchair':'\\u267f\\ufe0f',\n 'white_check_mark':'\\u2705',\n 'white_circle':'\\u26aa\\ufe0f',\n 'white_flag':'\\ud83c\\udff3\\ufe0f',\n 'white_flower':'\\ud83d\\udcae',\n 'white_large_square':'\\u2b1c\\ufe0f',\n 'white_medium_small_square':'\\u25fd\\ufe0f',\n 'white_medium_square':'\\u25fb\\ufe0f',\n 'white_small_square':'\\u25ab\\ufe0f',\n 'white_square_button':'\\ud83d\\udd33',\n 'wilted_flower':'\\ud83e\\udd40',\n 'wind_chime':'\\ud83c\\udf90',\n 'wind_face':'\\ud83c\\udf2c',\n 'wine_glass':'\\ud83c\\udf77',\n 'wink':'\\ud83d\\ude09',\n 'wolf':'\\ud83d\\udc3a',\n 'woman':'\\ud83d\\udc69',\n 'woman_artist':'\\ud83d\\udc69‍\\ud83c\\udfa8',\n 'woman_astronaut':'\\ud83d\\udc69‍\\ud83d\\ude80',\n 'woman_cartwheeling':'\\ud83e\\udd38‍\\u2640\\ufe0f',\n 'woman_cook':'\\ud83d\\udc69‍\\ud83c\\udf73',\n 'woman_facepalming':'\\ud83e\\udd26‍\\u2640\\ufe0f',\n 'woman_factory_worker':'\\ud83d\\udc69‍\\ud83c\\udfed',\n 'woman_farmer':'\\ud83d\\udc69‍\\ud83c\\udf3e',\n 'woman_firefighter':'\\ud83d\\udc69‍\\ud83d\\ude92',\n 'woman_health_worker':'\\ud83d\\udc69‍\\u2695\\ufe0f',\n 'woman_judge':'\\ud83d\\udc69‍\\u2696\\ufe0f',\n 'woman_juggling':'\\ud83e\\udd39‍\\u2640\\ufe0f',\n 'woman_mechanic':'\\ud83d\\udc69‍\\ud83d\\udd27',\n 'woman_office_worker':'\\ud83d\\udc69‍\\ud83d\\udcbc',\n 'woman_pilot':'\\ud83d\\udc69‍\\u2708\\ufe0f',\n 'woman_playing_handball':'\\ud83e\\udd3e‍\\u2640\\ufe0f',\n 'woman_playing_water_polo':'\\ud83e\\udd3d‍\\u2640\\ufe0f',\n 'woman_scientist':'\\ud83d\\udc69‍\\ud83d\\udd2c',\n 'woman_shrugging':'\\ud83e\\udd37‍\\u2640\\ufe0f',\n 'woman_singer':'\\ud83d\\udc69‍\\ud83c\\udfa4',\n 'woman_student':'\\ud83d\\udc69‍\\ud83c\\udf93',\n 'woman_teacher':'\\ud83d\\udc69‍\\ud83c\\udfeb',\n 'woman_technologist':'\\ud83d\\udc69‍\\ud83d\\udcbb',\n 'woman_with_turban':'\\ud83d\\udc73‍\\u2640\\ufe0f',\n 'womans_clothes':'\\ud83d\\udc5a',\n 'womans_hat':'\\ud83d\\udc52',\n 'women_wrestling':'\\ud83e\\udd3c‍\\u2640\\ufe0f',\n 'womens':'\\ud83d\\udeba',\n 'world_map':'\\ud83d\\uddfa',\n 'worried':'\\ud83d\\ude1f',\n 'wrench':'\\ud83d\\udd27',\n 'writing_hand':'\\u270d\\ufe0f',\n 'x':'\\u274c',\n 'yellow_heart':'\\ud83d\\udc9b',\n 'yen':'\\ud83d\\udcb4',\n 'yin_yang':'\\u262f\\ufe0f',\n 'yum':'\\ud83d\\ude0b',\n 'zap':'\\u26a1\\ufe0f',\n 'zipper_mouth_face':'\\ud83e\\udd10',\n 'zzz':'\\ud83d\\udca4',\n\n /* special emojis :P */\n 'octocat': '',\n 'showdown': ''\n};\n","/**\n * Created by Estevao on 31-05-2015.\n */\n\n/**\n * Showdown Converter class\n * @class\n * @param {object} [converterOptions]\n * @returns {Converter}\n */\nshowdown.Converter = function (converterOptions) {\n 'use strict';\n\n var\n /**\n * Options used by this converter\n * @private\n * @type {{}}\n */\n options = {},\n\n /**\n * Language extensions used by this converter\n * @private\n * @type {Array}\n */\n langExtensions = [],\n\n /**\n * Output modifiers extensions used by this converter\n * @private\n * @type {Array}\n */\n outputModifiers = [],\n\n /**\n * Event listeners\n * @private\n * @type {{}}\n */\n listeners = {},\n\n /**\n * The flavor set in this converter\n */\n setConvFlavor = setFlavor,\n\n /**\n * Metadata of the document\n * @type {{parsed: {}, raw: string, format: string}}\n */\n metadata = {\n parsed: {},\n raw: '',\n format: ''\n };\n\n _constructor();\n\n /**\n * Converter constructor\n * @private\n */\n function _constructor () {\n converterOptions = converterOptions || {};\n\n for (var gOpt in globalOptions) {\n if (globalOptions.hasOwnProperty(gOpt)) {\n options[gOpt] = globalOptions[gOpt];\n }\n }\n\n // Merge options\n if (typeof converterOptions === 'object') {\n for (var opt in converterOptions) {\n if (converterOptions.hasOwnProperty(opt)) {\n options[opt] = converterOptions[opt];\n }\n }\n } else {\n throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +\n ' was passed instead.');\n }\n\n if (options.extensions) {\n showdown.helper.forEach(options.extensions, _parseExtension);\n }\n }\n\n /**\n * Parse extension\n * @param {*} ext\n * @param {string} [name='']\n * @private\n */\n function _parseExtension (ext, name) {\n\n name = name || null;\n // If it's a string, the extension was previously loaded\n if (showdown.helper.isString(ext)) {\n ext = showdown.helper.stdExtName(ext);\n name = ext;\n\n // LEGACY_SUPPORT CODE\n if (showdown.extensions[ext]) {\n console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +\n 'Please inform the developer that the extension should be updated!');\n legacyExtensionLoading(showdown.extensions[ext], ext);\n return;\n // END LEGACY SUPPORT CODE\n\n } else if (!showdown.helper.isUndefined(extensions[ext])) {\n ext = extensions[ext];\n\n } else {\n throw Error('Extension \"' + ext + '\" could not be loaded. It was either not found or is not a valid extension.');\n }\n }\n\n if (typeof ext === 'function') {\n ext = ext();\n }\n\n if (!showdown.helper.isArray(ext)) {\n ext = [ext];\n }\n\n var validExt = validate(ext, name);\n if (!validExt.valid) {\n throw Error(validExt.error);\n }\n\n for (var i = 0; i < ext.length; ++i) {\n switch (ext[i].type) {\n\n case 'lang':\n langExtensions.push(ext[i]);\n break;\n\n case 'output':\n outputModifiers.push(ext[i]);\n break;\n }\n if (ext[i].hasOwnProperty('listeners')) {\n for (var ln in ext[i].listeners) {\n if (ext[i].listeners.hasOwnProperty(ln)) {\n listen(ln, ext[i].listeners[ln]);\n }\n }\n }\n }\n\n }\n\n /**\n * LEGACY_SUPPORT\n * @param {*} ext\n * @param {string} name\n */\n function legacyExtensionLoading (ext, name) {\n if (typeof ext === 'function') {\n ext = ext(new showdown.Converter());\n }\n if (!showdown.helper.isArray(ext)) {\n ext = [ext];\n }\n var valid = validate(ext, name);\n\n if (!valid.valid) {\n throw Error(valid.error);\n }\n\n for (var i = 0; i < ext.length; ++i) {\n switch (ext[i].type) {\n case 'lang':\n langExtensions.push(ext[i]);\n break;\n case 'output':\n outputModifiers.push(ext[i]);\n break;\n default:// should never reach here\n throw Error('Extension loader error: Type unrecognized!!!');\n }\n }\n }\n\n /**\n * Listen to an event\n * @param {string} name\n * @param {function} callback\n */\n function listen (name, callback) {\n if (!showdown.helper.isString(name)) {\n throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');\n }\n\n if (typeof callback !== 'function') {\n throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');\n }\n\n if (!listeners.hasOwnProperty(name)) {\n listeners[name] = [];\n }\n listeners[name].push(callback);\n }\n\n function rTrimInputText (text) {\n var rsp = text.match(/^\\s*/)[0].length,\n rgx = new RegExp('^\\\\s{0,' + rsp + '}', 'gm');\n return text.replace(rgx, '');\n }\n\n /**\n * Dispatch an event\n * @private\n * @param {string} evtName Event name\n * @param {string} text Text\n * @param {{}} options Converter Options\n * @param {{}} globals\n * @returns {string}\n */\n this._dispatch = function dispatch (evtName, text, options, globals) {\n if (listeners.hasOwnProperty(evtName)) {\n for (var ei = 0; ei < listeners[evtName].length; ++ei) {\n var nText = listeners[evtName][ei](evtName, text, this, options, globals);\n if (nText && typeof nText !== 'undefined') {\n text = nText;\n }\n }\n }\n return text;\n };\n\n /**\n * Listen to an event\n * @param {string} name\n * @param {function} callback\n * @returns {showdown.Converter}\n */\n this.listen = function (name, callback) {\n listen(name, callback);\n return this;\n };\n\n /**\n * Converts a markdown string into HTML\n * @param {string} text\n * @returns {*}\n */\n this.makeHtml = function (text) {\n //check if text is not falsy\n if (!text) {\n return text;\n }\n\n var globals = {\n gHtmlBlocks: [],\n gHtmlMdBlocks: [],\n gHtmlSpans: [],\n gUrls: {},\n gTitles: {},\n gDimensions: {},\n gListLevel: 0,\n hashLinkCounts: {},\n langExtensions: langExtensions,\n outputModifiers: outputModifiers,\n converter: this,\n ghCodeBlocks: [],\n metadata: {\n parsed: {},\n raw: '',\n format: ''\n }\n };\n\n // This lets us use ¨ trema as an escape char to avoid md5 hashes\n // The choice of character is arbitrary; anything that isn't\n // magic in Markdown will work.\n text = text.replace(/¨/g, '¨T');\n\n // Replace $ with ¨D\n // RegExp interprets $ as a special character\n // when it's in a replacement string\n text = text.replace(/\\$/g, '¨D');\n\n // Standardize line endings\n text = text.replace(/\\r\\n/g, '\\n'); // DOS to Unix\n text = text.replace(/\\r/g, '\\n'); // Mac to Unix\n\n // Stardardize line spaces (nbsp causes trouble in older browsers and some regex flavors)\n text = text.replace(/\\u00A0/g, ' ');\n\n if (options.smartIndentationFix) {\n text = rTrimInputText(text);\n }\n\n // Make sure text begins and ends with a couple of newlines:\n text = '\\n\\n' + text + '\\n\\n';\n\n // detab\n text = showdown.subParser('detab')(text, options, globals);\n\n /**\n * Strip any lines consisting only of spaces and tabs.\n * This makes subsequent regexs easier to write, because we can\n * match consecutive blank lines with /\\n+/ instead of something\n * contorted like /[ \\t]*\\n+/\n */\n text = text.replace(/^[ \\t]+$/mg, '');\n\n //run languageExtensions\n showdown.helper.forEach(langExtensions, function (ext) {\n text = showdown.subParser('runExtension')(ext, text, options, globals);\n });\n\n // run the sub parsers\n text = showdown.subParser('metadata')(text, options, globals);\n text = showdown.subParser('hashPreCodeTags')(text, options, globals);\n text = showdown.subParser('githubCodeBlocks')(text, options, globals);\n text = showdown.subParser('hashHTMLBlocks')(text, options, globals);\n text = showdown.subParser('hashCodeTags')(text, options, globals);\n text = showdown.subParser('stripLinkDefinitions')(text, options, globals);\n text = showdown.subParser('blockGamut')(text, options, globals);\n text = showdown.subParser('unhashHTMLSpans')(text, options, globals);\n text = showdown.subParser('unescapeSpecialChars')(text, options, globals);\n\n // attacklab: Restore dollar signs\n text = text.replace(/¨D/g, '$$');\n\n // attacklab: Restore tremas\n text = text.replace(/¨T/g, '¨');\n\n // render a complete html document instead of a partial if the option is enabled\n text = showdown.subParser('completeHTMLDocument')(text, options, globals);\n\n // Run output modifiers\n showdown.helper.forEach(outputModifiers, function (ext) {\n text = showdown.subParser('runExtension')(ext, text, options, globals);\n });\n\n // update metadata\n metadata = globals.metadata;\n return text;\n };\n\n /**\n * Set an option of this Converter instance\n * @param {string} key\n * @param {*} value\n */\n this.setOption = function (key, value) {\n options[key] = value;\n };\n\n /**\n * Get the option of this Converter instance\n * @param {string} key\n * @returns {*}\n */\n this.getOption = function (key) {\n return options[key];\n };\n\n /**\n * Get the options of this Converter instance\n * @returns {{}}\n */\n this.getOptions = function () {\n return options;\n };\n\n /**\n * Add extension to THIS converter\n * @param {{}} extension\n * @param {string} [name=null]\n */\n this.addExtension = function (extension, name) {\n name = name || null;\n _parseExtension(extension, name);\n };\n\n /**\n * Use a global registered extension with THIS converter\n * @param {string} extensionName Name of the previously registered extension\n */\n this.useExtension = function (extensionName) {\n _parseExtension(extensionName);\n };\n\n /**\n * Set the flavor THIS converter should use\n * @param {string} name\n */\n this.setFlavor = function (name) {\n if (!flavor.hasOwnProperty(name)) {\n throw Error(name + ' flavor was not found');\n }\n var preset = flavor[name];\n setConvFlavor = name;\n for (var option in preset) {\n if (preset.hasOwnProperty(option)) {\n options[option] = preset[option];\n }\n }\n };\n\n /**\n * Get the currently set flavor of this converter\n * @returns {string}\n */\n this.getFlavor = function () {\n return setConvFlavor;\n };\n\n /**\n * Remove an extension from THIS converter.\n * Note: This is a costly operation. It's better to initialize a new converter\n * and specify the extensions you wish to use\n * @param {Array} extension\n */\n this.removeExtension = function (extension) {\n if (!showdown.helper.isArray(extension)) {\n extension = [extension];\n }\n for (var a = 0; a < extension.length; ++a) {\n var ext = extension[a];\n for (var i = 0; i < langExtensions.length; ++i) {\n if (langExtensions[i] === ext) {\n langExtensions[i].splice(i, 1);\n }\n }\n for (var ii = 0; ii < outputModifiers.length; ++i) {\n if (outputModifiers[ii] === ext) {\n outputModifiers[ii].splice(i, 1);\n }\n }\n }\n };\n\n /**\n * Get all extension of THIS converter\n * @returns {{language: Array, output: Array}}\n */\n this.getAllExtensions = function () {\n return {\n language: langExtensions,\n output: outputModifiers\n };\n };\n\n /**\n * Get the metadata of the previously parsed document\n * @param raw\n * @returns {string|{}}\n */\n this.getMetadata = function (raw) {\n if (raw) {\n return metadata.raw;\n } else {\n return metadata.parsed;\n }\n };\n\n /**\n * Get the metadata format of the previously parsed document\n * @returns {string}\n */\n this.getMetadataFormat = function () {\n return metadata.format;\n };\n\n /**\n * Private: set a single key, value metadata pair\n * @param {string} key\n * @param {string} value\n */\n this._setMetadataPair = function (key, value) {\n metadata.parsed[key] = value;\n };\n\n /**\n * Private: set metadata format\n * @param {string} format\n */\n this._setMetadataFormat = function (format) {\n metadata.format = format;\n };\n\n /**\n * Private: set metadata raw text\n * @param {string} raw\n */\n this._setMetadataRaw = function (raw) {\n metadata.raw = raw;\n };\n};\n","/**\n * Turn Markdown link shortcuts into XHTML tags.\n */\nshowdown.subParser('anchors', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('anchors.before', text, options, globals);\n\n var writeAnchorTag = function (wholeMatch, linkText, linkId, url, m5, m6, title) {\n if (showdown.helper.isUndefined(title)) {\n title = '';\n }\n linkId = linkId.toLowerCase();\n\n // Special case for explicit empty url\n if (wholeMatch.search(/\\(? ?(['\"].*['\"])?\\)$/m) > -1) {\n url = '';\n } else if (!url) {\n if (!linkId) {\n // lower-case and turn embedded newlines into spaces\n linkId = linkText.toLowerCase().replace(/ ?\\n/g, ' ');\n }\n url = '#' + linkId;\n\n if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {\n url = globals.gUrls[linkId];\n if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {\n title = globals.gTitles[linkId];\n }\n } else {\n return wholeMatch;\n }\n }\n\n //url = showdown.helper.escapeCharacters(url, '*_', false); // replaced line to improve performance\n url = url.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback);\n\n var result = '';\n\n return result;\n };\n\n // First, handle reference-style links: [link text] [id]\n text = text.replace(/\\[((?:\\[[^\\]]*]|[^\\[\\]])*)] ?(?:\\n *)?\\[(.*?)]()()()()/g, writeAnchorTag);\n\n // Next, inline-style links: [link text](url \"optional title\")\n // cases with crazy urls like ./image/cat1).png\n text = text.replace(/\\[((?:\\[[^\\]]*]|[^\\[\\]])*)]()[ \\t]*\\([ \\t]?<([^>]*)>(?:[ \\t]*(([\"'])([^\"]*?)\\5))?[ \\t]?\\)/g,\n writeAnchorTag);\n\n // normal cases\n text = text.replace(/\\[((?:\\[[^\\]]*]|[^\\[\\]])*)]()[ \\t]*\\([ \\t]??(?:[ \\t]*(([\"'])([^\"]*?)\\5))?[ \\t]?\\)/g,\n writeAnchorTag);\n\n // handle reference-style shortcuts: [link text]\n // These must come last in case you've also got [link test][1]\n // or [link test](/foo)\n text = text.replace(/\\[([^\\[\\]]+)]()()()()()/g, writeAnchorTag);\n\n // Lastly handle GithubMentions if option is enabled\n if (options.ghMentions) {\n text = text.replace(/(^|\\s)(\\\\)?(@([a-z\\d\\-]+))(?=[.!?;,[\\]()]|\\s|$)/gmi, function (wm, st, escape, mentions, username) {\n if (escape === '\\\\') {\n return st + mentions;\n }\n\n //check if options.ghMentionsLink is a string\n if (!showdown.helper.isString(options.ghMentionsLink)) {\n throw new Error('ghMentionsLink option must be a string');\n }\n var lnk = options.ghMentionsLink.replace(/\\{u}/g, username),\n target = '';\n if (options.openLinksInNewWindow) {\n target = ' target=\"¨E95Eblank\"';\n }\n return st + '' + mentions + '';\n });\n }\n\n text = globals.converter._dispatch('anchors.after', text, options, globals);\n return text;\n});\n","// url allowed chars [a-z\\d_.~:/?#[]@!$&'()*+,;=-]\n\nvar simpleURLRegex = /([*~_]+|\\b)(((https?|ftp|dict):\\/\\/|www\\.)[^'\">\\s]+?\\.[^'\">\\s]+?)()(\\1)?(?=\\s|$)(?![\"<>])/gi,\n simpleURLRegex2 = /([*~_]+|\\b)(((https?|ftp|dict):\\/\\/|www\\.)[^'\">\\s]+\\.[^'\">\\s]+?)([.!?,()\\[\\]])?(\\1)?(?=\\s|$)(?![\"<>])/gi,\n delimUrlRegex = /()<(((https?|ftp|dict):\\/\\/|www\\.)[^'\">\\s]+)()>()/gi,\n simpleMailRegex = /(^|\\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]+)(?=$|\\s)/gmi,\n delimMailRegex = /<()(?:mailto:)?([-.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]+)>/gi,\n\n replaceLink = function (options) {\n 'use strict';\n return function (wm, leadingMagicChars, link, m2, m3, trailingPunctuation, trailingMagicChars) {\n link = link.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback);\n var lnkTxt = link,\n append = '',\n target = '',\n lmc = leadingMagicChars || '',\n tmc = trailingMagicChars || '';\n if (/^www\\./i.test(link)) {\n link = link.replace(/^www\\./i, 'http://www.');\n }\n if (options.excludeTrailingPunctuationFromURLs && trailingPunctuation) {\n append = trailingPunctuation;\n }\n if (options.openLinksInNewWindow) {\n target = ' target=\"¨E95Eblank\"';\n }\n return lmc + '' + lnkTxt + '' + append + tmc;\n };\n },\n\n replaceMail = function (options, globals) {\n 'use strict';\n return function (wholeMatch, b, mail) {\n var href = 'mailto:';\n b = b || '';\n mail = showdown.subParser('unescapeSpecialChars')(mail, options, globals);\n if (options.encodeEmails) {\n href = showdown.helper.encodeEmailAddress(href + mail);\n mail = showdown.helper.encodeEmailAddress(mail);\n } else {\n href = href + mail;\n }\n return b + '' + mail + '';\n };\n };\n\nshowdown.subParser('autoLinks', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('autoLinks.before', text, options, globals);\n\n text = text.replace(delimUrlRegex, replaceLink(options));\n text = text.replace(delimMailRegex, replaceMail(options, globals));\n\n text = globals.converter._dispatch('autoLinks.after', text, options, globals);\n\n return text;\n});\n\nshowdown.subParser('simplifiedAutoLinks', function (text, options, globals) {\n 'use strict';\n\n if (!options.simplifiedAutoLink) {\n return text;\n }\n\n text = globals.converter._dispatch('simplifiedAutoLinks.before', text, options, globals);\n\n if (options.excludeTrailingPunctuationFromURLs) {\n text = text.replace(simpleURLRegex2, replaceLink(options));\n } else {\n text = text.replace(simpleURLRegex, replaceLink(options));\n }\n text = text.replace(simpleMailRegex, replaceMail(options, globals));\n\n text = globals.converter._dispatch('simplifiedAutoLinks.after', text, options, globals);\n\n return text;\n});\n","/**\n * These are all the transformations that form block-level\n * tags like paragraphs, headers, and list items.\n */\nshowdown.subParser('blockGamut', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('blockGamut.before', text, options, globals);\n\n // we parse blockquotes first so that we can have headings and hrs\n // inside blockquotes\n text = showdown.subParser('blockQuotes')(text, options, globals);\n text = showdown.subParser('headers')(text, options, globals);\n\n // Do Horizontal Rules:\n text = showdown.subParser('horizontalRule')(text, options, globals);\n\n text = showdown.subParser('lists')(text, options, globals);\n text = showdown.subParser('codeBlocks')(text, options, globals);\n text = showdown.subParser('tables')(text, options, globals);\n\n // We already ran _HashHTMLBlocks() before, in Markdown(), but that\n // was to escape raw HTML in the original Markdown source. This time,\n // we're escaping the markup we've just created, so that we don't wrap\n //

    tags around block-level tags.\n text = showdown.subParser('hashHTMLBlocks')(text, options, globals);\n text = showdown.subParser('paragraphs')(text, options, globals);\n\n text = globals.converter._dispatch('blockGamut.after', text, options, globals);\n\n return text;\n});\n","showdown.subParser('blockQuotes', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('blockQuotes.before', text, options, globals);\n\n // add a couple extra lines after the text and endtext mark\n text = text + '\\n\\n';\n\n var rgx = /(^ {0,3}>[ \\t]?.+\\n(.+\\n)*\\n*)+/gm;\n\n if (options.splitAdjacentBlockquotes) {\n rgx = /^ {0,3}>[\\s\\S]*?(?:\\n\\n)/gm;\n }\n\n text = text.replace(rgx, function (bq) {\n // attacklab: hack around Konqueror 3.5.4 bug:\n // \"----------bug\".replace(/^-/g,\"\") == \"bug\"\n bq = bq.replace(/^[ \\t]*>[ \\t]?/gm, ''); // trim one level of quoting\n\n // attacklab: clean up hack\n bq = bq.replace(/¨0/g, '');\n\n bq = bq.replace(/^[ \\t]+$/gm, ''); // trim whitespace-only lines\n bq = showdown.subParser('githubCodeBlocks')(bq, options, globals);\n bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse\n\n bq = bq.replace(/(^|\\n)/g, '$1 ');\n // These leading spaces screw with

     content, so we need to fix that:\n    bq = bq.replace(/(\\s*
    [^\\r]+?<\\/pre>)/gm, function (wholeMatch, m1) {\n      var pre = m1;\n      // attacklab: hack around Konqueror 3.5.4 bug:\n      pre = pre.replace(/^  /mg, '¨0');\n      pre = pre.replace(/¨0/g, '');\n      return pre;\n    });\n\n    return showdown.subParser('hashBlock')('
    \\n' + bq + '\\n
    ', options, globals);\n });\n\n text = globals.converter._dispatch('blockQuotes.after', text, options, globals);\n return text;\n});\n","/**\n * Process Markdown `
    ` blocks.\n */\nshowdown.subParser('codeBlocks', function (text, options, globals) {\n  'use strict';\n\n  text = globals.converter._dispatch('codeBlocks.before', text, options, globals);\n\n  // sentinel workarounds for lack of \\A and \\Z, safari\\khtml bug\n  text += '¨0';\n\n  var pattern = /(?:\\n\\n|^)((?:(?:[ ]{4}|\\t).*\\n+)+)(\\n*[ ]{0,3}[^ \\t\\n]|(?=¨0))/g;\n  text = text.replace(pattern, function (wholeMatch, m1, m2) {\n    var codeblock = m1,\n        nextChar = m2,\n        end = '\\n';\n\n    codeblock = showdown.subParser('outdent')(codeblock, options, globals);\n    codeblock = showdown.subParser('encodeCode')(codeblock, options, globals);\n    codeblock = showdown.subParser('detab')(codeblock, options, globals);\n    codeblock = codeblock.replace(/^\\n+/g, ''); // trim leading newlines\n    codeblock = codeblock.replace(/\\n+$/g, ''); // trim trailing newlines\n\n    if (options.omitExtraWLInCodeBlocks) {\n      end = '';\n    }\n\n    codeblock = '
    ' + codeblock + end + '
    ';\n\n return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;\n });\n\n // strip sentinel\n text = text.replace(/¨0/, '');\n\n text = globals.converter._dispatch('codeBlocks.after', text, options, globals);\n return text;\n});\n","/**\n *\n * * Backtick quotes are used for spans.\n *\n * * You can use multiple backticks as the delimiters if you want to\n * include literal backticks in the code span. So, this input:\n *\n * Just type ``foo `bar` baz`` at the prompt.\n *\n * Will translate to:\n *\n *

    Just type foo `bar` baz at the prompt.

    \n *\n * There's no arbitrary limit to the number of backticks you\n * can use as delimters. If you need three consecutive backticks\n * in your code, use four for delimiters, etc.\n *\n * * You can use spaces to get literal backticks at the edges:\n *\n * ... type `` `bar` `` ...\n *\n * Turns to:\n *\n * ... type `bar` ...\n */\nshowdown.subParser('codeSpans', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('codeSpans.before', text, options, globals);\n\n if (typeof(text) === 'undefined') {\n text = '';\n }\n text = text.replace(/(^|[^\\\\])(`+)([^\\r]*?[^`])\\2(?!`)/gm,\n function (wholeMatch, m1, m2, m3) {\n var c = m3;\n c = c.replace(/^([ \\t]*)/g, '');\t// leading whitespace\n c = c.replace(/[ \\t]*$/g, '');\t// trailing whitespace\n c = showdown.subParser('encodeCode')(c, options, globals);\n c = m1 + '' + c + '';\n c = showdown.subParser('hashHTMLSpans')(c, options, globals);\n return c;\n }\n );\n\n text = globals.converter._dispatch('codeSpans.after', text, options, globals);\n return text;\n});\n","/**\n * Turn Markdown link shortcuts into XHTML tags.\n */\nshowdown.subParser('completeHTMLDocument', function (text, options, globals) {\n 'use strict';\n\n if (!options.completeHTMLDocument) {\n return text;\n }\n\n text = globals.converter._dispatch('completeHTMLDocument.before', text, options, globals);\n\n var doctype = 'html',\n doctypeParsed = '\\n',\n title = '',\n charset = '\\n',\n lang = '',\n metadata = '';\n\n if (typeof globals.metadata.parsed.doctype !== 'undefined') {\n doctypeParsed = '\\n';\n doctype = globals.metadata.parsed.doctype.toString().toLowerCase();\n if (doctype === 'html' || doctype === 'html5') {\n charset = '';\n }\n }\n\n for (var meta in globals.metadata.parsed) {\n if (globals.metadata.parsed.hasOwnProperty(meta)) {\n switch (meta.toLowerCase()) {\n case 'doctype':\n break;\n\n case 'title':\n title = '' + globals.metadata.parsed.title + '\\n';\n break;\n\n case 'charset':\n if (doctype === 'html' || doctype === 'html5') {\n charset = '\\n';\n } else {\n charset = '\\n';\n }\n break;\n\n case 'language':\n case 'lang':\n lang = ' lang=\"' + globals.metadata.parsed[meta] + '\"';\n metadata += '\\n';\n break;\n\n default:\n metadata += '\\n';\n }\n }\n }\n\n text = doctypeParsed + '\\n\\n' + title + charset + metadata + '\\n\\n' + text.trim() + '\\n\\n';\n\n text = globals.converter._dispatch('completeHTMLDocument.after', text, options, globals);\n return text;\n});\n","/**\n * Convert all tabs to spaces\n */\nshowdown.subParser('detab', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('detab.before', text, options, globals);\n\n // expand first n-1 tabs\n text = text.replace(/\\t(?=\\t)/g, ' '); // g_tab_width\n\n // replace the nth with two sentinels\n text = text.replace(/\\t/g, '¨A¨B');\n\n // use the sentinel to anchor our regex so it doesn't explode\n text = text.replace(/¨B(.+?)¨A/g, function (wholeMatch, m1) {\n var leadingText = m1,\n numSpaces = 4 - leadingText.length % 4; // g_tab_width\n\n // there *must* be a better way to do this:\n for (var i = 0; i < numSpaces; i++) {\n leadingText += ' ';\n }\n\n return leadingText;\n });\n\n // clean up sentinels\n text = text.replace(/¨A/g, ' '); // g_tab_width\n text = text.replace(/¨B/g, '');\n\n text = globals.converter._dispatch('detab.after', text, options, globals);\n return text;\n});\n","showdown.subParser('ellipsis', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('ellipsis.before', text, options, globals);\n\n text = text.replace(/\\.\\.\\./g, '…');\n\n text = globals.converter._dispatch('ellipsis.after', text, options, globals);\n\n return text;\n});\n","/**\n * These are all the transformations that occur *within* block-level\n * tags like paragraphs, headers, and list items.\n */\nshowdown.subParser('emoji', function (text, options, globals) {\n 'use strict';\n\n if (!options.emoji) {\n return text;\n }\n\n text = globals.converter._dispatch('emoji.before', text, options, globals);\n\n var emojiRgx = /:([\\S]+?):/g;\n\n text = text.replace(emojiRgx, function (wm, emojiCode) {\n if (showdown.helper.emojis.hasOwnProperty(emojiCode)) {\n return showdown.helper.emojis[emojiCode];\n }\n return wm;\n });\n\n text = globals.converter._dispatch('emoji.after', text, options, globals);\n\n return text;\n});\n","/**\n * Smart processing for ampersands and angle brackets that need to be encoded.\n */\nshowdown.subParser('encodeAmpsAndAngles', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('encodeAmpsAndAngles.before', text, options, globals);\n\n // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:\n // http://bumppo.net/projects/amputator/\n text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\\w+);)/g, '&');\n\n // Encode naked <'s\n text = text.replace(/<(?![a-z\\/?$!])/gi, '<');\n\n // Encode <\n text = text.replace(/\n text = text.replace(/>/g, '>');\n\n text = globals.converter._dispatch('encodeAmpsAndAngles.after', text, options, globals);\n return text;\n});\n","/**\n * Returns the string, with after processing the following backslash escape sequences.\n *\n * attacklab: The polite way to do this is with the new escapeCharacters() function:\n *\n * text = escapeCharacters(text,\"\\\\\",true);\n * text = escapeCharacters(text,\"`*_{}[]()>#+-.!\",true);\n *\n * ...but we're sidestepping its use of the (slow) RegExp constructor\n * as an optimization for Firefox. This function gets called a LOT.\n */\nshowdown.subParser('encodeBackslashEscapes', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('encodeBackslashEscapes.before', text, options, globals);\n\n text = text.replace(/\\\\(\\\\)/g, showdown.helper.escapeCharactersCallback);\n text = text.replace(/\\\\([`*_{}\\[\\]()>#+.!~=|-])/g, showdown.helper.escapeCharactersCallback);\n\n text = globals.converter._dispatch('encodeBackslashEscapes.after', text, options, globals);\n return text;\n});\n","/**\n * Encode/escape certain characters inside Markdown code runs.\n * The point is that in code, these characters are literals,\n * and lose their special Markdown meanings.\n */\nshowdown.subParser('encodeCode', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('encodeCode.before', text, options, globals);\n\n // Encode all ampersands; HTML entities are not\n // entities within a Markdown code span.\n text = text\n .replace(/&/g, '&')\n // Do the angle bracket song and dance:\n .replace(//g, '>')\n // Now, escape characters that are magic in Markdown:\n .replace(/([*_{}\\[\\]\\\\=~-])/g, showdown.helper.escapeCharactersCallback);\n\n text = globals.converter._dispatch('encodeCode.after', text, options, globals);\n return text;\n});\n","/**\n * Within tags -- meaning between < and > -- encode [\\ ` * _ ~ =] so they\n * don't conflict with their use in Markdown for code, italics and strong.\n */\nshowdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('escapeSpecialCharsWithinTagAttributes.before', text, options, globals);\n\n // Build a regex to find HTML tags.\n var tags = /<\\/?[a-z\\d_:-]+(?:[\\s]+[\\s\\S]+?)?>/gi,\n comments = /-]|-[^>])(?:[^-]|-[^-])*)--)>/gi;\n\n text = text.replace(tags, function (wholeMatch) {\n return wholeMatch\n .replace(/(.)<\\/?code>(?=.)/g, '$1`')\n .replace(/([\\\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);\n });\n\n text = text.replace(comments, function (wholeMatch) {\n return wholeMatch\n .replace(/([\\\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);\n });\n\n text = globals.converter._dispatch('escapeSpecialCharsWithinTagAttributes.after', text, options, globals);\n return text;\n});\n","/**\n * Handle github codeblocks prior to running HashHTML so that\n * HTML contained within the codeblock gets escaped properly\n * Example:\n * ```ruby\n * def hello_world(x)\n * puts \"Hello, #{x}\"\n * end\n * ```\n */\nshowdown.subParser('githubCodeBlocks', function (text, options, globals) {\n 'use strict';\n\n // early exit if option is not enabled\n if (!options.ghCodeBlocks) {\n return text;\n }\n\n text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals);\n\n text += '¨0';\n\n text = text.replace(/(?:^|\\n)(```+|~~~+)([^\\s`~]*)\\n([\\s\\S]*?)\\n\\1/g, function (wholeMatch, delim, language, codeblock) {\n var end = (options.omitExtraWLInCodeBlocks) ? '' : '\\n';\n\n // First parse the github code block\n codeblock = showdown.subParser('encodeCode')(codeblock, options, globals);\n codeblock = showdown.subParser('detab')(codeblock, options, globals);\n codeblock = codeblock.replace(/^\\n+/g, ''); // trim leading newlines\n codeblock = codeblock.replace(/\\n+$/g, ''); // trim trailing whitespace\n\n codeblock = '
    ' + codeblock + end + '
    ';\n\n codeblock = showdown.subParser('hashBlock')(codeblock, options, globals);\n\n // Since GHCodeblocks can be false positives, we need to\n // store the primitive text and the parsed text in a global var,\n // and then return a token\n return '\\n\\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\\n\\n';\n });\n\n // attacklab: strip sentinel\n text = text.replace(/¨0/, '');\n\n return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals);\n});\n","showdown.subParser('hashBlock', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('hashBlock.before', text, options, globals);\n text = text.replace(/(^\\n+|\\n+$)/g, '');\n text = '\\n\\n¨K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\\n\\n';\n text = globals.converter._dispatch('hashBlock.after', text, options, globals);\n return text;\n});\n","/**\n * Hash and escape elements that should not be parsed as markdown\n */\nshowdown.subParser('hashCodeTags', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('hashCodeTags.before', text, options, globals);\n\n var repFunc = function (wholeMatch, match, left, right) {\n var codeblock = left + showdown.subParser('encodeCode')(match, options, globals) + right;\n return '¨C' + (globals.gHtmlSpans.push(codeblock) - 1) + 'C';\n };\n\n // Hash naked \n text = showdown.helper.replaceRecursiveRegExp(text, repFunc, ']*>', '', 'gim');\n\n text = globals.converter._dispatch('hashCodeTags.after', text, options, globals);\n return text;\n});\n","showdown.subParser('hashElement', function (text, options, globals) {\n 'use strict';\n\n return function (wholeMatch, m1) {\n var blockText = m1;\n\n // Undo double lines\n blockText = blockText.replace(/\\n\\n/g, '\\n');\n blockText = blockText.replace(/^\\n/, '');\n\n // strip trailing blank lines\n blockText = blockText.replace(/\\n+$/g, '');\n\n // Replace the element text with a marker (\"¨KxK\" where x is its key)\n blockText = '\\n\\n¨K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\\n\\n';\n\n return blockText;\n };\n});\n","showdown.subParser('hashHTMLBlocks', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('hashHTMLBlocks.before', text, options, globals);\n\n var blockTags = [\n 'pre',\n 'div',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'blockquote',\n 'table',\n 'dl',\n 'ol',\n 'ul',\n 'script',\n 'noscript',\n 'form',\n 'fieldset',\n 'iframe',\n 'math',\n 'style',\n 'section',\n 'header',\n 'footer',\n 'nav',\n 'article',\n 'aside',\n 'address',\n 'audio',\n 'canvas',\n 'figure',\n 'hgroup',\n 'output',\n 'video',\n 'p'\n ],\n repFunc = function (wholeMatch, match, left, right) {\n var txt = wholeMatch;\n // check if this html element is marked as markdown\n // if so, it's contents should be parsed as markdown\n if (left.search(/\\bmarkdown\\b/) !== -1) {\n txt = left + globals.converter.makeHtml(match) + right;\n }\n return '\\n\\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\\n\\n';\n };\n\n if (options.backslashEscapesHTMLTags) {\n // encode backslash escaped HTML tags\n text = text.replace(/\\\\<(\\/?[^>]+?)>/g, function (wm, inside) {\n return '<' + inside + '>';\n });\n }\n\n // hash HTML Blocks\n for (var i = 0; i < blockTags.length; ++i) {\n\n var opTagPos,\n rgx1 = new RegExp('^ {0,3}(<' + blockTags[i] + '\\\\b[^>]*>)', 'im'),\n patLeft = '<' + blockTags[i] + '\\\\b[^>]*>',\n patRight = '';\n // 1. Look for the first position of the first opening HTML tag in the text\n while ((opTagPos = showdown.helper.regexIndexOf(text, rgx1)) !== -1) {\n\n // if the HTML tag is \\ escaped, we need to escape it and break\n\n\n //2. Split the text in that position\n var subTexts = showdown.helper.splitAtIndex(text, opTagPos),\n //3. Match recursively\n newSubText1 = showdown.helper.replaceRecursiveRegExp(subTexts[1], repFunc, patLeft, patRight, 'im');\n\n // prevent an infinite loop\n if (newSubText1 === subTexts[1]) {\n break;\n }\n text = subTexts[0].concat(newSubText1);\n }\n }\n // HR SPECIAL CASE\n text = text.replace(/(\\n {0,3}(<(hr)\\b([^<>])*?\\/?>)[ \\t]*(?=\\n{2,}))/g,\n showdown.subParser('hashElement')(text, options, globals));\n\n // Special case for standalone HTML comments\n text = showdown.helper.replaceRecursiveRegExp(text, function (txt) {\n return '\\n\\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\\n\\n';\n }, '^ {0,3}', 'gm');\n\n // PHP and ASP-style processor instructions ( and <%...%>)\n text = text.replace(/(?:\\n\\n)( {0,3}(?:<([?%])[^\\r]*?\\2>)[ \\t]*(?=\\n{2,}))/g,\n showdown.subParser('hashElement')(text, options, globals));\n\n text = globals.converter._dispatch('hashHTMLBlocks.after', text, options, globals);\n return text;\n});\n","/**\n * Hash span elements that should not be parsed as markdown\n */\nshowdown.subParser('hashHTMLSpans', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('hashHTMLSpans.before', text, options, globals);\n\n function hashHTMLSpan (html) {\n return '¨C' + (globals.gHtmlSpans.push(html) - 1) + 'C';\n }\n\n // Hash Self Closing tags\n text = text.replace(/<[^>]+?\\/>/gi, function (wm) {\n return hashHTMLSpan(wm);\n });\n\n // Hash tags without properties\n text = text.replace(/<([^>]+?)>[\\s\\S]*?<\\/\\1>/g, function (wm) {\n return hashHTMLSpan(wm);\n });\n\n // Hash tags with properties\n text = text.replace(/<([^>]+?)\\s[^>]+?>[\\s\\S]*?<\\/\\1>/g, function (wm) {\n return hashHTMLSpan(wm);\n });\n\n // Hash self closing tags without />\n text = text.replace(/<[^>]+?>/gi, function (wm) {\n return hashHTMLSpan(wm);\n });\n\n /*showdown.helper.matchRecursiveRegExp(text, ']*>', '', 'gi');*/\n\n text = globals.converter._dispatch('hashHTMLSpans.after', text, options, globals);\n return text;\n});\n\n/**\n * Unhash HTML spans\n */\nshowdown.subParser('unhashHTMLSpans', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('unhashHTMLSpans.before', text, options, globals);\n\n for (var i = 0; i < globals.gHtmlSpans.length; ++i) {\n var repText = globals.gHtmlSpans[i],\n // limiter to prevent infinite loop (assume 10 as limit for recurse)\n limit = 0;\n\n while (/¨C(\\d+)C/.test(repText)) {\n var num = RegExp.$1;\n repText = repText.replace('¨C' + num + 'C', globals.gHtmlSpans[num]);\n if (limit === 10) {\n console.error('maximum nesting of 10 spans reached!!!');\n break;\n }\n ++limit;\n }\n text = text.replace('¨C' + i + 'C', repText);\n }\n\n text = globals.converter._dispatch('unhashHTMLSpans.after', text, options, globals);\n return text;\n});\n","/**\n * Hash and escape
     elements that should not be parsed as markdown\n */\nshowdown.subParser('hashPreCodeTags', function (text, options, globals) {\n  'use strict';\n  text = globals.converter._dispatch('hashPreCodeTags.before', text, options, globals);\n\n  var repFunc = function (wholeMatch, match, left, right) {\n    // encode html entities\n    var codeblock = left + showdown.subParser('encodeCode')(match, options, globals) + right;\n    return '\\n\\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\\n\\n';\n  };\n\n  // Hash 
    \n  text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^ {0,3}]*>\\\\s*]*>', '^ {0,3}\\\\s*
    ', 'gim');\n\n text = globals.converter._dispatch('hashPreCodeTags.after', text, options, globals);\n return text;\n});\n","showdown.subParser('headers', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('headers.before', text, options, globals);\n\n var headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),\n\n // Set text-style headers:\n //\tHeader 1\n //\t========\n //\n //\tHeader 2\n //\t--------\n //\n setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \\t]*\\n={2,}[ \\t]*\\n+/gm : /^(.+)[ \\t]*\\n=+[ \\t]*\\n+/gm,\n setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \\t]*\\n-{2,}[ \\t]*\\n+/gm : /^(.+)[ \\t]*\\n-+[ \\t]*\\n+/gm;\n\n text = text.replace(setextRegexH1, function (wholeMatch, m1) {\n\n var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),\n hID = (options.noHeaderId) ? '' : ' id=\"' + headerId(m1) + '\"',\n hLevel = headerLevelStart,\n hashBlock = '' + spanGamut + '';\n return showdown.subParser('hashBlock')(hashBlock, options, globals);\n });\n\n text = text.replace(setextRegexH2, function (matchFound, m1) {\n var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),\n hID = (options.noHeaderId) ? '' : ' id=\"' + headerId(m1) + '\"',\n hLevel = headerLevelStart + 1,\n hashBlock = '' + spanGamut + '';\n return showdown.subParser('hashBlock')(hashBlock, options, globals);\n });\n\n // atx-style headers:\n // # Header 1\n // ## Header 2\n // ## Header 2 with closing hashes ##\n // ...\n // ###### Header 6\n //\n var atxStyle = (options.requireSpaceBeforeHeadingText) ? /^(#{1,6})[ \\t]+(.+?)[ \\t]*#*\\n+/gm : /^(#{1,6})[ \\t]*(.+?)[ \\t]*#*\\n+/gm;\n\n text = text.replace(atxStyle, function (wholeMatch, m1, m2) {\n var hText = m2;\n if (options.customizedHeaderId) {\n hText = m2.replace(/\\s?\\{([^{]+?)}\\s*$/, '');\n }\n\n var span = showdown.subParser('spanGamut')(hText, options, globals),\n hID = (options.noHeaderId) ? '' : ' id=\"' + headerId(m2) + '\"',\n hLevel = headerLevelStart - 1 + m1.length,\n header = '' + span + '';\n\n return showdown.subParser('hashBlock')(header, options, globals);\n });\n\n function headerId (m) {\n var title,\n prefix;\n\n // It is separate from other options to allow combining prefix and customized\n if (options.customizedHeaderId) {\n var match = m.match(/\\{([^{]+?)}\\s*$/);\n if (match && match[1]) {\n m = match[1];\n }\n }\n\n title = m;\n\n // Prefix id to prevent causing inadvertent pre-existing style matches.\n if (showdown.helper.isString(options.prefixHeaderId)) {\n prefix = options.prefixHeaderId;\n } else if (options.prefixHeaderId === true) {\n prefix = 'section-';\n } else {\n prefix = '';\n }\n\n if (!options.rawPrefixHeaderId) {\n title = prefix + title;\n }\n\n if (options.ghCompatibleHeaderId) {\n title = title\n .replace(/ /g, '-')\n // replace previously escaped chars (&, ¨ and $)\n .replace(/&/g, '')\n .replace(/¨T/g, '')\n .replace(/¨D/g, '')\n // replace rest of the chars (&~$ are repeated as they might have been escaped)\n // borrowed from github's redcarpet (some they should produce similar results)\n .replace(/[&+$,\\/:;=?@\"#{}|^¨~\\[\\]`\\\\*)(%.!'<>]/g, '')\n .toLowerCase();\n } else if (options.rawHeaderId) {\n title = title\n .replace(/ /g, '-')\n // replace previously escaped chars (&, ¨ and $)\n .replace(/&/g, '&')\n .replace(/¨T/g, '¨')\n .replace(/¨D/g, '$')\n // replace \" and '\n .replace(/[\"']/g, '-')\n .toLowerCase();\n } else {\n title = title\n .replace(/[^\\w]/g, '')\n .toLowerCase();\n }\n\n if (options.rawPrefixHeaderId) {\n title = prefix + title;\n }\n\n if (globals.hashLinkCounts[title]) {\n title = title + '-' + (globals.hashLinkCounts[title]++);\n } else {\n globals.hashLinkCounts[title] = 1;\n }\n return title;\n }\n\n text = globals.converter._dispatch('headers.after', text, options, globals);\n return text;\n});\n","/**\n * Turn Markdown link shortcuts into XHTML
    tags.\n */\nshowdown.subParser('horizontalRule', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('horizontalRule.before', text, options, globals);\n\n var key = showdown.subParser('hashBlock')('
    ', options, globals);\n text = text.replace(/^ {0,2}( ?-){3,}[ \\t]*$/gm, key);\n text = text.replace(/^ {0,2}( ?\\*){3,}[ \\t]*$/gm, key);\n text = text.replace(/^ {0,2}( ?_){3,}[ \\t]*$/gm, key);\n\n text = globals.converter._dispatch('horizontalRule.after', text, options, globals);\n return text;\n});\n","/**\n * Turn Markdown image shortcuts into tags.\n */\nshowdown.subParser('images', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('images.before', text, options, globals);\n\n var inlineRegExp = /!\\[([^\\]]*?)][ \\t]*()\\([ \\t]??(?: =([*\\d]+[A-Za-z%]{0,4})x([*\\d]+[A-Za-z%]{0,4}))?[ \\t]*(?:([\"'])([^\"]*?)\\6)?[ \\t]?\\)/g,\n crazyRegExp = /!\\[([^\\]]*?)][ \\t]*()\\([ \\t]?<([^>]*)>(?: =([*\\d]+[A-Za-z%]{0,4})x([*\\d]+[A-Za-z%]{0,4}))?[ \\t]*(?:(?:([\"'])([^\"]*?)\\6))?[ \\t]?\\)/g,\n base64RegExp = /!\\[([^\\]]*?)][ \\t]*()\\([ \\t]??(?: =([*\\d]+[A-Za-z%]{0,4})x([*\\d]+[A-Za-z%]{0,4}))?[ \\t]*(?:([\"'])([^\"]*?)\\6)?[ \\t]?\\)/g,\n referenceRegExp = /!\\[([^\\]]*?)] ?(?:\\n *)?\\[([\\s\\S]*?)]()()()()()/g,\n refShortcutRegExp = /!\\[([^\\[\\]]+)]()()()()()/g;\n\n function writeImageTagBase64 (wholeMatch, altText, linkId, url, width, height, m5, title) {\n url = url.replace(/\\s/g, '');\n return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);\n }\n\n function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {\n\n var gUrls = globals.gUrls,\n gTitles = globals.gTitles,\n gDims = globals.gDimensions;\n\n linkId = linkId.toLowerCase();\n\n if (!title) {\n title = '';\n }\n // Special case for explicit empty url\n if (wholeMatch.search(/\\(? ?(['\"].*['\"])?\\)$/m) > -1) {\n url = '';\n\n } else if (url === '' || url === null) {\n if (linkId === '' || linkId === null) {\n // lower-case and turn embedded newlines into spaces\n linkId = altText.toLowerCase().replace(/ ?\\n/g, ' ');\n }\n url = '#' + linkId;\n\n if (!showdown.helper.isUndefined(gUrls[linkId])) {\n url = gUrls[linkId];\n if (!showdown.helper.isUndefined(gTitles[linkId])) {\n title = gTitles[linkId];\n }\n if (!showdown.helper.isUndefined(gDims[linkId])) {\n width = gDims[linkId].width;\n height = gDims[linkId].height;\n }\n } else {\n return wholeMatch;\n }\n }\n\n altText = altText\n .replace(/\"/g, '"')\n //altText = showdown.helper.escapeCharacters(altText, '*_', false);\n .replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback);\n //url = showdown.helper.escapeCharacters(url, '*_', false);\n url = url.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback);\n var result = '\"'x \"optional title\")\n\n // base64 encoded images\n text = text.replace(base64RegExp, writeImageTagBase64);\n\n // cases with crazy urls like ./image/cat1).png\n text = text.replace(crazyRegExp, writeImageTag);\n\n // normal cases\n text = text.replace(inlineRegExp, writeImageTag);\n\n // handle reference-style shortcuts: ![img text]\n text = text.replace(refShortcutRegExp, writeImageTag);\n\n text = globals.converter._dispatch('images.after', text, options, globals);\n return text;\n});\n","showdown.subParser('italicsAndBold', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);\n\n // it's faster to have 3 separate regexes for each case than have just one\n // because of backtracing, in some cases, it could lead to an exponential effect\n // called \"catastrophic backtrace\". Ominous!\n\n function parseInside (txt, left, right) {\n /*\n if (options.simplifiedAutoLink) {\n txt = showdown.subParser('simplifiedAutoLinks')(txt, options, globals);\n }\n */\n return left + txt + right;\n }\n\n // Parse underscores\n if (options.literalMidWordUnderscores) {\n text = text.replace(/\\b___(\\S[\\s\\S]*)___\\b/g, function (wm, txt) {\n return parseInside (txt, '', '');\n });\n text = text.replace(/\\b__(\\S[\\s\\S]*)__\\b/g, function (wm, txt) {\n return parseInside (txt, '', '');\n });\n text = text.replace(/\\b_(\\S[\\s\\S]*?)_\\b/g, function (wm, txt) {\n return parseInside (txt, '', '');\n });\n } else {\n text = text.replace(/___(\\S[\\s\\S]*?)___/g, function (wm, m) {\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n text = text.replace(/__(\\S[\\s\\S]*?)__/g, function (wm, m) {\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n text = text.replace(/_([^\\s_][\\s\\S]*?)_/g, function (wm, m) {\n // !/^_[^_]/.test(m) - test if it doesn't start with __ (since it seems redundant, we removed it)\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n }\n\n // Now parse asterisks\n if (options.literalMidWordAsterisks) {\n text = text.replace(/([^*]|^)\\B\\*\\*\\*(\\S[\\s\\S]+?)\\*\\*\\*\\B(?!\\*)/g, function (wm, lead, txt) {\n return parseInside (txt, lead + '', '');\n });\n text = text.replace(/([^*]|^)\\B\\*\\*(\\S[\\s\\S]+?)\\*\\*\\B(?!\\*)/g, function (wm, lead, txt) {\n return parseInside (txt, lead + '', '');\n });\n text = text.replace(/([^*]|^)\\B\\*(\\S[\\s\\S]+?)\\*\\B(?!\\*)/g, function (wm, lead, txt) {\n return parseInside (txt, lead + '', '');\n });\n } else {\n text = text.replace(/\\*\\*\\*(\\S[\\s\\S]*?)\\*\\*\\*/g, function (wm, m) {\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n text = text.replace(/\\*\\*(\\S[\\s\\S]*?)\\*\\*/g, function (wm, m) {\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n text = text.replace(/\\*([^\\s*][\\s\\S]*?)\\*/g, function (wm, m) {\n // !/^\\*[^*]/.test(m) - test if it doesn't start with ** (since it seems redundant, we removed it)\n return (/\\S$/.test(m)) ? parseInside (m, '', '') : wm;\n });\n }\n\n\n text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);\n return text;\n});\n","/**\n * Form HTML ordered (numbered) and unordered (bulleted) lists.\n */\nshowdown.subParser('lists', function (text, options, globals) {\n 'use strict';\n\n /**\n * Process the contents of a single ordered or unordered list, splitting it\n * into individual list items.\n * @param {string} listStr\n * @param {boolean} trimTrailing\n * @returns {string}\n */\n function processListItems (listStr, trimTrailing) {\n // The $g_list_level global keeps track of when we're inside a list.\n // Each time we enter a list, we increment it; when we leave a list,\n // we decrement. If it's zero, we're not in a list anymore.\n //\n // We do this because when we're not inside a list, we want to treat\n // something like this:\n //\n // I recommend upgrading to version\n // 8. Oops, now this line is treated\n // as a sub-list.\n //\n // As a single paragraph, despite the fact that the second line starts\n // with a digit-period-space sequence.\n //\n // Whereas when we're inside a list (or sub-list), that line will be\n // treated as the start of a sub-list. What a kludge, huh? This is\n // an aspect of Markdown's syntax that's hard to parse perfectly\n // without resorting to mind-reading. Perhaps the solution is to\n // change the syntax rules such that sub-lists must start with a\n // starting cardinal number; e.g. \"1.\" or \"a.\".\n globals.gListLevel++;\n\n // trim trailing blank lines:\n listStr = listStr.replace(/\\n{2,}$/, '\\n');\n\n // attacklab: add sentinel to emulate \\z\n listStr += '¨0';\n\n var rgx = /(\\n)?(^ {0,3})([*+-]|\\d+[.])[ \\t]+((\\[(x|X| )?])?[ \\t]*[^\\r]+?(\\n{1,2}))(?=\\n*(¨0| {0,3}([*+-]|\\d+[.])[ \\t]+))/gm,\n isParagraphed = (/\\n[ \\t]*\\n(?!¨0)/.test(listStr));\n\n // Since version 1.5, nesting sublists requires 4 spaces (or 1 tab) indentation,\n // which is a syntax breaking change\n // activating this option reverts to old behavior\n if (options.disableForced4SpacesIndentedSublists) {\n rgx = /(\\n)?(^ {0,3})([*+-]|\\d+[.])[ \\t]+((\\[(x|X| )?])?[ \\t]*[^\\r]+?(\\n{1,2}))(?=\\n*(¨0|\\2([*+-]|\\d+[.])[ \\t]+))/gm;\n }\n\n listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {\n checked = (checked && checked.trim() !== '');\n\n var item = showdown.subParser('outdent')(m4, options, globals),\n bulletStyle = '';\n\n // Support for github tasklists\n if (taskbtn && options.tasklists) {\n bulletStyle = ' class=\"task-list-item\" style=\"list-style-type: none;\"';\n item = item.replace(/^[ \\t]*\\[(x|X| )?]/m, function () {\n var otp = '
  • a
  • \n // instead of:\n //
    • - - a
    \n // So, to prevent it, we will put a marker (¨A)in the beginning of the line\n // Kind of hackish/monkey patching, but seems more effective than overcomplicating the list parser\n item = item.replace(/^([-*+]|\\d\\.)[ \\t]+[\\S\\n ]*/g, function (wm2) {\n return '¨A' + wm2;\n });\n\n // m1 - Leading line or\n // Has a double return (multi paragraph) or\n // Has sublist\n if (m1 || (item.search(/\\n{2,}/) > -1)) {\n item = showdown.subParser('githubCodeBlocks')(item, options, globals);\n item = showdown.subParser('blockGamut')(item, options, globals);\n } else {\n // Recursion for sub-lists:\n item = showdown.subParser('lists')(item, options, globals);\n item = item.replace(/\\n$/, ''); // chomp(item)\n item = showdown.subParser('hashHTMLBlocks')(item, options, globals);\n\n // Colapse double linebreaks\n item = item.replace(/\\n\\n+/g, '\\n\\n');\n if (isParagraphed) {\n item = showdown.subParser('paragraphs')(item, options, globals);\n } else {\n item = showdown.subParser('spanGamut')(item, options, globals);\n }\n }\n\n // now we need to remove the marker (¨A)\n item = item.replace('¨A', '');\n // we can finally wrap the line in list item tags\n item = '' + item + '\\n';\n\n return item;\n });\n\n // attacklab: strip sentinel\n listStr = listStr.replace(/¨0/g, '');\n\n globals.gListLevel--;\n\n if (trimTrailing) {\n listStr = listStr.replace(/\\s+$/, '');\n }\n\n return listStr;\n }\n\n function styleStartNumber (list, listType) {\n // check if ol and starts by a number different than 1\n if (listType === 'ol') {\n var res = list.match(/^ *(\\d+)\\./);\n if (res && res[1] !== '1') {\n return ' start=\"' + res[1] + '\"';\n }\n }\n return '';\n }\n\n /**\n * Check and parse consecutive lists (better fix for issue #142)\n * @param {string} list\n * @param {string} listType\n * @param {boolean} trimTrailing\n * @returns {string}\n */\n function parseConsecutiveLists (list, listType, trimTrailing) {\n // check if we caught 2 or more consecutive lists by mistake\n // we use the counterRgx, meaning if listType is UL we look for OL and vice versa\n var olRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?\\d+\\.[ \\t]/gm : /^ {0,3}\\d+\\.[ \\t]/gm,\n ulRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?[*+-][ \\t]/gm : /^ {0,3}[*+-][ \\t]/gm,\n counterRxg = (listType === 'ul') ? olRgx : ulRgx,\n result = '';\n\n if (list.search(counterRxg) !== -1) {\n (function parseCL (txt) {\n var pos = txt.search(counterRxg),\n style = styleStartNumber(list, listType);\n if (pos !== -1) {\n // slice\n result += '\\n\\n<' + listType + style + '>\\n' + processListItems(txt.slice(0, pos), !!trimTrailing) + '\\n';\n\n // invert counterType and listType\n listType = (listType === 'ul') ? 'ol' : 'ul';\n counterRxg = (listType === 'ul') ? olRgx : ulRgx;\n\n //recurse\n parseCL(txt.slice(pos));\n } else {\n result += '\\n\\n<' + listType + style + '>\\n' + processListItems(txt, !!trimTrailing) + '\\n';\n }\n })(list);\n } else {\n var style = styleStartNumber(list, listType);\n result = '\\n\\n<' + listType + style + '>\\n' + processListItems(list, !!trimTrailing) + '\\n';\n }\n\n return result;\n }\n\n /** Start of list parsing **/\n text = globals.converter._dispatch('lists.before', text, options, globals);\n // add sentinel to hack around khtml/safari bug:\n // http://bugs.webkit.org/show_bug.cgi?id=11231\n text += '¨0';\n\n if (globals.gListLevel) {\n text = text.replace(/^(( {0,3}([*+-]|\\d+[.])[ \\t]+)[^\\r]+?(¨0|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))/gm,\n function (wholeMatch, list, m2) {\n var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';\n return parseConsecutiveLists(list, listType, true);\n }\n );\n } else {\n text = text.replace(/(\\n\\n|^\\n?)(( {0,3}([*+-]|\\d+[.])[ \\t]+)[^\\r]+?(¨0|\\n{2,}(?=\\S)(?![ \\t]*(?:[*+-]|\\d+[.])[ \\t]+)))/gm,\n function (wholeMatch, m1, list, m3) {\n var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';\n return parseConsecutiveLists(list, listType, false);\n }\n );\n }\n\n // strip sentinel\n text = text.replace(/¨0/, '');\n text = globals.converter._dispatch('lists.after', text, options, globals);\n return text;\n});\n","/**\n * Parse metadata at the top of the document\n */\nshowdown.subParser('metadata', function (text, options, globals) {\n 'use strict';\n\n if (!options.metadata) {\n return text;\n }\n\n text = globals.converter._dispatch('metadata.before', text, options, globals);\n\n function parseMetadataContents (content) {\n // raw is raw so it's not changed in any way\n globals.metadata.raw = content;\n\n // escape chars forbidden in html attributes\n // double quotes\n content = content\n // ampersand first\n .replace(/&/g, '&')\n // double quotes\n .replace(/\"/g, '"');\n\n content = content.replace(/\\n {4}/g, ' ');\n content.replace(/^([\\S ]+): +([\\s\\S]+?)$/gm, function (wm, key, value) {\n globals.metadata.parsed[key] = value;\n return '';\n });\n }\n\n text = text.replace(/^\\s*«««+(\\S*?)\\n([\\s\\S]+?)\\n»»»+\\n/, function (wholematch, format, content) {\n parseMetadataContents(content);\n return '¨M';\n });\n\n text = text.replace(/^\\s*---+(\\S*?)\\n([\\s\\S]+?)\\n---+\\n/, function (wholematch, format, content) {\n if (format) {\n globals.metadata.format = format;\n }\n parseMetadataContents(content);\n return '¨M';\n });\n\n text = text.replace(/¨M/g, '');\n\n text = globals.converter._dispatch('metadata.after', text, options, globals);\n return text;\n});\n","/**\n * Remove one level of line-leading tabs or spaces\n */\nshowdown.subParser('outdent', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('outdent.before', text, options, globals);\n\n // attacklab: hack around Konqueror 3.5.4 bug:\n // \"----------bug\".replace(/^-/g,\"\") == \"bug\"\n text = text.replace(/^(\\t|[ ]{1,4})/gm, '¨0'); // attacklab: g_tab_width\n\n // attacklab: clean up hack\n text = text.replace(/¨0/g, '');\n\n text = globals.converter._dispatch('outdent.after', text, options, globals);\n return text;\n});\n","/**\n *\n */\nshowdown.subParser('paragraphs', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('paragraphs.before', text, options, globals);\n // Strip leading and trailing lines:\n text = text.replace(/^\\n+/g, '');\n text = text.replace(/\\n+$/g, '');\n\n var grafs = text.split(/\\n{2,}/g),\n grafsOut = [],\n end = grafs.length; // Wrap

    tags\n\n for (var i = 0; i < end; i++) {\n var str = grafs[i];\n // if this is an HTML marker, copy it\n if (str.search(/¨(K|G)(\\d+)\\1/g) >= 0) {\n grafsOut.push(str);\n\n // test for presence of characters to prevent empty lines being parsed\n // as paragraphs (resulting in undesired extra empty paragraphs)\n } else if (str.search(/\\S/) >= 0) {\n str = showdown.subParser('spanGamut')(str, options, globals);\n str = str.replace(/^([ \\t]*)/g, '

    ');\n str += '

    ';\n grafsOut.push(str);\n }\n }\n\n /** Unhashify HTML blocks */\n end = grafsOut.length;\n for (i = 0; i < end; i++) {\n var blockText = '',\n grafsOutIt = grafsOut[i],\n codeFlag = false;\n // if this is a marker for an html block...\n // use RegExp.test instead of string.search because of QML bug\n while (/¨(K|G)(\\d+)\\1/.test(grafsOutIt)) {\n var delim = RegExp.$1,\n num = RegExp.$2;\n\n if (delim === 'K') {\n blockText = globals.gHtmlBlocks[num];\n } else {\n // we need to check if ghBlock is a false positive\n if (codeFlag) {\n // use encoded version of all text\n blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text, options, globals);\n } else {\n blockText = globals.ghCodeBlocks[num].codeblock;\n }\n }\n blockText = blockText.replace(/\\$/g, '$$$$'); // Escape any dollar signs\n\n grafsOutIt = grafsOutIt.replace(/(\\n\\n)?¨(K|G)\\d+\\2(\\n\\n)?/, blockText);\n // Check if grafsOutIt is a pre->code\n if (/^]*>\\s*]*>/.test(grafsOutIt)) {\n codeFlag = true;\n }\n }\n grafsOut[i] = grafsOutIt;\n }\n text = grafsOut.join('\\n');\n // Strip leading and trailing lines:\n text = text.replace(/^\\n+/g, '');\n text = text.replace(/\\n+$/g, '');\n return globals.converter._dispatch('paragraphs.after', text, options, globals);\n});\n","/**\n * Run extension\n */\nshowdown.subParser('runExtension', function (ext, text, options, globals) {\n 'use strict';\n\n if (ext.filter) {\n text = ext.filter(text, globals.converter, options);\n\n } else if (ext.regex) {\n // TODO remove this when old extension loading mechanism is deprecated\n var re = ext.regex;\n if (!(re instanceof RegExp)) {\n re = new RegExp(re, 'g');\n }\n text = text.replace(re, ext.replace);\n }\n\n return text;\n});\n","/**\n * These are all the transformations that occur *within* block-level\n * tags like paragraphs, headers, and list items.\n */\nshowdown.subParser('spanGamut', function (text, options, globals) {\n 'use strict';\n\n text = globals.converter._dispatch('spanGamut.before', text, options, globals);\n text = showdown.subParser('codeSpans')(text, options, globals);\n text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);\n text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);\n\n // Process anchor and image tags. Images must come first,\n // because ![foo][f] looks like an anchor.\n text = showdown.subParser('images')(text, options, globals);\n text = showdown.subParser('anchors')(text, options, globals);\n\n // Make links out of things like ``\n // Must come after anchors, because you can use < and >\n // delimiters in inline links like [this]().\n text = showdown.subParser('autoLinks')(text, options, globals);\n text = showdown.subParser('simplifiedAutoLinks')(text, options, globals);\n text = showdown.subParser('emoji')(text, options, globals);\n text = showdown.subParser('underline')(text, options, globals);\n text = showdown.subParser('italicsAndBold')(text, options, globals);\n text = showdown.subParser('strikethrough')(text, options, globals);\n text = showdown.subParser('ellipsis')(text, options, globals);\n\n // we need to hash HTML tags inside spans\n text = showdown.subParser('hashHTMLSpans')(text, options, globals);\n\n // now we encode amps and angles\n text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);\n\n // Do hard breaks\n if (options.simpleLineBreaks) {\n // GFM style hard breaks\n // only add line breaks if the text does not contain a block (special case for lists)\n if (!/\\n\\n¨K/.test(text)) {\n text = text.replace(/\\n+/g, '
    \\n');\n }\n } else {\n // Vanilla hard breaks\n text = text.replace(/ +\\n/g, '
    \\n');\n }\n\n text = globals.converter._dispatch('spanGamut.after', text, options, globals);\n return text;\n});\n","showdown.subParser('strikethrough', function (text, options, globals) {\n 'use strict';\n\n function parseInside (txt) {\n if (options.simplifiedAutoLink) {\n txt = showdown.subParser('simplifiedAutoLinks')(txt, options, globals);\n }\n return '' + txt + '';\n }\n\n if (options.strikethrough) {\n text = globals.converter._dispatch('strikethrough.before', text, options, globals);\n text = text.replace(/(?:~){2}([\\s\\S]+?)(?:~){2}/g, function (wm, txt) { return parseInside(txt); });\n text = globals.converter._dispatch('strikethrough.after', text, options, globals);\n }\n\n return text;\n});\n","/**\n * Strips link definitions from text, stores the URLs and titles in\n * hash references.\n * Link defs are in the form: ^[id]: url \"optional title\"\n */\nshowdown.subParser('stripLinkDefinitions', function (text, options, globals) {\n 'use strict';\n\n var regex = /^ {0,3}\\[(.+)]:[ \\t]*\\n?[ \\t]*\\s]+)>?(?: =([*\\d]+[A-Za-z%]{0,4})x([*\\d]+[A-Za-z%]{0,4}))?[ \\t]*\\n?[ \\t]*(?:(\\n*)[\"|'(](.+?)[\"|')][ \\t]*)?(?:\\n+|(?=¨0))/gm,\n base64Regex = /^ {0,3}\\[(.+)]:[ \\t]*\\n?[ \\t]*?(?: =([*\\d]+[A-Za-z%]{0,4})x([*\\d]+[A-Za-z%]{0,4}))?[ \\t]*\\n?[ \\t]*(?:(\\n*)[\"|'(](.+?)[\"|')][ \\t]*)?(?:\\n\\n|(?=¨0)|(?=\\n\\[))/gm;\n\n // attacklab: sentinel workarounds for lack of \\A and \\Z, safari\\khtml bug\n text += '¨0';\n\n var replaceFunc = function (wholeMatch, linkId, url, width, height, blankLines, title) {\n linkId = linkId.toLowerCase();\n if (url.match(/^data:.+?\\/.+?;base64,/)) {\n // remove newlines\n globals.gUrls[linkId] = url.replace(/\\s/g, '');\n } else {\n globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url, options, globals); // Link IDs are case-insensitive\n }\n\n if (blankLines) {\n // Oops, found blank lines, so it's not a title.\n // Put back the parenthetical statement we stole.\n return blankLines + title;\n\n } else {\n if (title) {\n globals.gTitles[linkId] = title.replace(/\"|'/g, '"');\n }\n if (options.parseImgDimensions && width && height) {\n globals.gDimensions[linkId] = {\n width: width,\n height: height\n };\n }\n }\n // Completely remove the definition from the text\n return '';\n };\n\n // first we try to find base64 link references\n text = text.replace(base64Regex, replaceFunc);\n\n text = text.replace(regex, replaceFunc);\n\n // attacklab: strip sentinel\n text = text.replace(/¨0/, '');\n\n return text;\n});\n","showdown.subParser('tables', function (text, options, globals) {\n 'use strict';\n\n if (!options.tables) {\n return text;\n }\n\n var tableRgx = /^ {0,3}\\|?.+\\|.+\\n {0,3}\\|?[ \\t]*:?[ \\t]*(?:[-=]){2,}[ \\t]*:?[ \\t]*\\|[ \\t]*:?[ \\t]*(?:[-=]){2,}[\\s\\S]+?(?:\\n\\n|¨0)/gm,\n //singeColTblRgx = /^ {0,3}\\|.+\\|\\n {0,3}\\|[ \\t]*:?[ \\t]*(?:[-=]){2,}[ \\t]*:?[ \\t]*\\|[ \\t]*\\n(?: {0,3}\\|.+\\|\\n)+(?:\\n\\n|¨0)/gm;\n singeColTblRgx = /^ {0,3}\\|.+\\|[ \\t]*\\n {0,3}\\|[ \\t]*:?[ \\t]*(?:[-=]){2,}[ \\t]*:?[ \\t]*\\|[ \\t]*\\n( {0,3}\\|.+\\|[ \\t]*\\n)*(?:\\n|¨0)/gm;\n\n function parseStyles (sLine) {\n if (/^:[ \\t]*--*$/.test(sLine)) {\n return ' style=\"text-align:left;\"';\n } else if (/^--*[ \\t]*:[ \\t]*$/.test(sLine)) {\n return ' style=\"text-align:right;\"';\n } else if (/^:[ \\t]*--*[ \\t]*:$/.test(sLine)) {\n return ' style=\"text-align:center;\"';\n } else {\n return '';\n }\n }\n\n function parseHeaders (header, style) {\n var id = '';\n header = header.trim();\n // support both tablesHeaderId and tableHeaderId due to error in documentation so we don't break backwards compatibility\n if (options.tablesHeaderId || options.tableHeaderId) {\n id = ' id=\"' + header.replace(/ /g, '_').toLowerCase() + '\"';\n }\n header = showdown.subParser('spanGamut')(header, options, globals);\n\n return '' + header + '\\n';\n }\n\n function parseCells (cell, style) {\n var subText = showdown.subParser('spanGamut')(cell, options, globals);\n return '' + subText + '\\n';\n }\n\n function buildTable (headers, cells) {\n var tb = '\\n\\n\\n',\n tblLgn = headers.length;\n\n for (var i = 0; i < tblLgn; ++i) {\n tb += headers[i];\n }\n tb += '\\n\\n\\n';\n\n for (i = 0; i < cells.length; ++i) {\n tb += '\\n';\n for (var ii = 0; ii < tblLgn; ++ii) {\n tb += cells[i][ii];\n }\n tb += '\\n';\n }\n tb += '\\n
    \\n';\n return tb;\n }\n\n function parseTable (rawTable) {\n var i, tableLines = rawTable.split('\\n');\n\n for (i = 0; i < tableLines.length; ++i) {\n // strip wrong first and last column if wrapped tables are used\n if (/^ {0,3}\\|/.test(tableLines[i])) {\n tableLines[i] = tableLines[i].replace(/^ {0,3}\\|/, '');\n }\n if (/\\|[ \\t]*$/.test(tableLines[i])) {\n tableLines[i] = tableLines[i].replace(/\\|[ \\t]*$/, '');\n }\n // parse code spans first, but we only support one line code spans\n tableLines[i] = showdown.subParser('codeSpans')(tableLines[i], options, globals);\n }\n\n var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}),\n rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}),\n rawCells = [],\n headers = [],\n styles = [],\n cells = [];\n\n tableLines.shift();\n tableLines.shift();\n\n for (i = 0; i < tableLines.length; ++i) {\n if (tableLines[i].trim() === '') {\n continue;\n }\n rawCells.push(\n tableLines[i]\n .split('|')\n .map(function (s) {\n return s.trim();\n })\n );\n }\n\n if (rawHeaders.length < rawStyles.length) {\n return rawTable;\n }\n\n for (i = 0; i < rawStyles.length; ++i) {\n styles.push(parseStyles(rawStyles[i]));\n }\n\n for (i = 0; i < rawHeaders.length; ++i) {\n if (showdown.helper.isUndefined(styles[i])) {\n styles[i] = '';\n }\n headers.push(parseHeaders(rawHeaders[i], styles[i]));\n }\n\n for (i = 0; i < rawCells.length; ++i) {\n var row = [];\n for (var ii = 0; ii < headers.length; ++ii) {\n if (showdown.helper.isUndefined(rawCells[i][ii])) {\n\n }\n row.push(parseCells(rawCells[i][ii], styles[ii]));\n }\n cells.push(row);\n }\n\n return buildTable(headers, cells);\n }\n\n text = globals.converter._dispatch('tables.before', text, options, globals);\n\n // find escaped pipe characters\n text = text.replace(/\\\\(\\|)/g, showdown.helper.escapeCharactersCallback);\n\n // parse multi column tables\n text = text.replace(tableRgx, parseTable);\n\n // parse one column tables\n text = text.replace(singeColTblRgx, parseTable);\n\n text = globals.converter._dispatch('tables.after', text, options, globals);\n\n return text;\n});\n","showdown.subParser('underline', function (text, options, globals) {\n 'use strict';\n\n if (!options.underline) {\n return text;\n }\n\n text = globals.converter._dispatch('underline.before', text, options, globals);\n\n if (options.literalMidWordUnderscores) {\n text = text.replace(/\\b_?__(\\S[\\s\\S]*)___?\\b/g, function (wm, txt) {\n return '' + txt + '';\n });\n } else {\n text = text.replace(/_?__(\\S[\\s\\S]*?)___?/g, function (wm, m) {\n return (/\\S$/.test(m)) ? '' + m + '' : wm;\n });\n }\n\n // escape remaining underscores to prevent them being parsed by italic and bold\n text = text.replace(/(_)/g, showdown.helper.escapeCharactersCallback);\n\n text = globals.converter._dispatch('underline.after', text, options, globals);\n\n return text;\n});\n","/**\n * Swap back in all the special characters we've hidden.\n */\nshowdown.subParser('unescapeSpecialChars', function (text, options, globals) {\n 'use strict';\n text = globals.converter._dispatch('unescapeSpecialChars.before', text, options, globals);\n\n text = text.replace(/¨E(\\d+)E/g, function (wholeMatch, m1) {\n var charCodeToReplace = parseInt(m1);\n return String.fromCharCode(charCodeToReplace);\n });\n\n text = globals.converter._dispatch('unescapeSpecialChars.after', text, options, globals);\n return text;\n});\n","var root = this;\n\n// AMD Loader\nif (typeof define === 'function' && define.amd) {\n define(function () {\n 'use strict';\n return showdown;\n });\n\n// CommonJS/nodeJS Loader\n} else if (typeof module !== 'undefined' && module.exports) {\n module.exports = showdown;\n\n// Regular Browser loader\n} else {\n root.showdown = showdown;\n}\n"]} \ No newline at end of file diff --git a/cvat/apps/documentation/templates/documentation/base_page.html b/cvat/apps/documentation/templates/documentation/base_page.html deleted file mode 100644 index f49fe5f1..00000000 --- a/cvat/apps/documentation/templates/documentation/base_page.html +++ /dev/null @@ -1,34 +0,0 @@ - - -{% load static compress %} - - - {% block title %} {% endblock %} - {% compress js file thirdparty %} - - - {% endcompress %} - - - - - - diff --git a/cvat/apps/documentation/templates/documentation/user_guide.html b/cvat/apps/documentation/templates/documentation/user_guide.html deleted file mode 100644 index fa1b6221..00000000 --- a/cvat/apps/documentation/templates/documentation/user_guide.html +++ /dev/null @@ -1,14 +0,0 @@ - -{% extends 'documentation/base_page.html' %} - -{% block title %} -CVAT User Guide -{% endblock %} - -{% block content %} -{{ user_guide }} -{% endblock %} diff --git a/cvat/apps/documentation/templates/documentation/xml_format.html b/cvat/apps/documentation/templates/documentation/xml_format.html deleted file mode 100644 index ff871aba..00000000 --- a/cvat/apps/documentation/templates/documentation/xml_format.html +++ /dev/null @@ -1,8 +0,0 @@ - -{% extends 'documentation/base_page.html' %} -{% block title %} CVAT XML format {% endblock %} -{% block content %} {{ xml_format }} {% endblock %} diff --git a/cvat/apps/documentation/urls.py b/cvat/apps/documentation/urls.py deleted file mode 100644 index a0f132c4..00000000 --- a/cvat/apps/documentation/urls.py +++ /dev/null @@ -1,13 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('user_guide.html', views.UserGuideView), - path('xml_format.html', views.XmlFormatView), -] - diff --git a/cvat/apps/documentation/user_guide.md b/cvat/apps/documentation/user_guide.md deleted file mode 100644 index 9406756d..00000000 --- a/cvat/apps/documentation/user_guide.md +++ /dev/null @@ -1,1823 +0,0 @@ -- [User's guide](#users-guide) - - [Getting started](#getting-started) - - [Authorization](#authorization) - - [Administration panel](#administration-panel) - - [Creating an annotation task](#creating-an-annotation-task) - - [Projects](#projects) - - [Models](#models) - - [Search](#search) - - [Interface of the annotation tool](#interface-of-the-annotation-tool) - - [Basic navigation](#basic-navigation) - - [Types of shapes (basics)](#types-of-shapes-basics) - - [Shape mode (basics)](#shape-mode-basics) - - [Track mode (basics)](#track-mode-basics) - - [Attribute annotation mode (basics)](#attribute-annotation-mode-basics) - - [Downloading annotations](#downloading-annotations) - - [Task synchronization with a repository](#task-synchronization-with-a-repository) - - [Vocabulary](#vocabulary) - - [Workspace](#workspace) - - [Settings](#settings) - - [Top Panel](#top-panel) - - [Controls sidebar](#controls-sidebar) - - [Objects sidebar](#objects-sidebar) - - [Objects](#objects) - - [Labels](#labels) - - [Shape mode (advanced)](#shape-mode-advanced) - - [Track mode (advanced)](#track-mode-advanced) - - [Attribute annotation mode (advanced)](#attribute-annotation-mode-advanced) - - [AI Tools](#ai-tools) - - [OpenCV Tools](#opencv-tools) - - [Annotation with rectangle by 4 points](#annotation-with-rectangle-by-4-points) - - [Annotation with polygons](#annotation-with-polygons) - - [Creating masks](#creating-masks) - - [Annotation with polylines](#annotation-with-polylines) - - [Annotation with points](#annotation-with-points) - - [Points in shape mode](#points-in-shape-mode) - - [Linear interpolation with one point](#linear-interpolation-with-one-point) - - [Annotation with cuboids](#annotation-with-cuboids) - - [Annotation with tags](#annotation-with-tags) - - [Track mode with polygons](#track-mode-with-polygons) - - [Review](#review) - - [Automatic annotation](#automatic-annotation) - - [Shape grouping](#shape-grouping) - - [Filter](#filter) - - [Analytics](#analytics) - - [Shortcuts](#shortcuts) - -# User's guide - -Computer Vision Annotation Tool (CVAT) is a web-based tool which helps to -annotate videos and images for Computer Vision algorithms. It was inspired -by [Vatic](http://carlvondrick.com/vatic/) free, online, interactive video -annotation tool. CVAT has many powerful features: _interpolation of bounding -boxes between key frames, automatic annotation using deep learning models, -shortcuts for most of critical actions, dashboard with a list of annotation -tasks, LDAP and basic authorization, etc..._ It was created for and used by -a professional data annotation team. UX and UI were optimized especially for -computer vision tasks developed by our team. - -## Getting started - -### Authorization - -- First of all, you have to log in to CVAT tool. - - ![](static/documentation/images/image001.jpg) - -- For register a new user press "Create an account" - - ![](static/documentation/images/image002.jpg) - -- You can register a user but by default it will not have rights even to view - list of tasks. Thus you should create a superuser. The superuser can use - [Django administration panel](http://localhost:8080/admin) to assign correct - groups to the user. Please use the command below to create an admin account: - - `docker exec -it cvat bash -ic 'python3 ~/manage.py createsuperuser'` - -- If you want to create a non-admin account, you can do that using the link below - on the login page. Don't forget to modify permissions for the new user in the - administration panel. There are several groups (aka roles): admin, user, - annotator, observer. - - ![](static/documentation/images/image003.jpg) - -### Administration panel - -Go to the [Django administration panel](http://localhost:8080/admin). There you can: - -- Create / edit / delete users -- Control permissions of users and access to the tool. - - ![](static/documentation/images/image115.jpg) - -### Creating an annotation task - -1. Create an annotation task pressing `Create new task` button on the tasks page or on the project page. - ![](static/documentation/images/image004.jpg) - -1. Specify parameters of the task: - - #### Basic configuration - - **Name** The name of the task to be created. - - ![](static/documentation/images/image005.jpg) - - **Projects** The project that this task will be related with. - - ![](static/documentation/images/image193.jpg) - - **Labels**. There are two ways of working with labels (available only if the task is not related to the project): - - - The `Constructor` is a simple way to add and adjust labels. To add a new label click the `Add label` button. - ![](static/documentation/images/image123.jpg) - - You can set a name of the label in the `Label name` field and choose a color for each label. - - ![](static/documentation/images/image124.jpg) - - If necessary you can add an attribute and set its properties by clicking `Add an attribute`: - - ![](static/documentation/images/image125.jpg) - - The following actions are available here: - - 1. Set the attribute’s name. - 1. Choose the way to display the attribute: - - Select — drop down list of value - - Radio — is used when it is necessary to choose just one option out of few suggested. - - Checkbox — is used when it is necessary to choose any number of options out of suggested. - - Text — is used when an attribute is entered as a text. - - Number — is used when an attribute is entered as a number. - 1. Set values for the attribute. The values could be separated by pressing `Enter`. - The entered value is displayed as a separate element which could be deleted - by pressing `Backspace` or clicking the close button (x). - If the specified way of displaying the attribute is Text or Number, - the entered value will be displayed as text by default (e.g. you can specify the text format). - 1. Checkbox `Mutable` determines if an attribute would be changed frame to frame. - 1. You can delete the attribute by clicking the close button (x). - - Click the `Continue` button to add more labels. - If you need to cancel adding a label - press the `Cancel` button. - After all the necessary labels are added click the `Done` button. - After clicking `Done` the added labels would be displayed as separate elements of different colour. - You can edit or delete labels by clicking `Update attributes` or `Delete label`. - - - The `Raw` is a way of working with labels for an advanced user. - Raw presents label data in _json_ format with an option of editing and copying labels as a text. - The `Done` button applies the changes and the `Reset` button cancels the changes. - ![](static/documentation/images/image126.jpg) - - In `Raw` and `Constructor` mode, you can press the `Copy` button to copy the list of labels. - - **Select files**. Press tab `My computer` to choose some files for annotation from your PC. - If you select tab `Connected file share` you can choose files for annotation from your network. - If you select ` Remote source` , you'll see a field where you can enter a list of URLs (one URL per line). - If you upload a video or dataset with images and select `Use cache` option, you can attach a `manifest.jsonl` file. - You can find how to prepare it [here](/utils/dataset_manifest/README.md). - - ![](static/documentation/images/image127.jpg) - - #### Advanced configuration - - ![](static/documentation/images/image128_use_cache.jpg) - - **Use zip chunks**. Force to use zip chunks as compressed data. Actual for videos only. - - **Use cache**. Defines how to work with data. Select the checkbox to switch to the "on-the-fly data processing", - which will reduce the task creation time (by preparing chunks when requests are received) - and store data in a cache of limited size with a policy of evicting less popular items. - See more [here](/cvat/apps/documentation/data_on_fly.md). - - **Image Quality**. Use this option to specify quality of uploaded images. - The option helps to load high resolution datasets faster. - Use the value from `5` (almost completely compressed images) to `100` (not compressed images). - - **Overlap Size**. Use this option to make overlapped segments. - The option makes tracks continuous from one segment into another. - Use it for interpolation mode. There are several options for using the parameter: - - - For an interpolation task (video sequence). - If you annotate a bounding box on two adjacent segments they will be merged into one bounding box. - If overlap equals to zero or annotation is poor on adjacent segments inside a dumped annotation file, - you will have several tracks, one for each segment, which corresponds to the object. - - For an annotation task (independent images). - If an object exists on overlapped segments, the overlap is greater than zero - and the annotation is good enough on adjacent segments, it will be automatically merged into one object. - If overlap equals to zero or annotation is poor on adjacent segments inside a dumped annotation file, - you will have several bounding boxes for the same object. - Thus, you annotate an object on the first segment. - You annotate the same object on second segment, and if you do it right, you - will have one track inside the annotations. - If annotations on different segments (on overlapped frames) - are very different, you will have two shapes for the same object. - This functionality works only for bounding boxes. - Polygons, polylines, points don't support automatic merge on overlapped segments - even the overlap parameter isn't zero and match between corresponding shapes on adjacent segments is perfect. - - **Segment size**. Use this option to divide a huge dataset into a few smaller segments. - For example, one job cannot be annotated by several labelers (it isn't supported). - Thus using "segment size" you can create several jobs for the same annotation task. - It will help you to parallel data annotation process. - - **Start frame**. Frame from which video in task begins. - - **Stop frame**. Frame on which video in task ends. - - **Frame Step**. Use this option to filter video frames. - For example, enter `25` to leave every twenty fifth frame in the video or every twenty fifth image. - - **Chunk size**. Defines a number of frames to be packed in a chunk when send from client to server. - Server defines automatically if empty. - - Recommended values: - - - 1080p or less: 36 - - 2k or less: 8 - 16 - - 4k or less: 4 - 8 - - More: 1 - 4 - - **Dataset Repository**. URL link of the repository optionally specifies the path to the repository for storage - (`default: annotation / .zip`). - The .zip and .xml file extension of annotation are supported. - Field format: `URL [PATH]` example: `https://github.com/project/repos.git [1/2/3/4/annotation.xml]` - - Supported URL formats : - - - `https://github.com/project/repos[.git]` - - `github.com/project/repos[.git]` - - `git@github.com:project/repos[.git]` - - The task will be highlighted in red after creation if annotation isn't synchronized with the repository. - - **Use LFS**. If the annotation file is large, you can create a repository with - [LFS](https://git-lfs.github.com/) support. - - **Issue tracker**. Specify full issue tracker's URL if it's necessary. - - Push `Submit` button and it will be added into the list of annotation tasks. - Then, the created task will be displayed on a tasks page: - - ![](static/documentation/images/image006_detrac.jpg) - -1. The tasks page contains elements and each of them relates to a separate task. They are sorted in creation order. - Each element contains: task name, preview, progress bar, button `Open`, and menu `Actions`. - Each button is responsible for a in menu `Actions` specific function: - - - `Dump Annotation` and `Export as a dataset` — download annotations or - annotations and images in a specific format. The following formats are available: - - [CVAT for video](/cvat/apps/documentation/xml_format.md#interpolation) - is highlighted if a task has the interpolation mode. - - [CVAT for images](/cvat/apps/documentation/xml_format.md#annotation) - is highlighted if a task has the annotation mode. - - [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) - - [(VOC) Segmentation mask](http://host.robots.ox.ac.uk/pascal/VOC/) — - archive contains class and instance masks for each frame in the png - format and a text file with the value of each color. - - [YOLO](https://pjreddie.com/darknet/yolo/) - - [COCO](http://cocodataset.org/#format-data) - - [TFRecord](https://www.tensorflow.org/tutorials/load_data/tf_records) - - [MOT](https://motchallenge.net/) - - [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0/) - - [Datumaro](https://github.com/opencv/cvat/blob/develop/datumaro/) - - `Upload annotation` is available in the same formats as in `Dump annotation`. - - [CVAT](/cvat/apps/documentation/xml_format.md) accepts both video and image sub-formats. - - `Automatic Annotation` — automatic annotation with OpenVINO toolkit. - Presence depends on how you build CVAT instance. - - `Delete` — delete task. - - Push `Open` button to go to task details. - -1. Task details is a task page which contains a preview, a progress bar - and the details of the task (specified when the task was created) and the jobs section. - - ![](static/documentation/images/image131_detrac.jpg) - - - The next actions are available on this page: - 1. Change the task’s title. - 1. Open `Actions` menu. - 1. Change issue tracker or open issue tracker if it is specified. - 1. Change labels (available only if the task is not related to the project). - You can add new labels or add attributes for the existing labels in the Raw mode or the Constructor mode. - By clicking `Copy` you will copy the labels to the clipboard. - 1. Assigned to — is used to assign a task to a person. Start typing an assignee’s name and/or - choose the right person out of the dropdown list. - - `Jobs` — is a list of all jobs for a particular task. Here you can find the next data: - - Jobs name with a hyperlink to it. - - Frames — the frame interval. - - A status of the job. The status is specified by the user in the menu inside the job. - There are three types of status: annotation, validation or completed. - The status of the job is changes the progress bar of the task. - - Started on — start date of this job. - - Duration — is the amount of time the job is being worked. - - Assignee is the user who is working on the job. - You can start typing an assignee’s name and/or choose the right person out of the dropdown list. - - Reviewer – a user assigned to carry out the review, read more in the [review](#review) section. - - `Copy`. By clicking `Copy` you will copy the job list to the clipboard. - The job list contains direct links to jobs. - - You can filter or sort jobs by status, as well as by assigner or reviewer. - -1. Follow a link inside `Jobs` section to start annotation process. - In some cases, you can have several links. It depends on size of your - task and `Overlap Size` and `Segment Size` parameters. To improve - UX, only the first chunk of several frames will be loaded and you will be able - to annotate first images. Other frames will be loaded in background. - - ![](static/documentation/images/image007_detrac.jpg) - -### Projects - -At CVAT, you can create a project containing tasks of the same type. All tasks related to the project will inherit a list of labels. - -To create a project, go to the projects section by clicking on the `Projects` item in the top menu.  -On the projects page, you can see a list of projects, use a search, or create a new project by clicking `Create New Project`. - -![](static/documentation/images/image190.jpg) - -You can change: the name of the project, the list of labels (which will be used for tasks created as parts of this project) and a link to the issue. - -![](static/documentation/images/image191.jpg) - -Once created, the project will appear on the projects page. To open a project, just click on it. - -![](static/documentation/images/image192_mapillary_vistas.jpg) - -Here you can do the following: - -1. Change the project's title. -1. Open the `Actions` menu. -1. Change issue tracker or open issue tracker if it is specified. -1. Change labels. - You can add new labels or add attributes for the existing labels in the Raw mode or the Constructor mode.  - You can also change the color for different labels. By clicking `Copy` you can copy the labels to the clipboard. -1. Assigned to — is used to assign a project to a person. Start typing an assignee's name and/or choose the right person out of the dropdown list. -1. `Tasks` — is a list of all tasks for a particular project. - -You can remove the project and all related tasks through the Action menu. - -### Models - -The Models page contains a list of deep learning (DL) models deployed for semi-automatic and automatic annotation. -To open the Models page, click the Models button on the navigation bar. -The list of models is presented in the form of a table. The parameters indicated for each model are the following: - -- `Framework` the model is based on -- model `Name` -- model `Type`: - - `detector` - used for automatic annotation (available in [detectors](#detectors) and [automatic annotation](#automatic-annotation)) - - `interactor` - used for semi-automatic shape annotation (available in [interactors](#interactors)) - - `tracker` - used for semi-automatic track annotation (available in [trackers](#trackers)) - - `reid` - used to combine individual objects into a track (available in [automatic annotation](#automatic-annotation)) -- `Description` - brief description of the model -- `Labels` - list of the supported labels (only for the models of the `detectors` type) - -![](static/documentation/images/image099.jpg) - -Read how to install your model [here](installation.md#semi-automatic-and-automatic-annotation). - -### Search - -There are several options how to use the search. - -- Search within all fields (owner, assignee, task name, task status, task mode). - To execute enter a search string in search field. -- Search for specific fields. How to perform: - - `owner: admin` - all tasks created by the user who has the substring "admin" in his name - - `assignee: employee` - all tasks which are assigned to a user who has the substring "employee" in his name - - `name: training` - all tasks with the substring "training" in their names - - `mode: annotation` or `mode: interpolation` - all tasks with images or videos. - - `status: annotation` or `status: validation` or `status: completed` - search by status - - `id: 5` - task with id = 5. -- Multiple filters. Filters can be combined (except for the identifier) ​​using the keyword ` AND`: - - `mode: interpolation AND owner: admin` - - `mode: annotation and status: annotation` - -The search is case insensitive. - -![](static/documentation/images/image100_detrac.jpg) - -## Interface of the annotation tool - -The tool consists of: - -- `Header` - pinned header used to navigate CVAT sections and account settings; -- `Top panel` — contains navigation buttons, main functions and menu access; -- `Workspace` — space where images are shown; -- `Controls sidebar` — contains tools for navigating the image, zoom, - creating shapes and editing tracks (merge, split, group) -- `Objects sidebar` — contains label filter, two lists: - objects (on the frame) and labels (of objects on the frame) and appearance settings. - -![](static/documentation/images/image034_detrac.jpg) - -### Basic navigation - -1. Use arrows below to move to the next/previous frame. - Use the scroll bar slider to scroll through frames. - Almost every button has a shortcut. - To get a hint about a shortcut, just move your mouse pointer over an UI element. - - ![](static/documentation/images/image008.jpg) - -1. To navigate the image, use the button on the controls sidebar. - Another way an image can be moved/shifted is by holding the left mouse button inside - an area without annotated objects. - If the `Mouse Wheel` is pressed, then all annotated objects are ignored. Otherwise the - a highlighted bounding box will be moved instead of the image itself. - - ![](static/documentation/images/image136.jpg) - -1. You can use the button on the sidebar controls to zoom on a region of interest. - Use the button `Fit the image` to fit the image in the workspace. - You can also use the mouse wheel to scale the image - (the image will be zoomed relatively to your current cursor position). - - ![](static/documentation/images/image137.jpg) - -### Types of shapes (basics) - -There are five shapes which you can annotate your images with: - -- `Rectangle` or `Bounding box` -- `Polygon` -- `Polyline` -- `Points` -- `Cuboid` -- `Tag` - -And there is how they all look like: - -![](static/documentation/images/image038_detrac.jpg 'Rectangle') ![](static/documentation/images/image033_detrac.jpg 'Polygon') - -![](static/documentation/images/image009_mapillary_vistas.jpg 'Polyline') ![](static/documentation/images/image010_affectnet.jpg 'Points') - -![](static/documentation/images/image015_detrac.jpg 'Cuboid') ![](static/documentation/images/image135.jpg 'Tag') - -`Tag` - has no shape in the workspace, but is displayed in objects sidebar. - -### Shape mode (basics) - -Usage examples: - -- Create new annotations for a set of images. -- Add/modify/delete objects for existing annotations. - -1. You need to select `Rectangle` on the controls sidebar: - - ![](static/documentation/images/image082.jpg) - - Before you start, select the correct ` Label` (should be specified by you when creating the task) - and ` Drawing Method` (by 2 points or by 4 points): - - ![](static/documentation/images/image080.jpg) - -1. Creating a new annotation in `Shape mode`: - - - Create a separate `Rectangle` by clicking on `Shape`. - - ![](static/documentation/images/image081.jpg) - - - Choose the opposite points. Your first rectangle is ready! - - ![](static/documentation/images/image011_detrac.jpg) - - - To learn about creating a rectangle using the by 4 point drawing method, ([read here](#annotation-by-rectangle-4-points)). - - - It is possible to adjust boundaries and location of the rectangle using a mouse. - Rectangle's size is shown in the top right corner , you can check it by clicking on any point of the shape. - You can also undo your actions using `Ctrl+Z` and redo them with `Shift+Ctrl+Z` or `Ctrl+Y`. - -1. You can see the `Object card` in the objects sidebar or open it by right-clicking on the object. - You can change the attributes in the details section. - You can perform basic operations or delete an object by clicking on the action menu button. - - ![](static/documentation/images/image012.jpg) - -1. The following figure is an example of a fully annotated frame with separate shapes. - - ![](static/documentation/images/image013_detrac.jpg) - - Read more in the section [shape mode (advanced)](#shape-mode-advanced). - -### Track mode (basics) - -Usage examples: - -- Create new annotations for a sequence of frames. -- Add/modify/delete objects for existing annotations. -- Edit tracks, merge several rectangles into one track. - -1. Like in the `Shape mode`, you need to select a `Rectangle` on the sidebar, - in the appearing form, select the desired `Label` and the `Drawing method`. - - ![](static/documentation/images/image083.jpg) - -1. Creating a track for an object (look at the selected car as an example): - - - Create a `Rectangle` in `Track mode` by clicking on `Track`. - - ![](static/documentation/images/image014.jpg) - - - In `Track mode` the rectangle will be automatically interpolated on the next frames. - - The cyclist starts moving on frame #2270. Let's mark the frame as a key frame. - You can press `K` for that or click the `star` button (see the screenshot below). - - ![](static/documentation/images/image016.jpg) - - - If the object starts to change its position, you need to modify the rectangle where it happens. - It isn't necessary to change the rectangle on each frame, simply update several keyframes - and the frames between them will be interpolated automatically. - - Let's jump 30 frames forward and adjust the boundaries of the object. See an example below: - - ![](static/documentation/images/image017_detrac.jpg) - - - After that the rectangle of the object will be changed automatically on frames 2270 to 2300: - - ![](static/documentation/images/gif019_detrac.gif) - -1. When the annotated object disappears or becomes too small, you need to - finish the track. You have to choose `Outside Property`, shortcut `O`. - - ![](static/documentation/images/image019.jpg) - -1. If the object isn't visible on a couple of frames and then appears again, - you can use the `Merge` feature to merge several individual tracks - into one. - - ![](static/documentation/images/image020.jpg) - - - Create tracks for moments when the cyclist is visible: - - ![](static/documentation/images/gif001_detrac.gif) - - - Click `Merge` button or press key `M` and click on any rectangle of the first track - and on any rectangle of the second track and so on: - - ![](static/documentation/images/image162_detrac.jpg) - - - Click `Merge` button or press `M` to apply changes. - - ![](static/documentation/images/image020.jpg) - - - The final annotated sequence of frames in `Interpolation` mode can - look like the clip below: - - ![](static/documentation/images/gif003_detrac.gif) - - Read more in the section [track mode (advanced)](#track-mode-advanced). - -### Attribute annotation mode (basics) - -- In this mode you can edit attributes with fast navigation between objects and frames using a keyboard. - Open the drop-down list in the top panel and select Attribute annotation Mode. - - ![](static/documentation/images/image023_affectnet.jpg) - -- In this mode objects panel change to a special panel : - - ![](static/documentation/images/image026.jpg) - -- The active attribute will be red. In this case it is `gender` . Look at the bottom side panel to see all possible - shortcuts for changing the attribute. Press key `2` on your keyboard to assign a value (female) for the attribute - or select from the drop-down list. - - ![](static/documentation/images/image024_affectnet.jpg) - -- Press `Up Arrow`/`Down Arrow` on your keyboard or click the buttons in the UI to go to the next/previous - attribute. In this case, after pressing `Down Arrow` you will be able to edit the `Age` attribute. - - ![](static/documentation/images/image025_affectnet.jpg) - -- Use `Right Arrow`/`Left Arrow` keys to move to the previous/next image with annotation. - -To see all the hot keys available in the attribute annotation mode, press `F2`. -Read more in the section [attribute annotation mode (advanced)](#attribute-annotation-mode-advanced). - -### Downloading annotations - -1. To download the latest annotations, you have to save all changes first. - click the `Save` button. There is a `Ctrl+S` shortcut to save annotations quickly. -1. After that, сlick the `Menu` button. -1. Press the `Dump Annotation` button. - - ![](static/documentation/images/image028.jpg) - -1. Choose format dump annotation file. Dump annotation are available in several formats: - - - [CVAT for video](/cvat/apps/documentation/xml_format.md#interpolation) - is highlighted if a task has the interpolation mode. - - [CVAT for images](/cvat/apps/documentation/xml_format.md#annotation) - is highlighted if a task has the annotation mode. - - ![](static/documentation/images/image029.jpg 'Example XML format') - - - [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) - - [(VOC) Segmentation mask](http://host.robots.ox.ac.uk/pascal/VOC/) — - archive contains class and instance masks for each frame in the png - format and a text file with the value of each color. - - [YOLO](https://pjreddie.com/darknet/yolo/) - - [COCO](http://cocodataset.org/#format-data) - - [TFRecord](https://www.tensorflow.org/tutorials/load_data/tf_records) - - [MOT](https://motchallenge.net/) - - [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0/) - - [Datumaro](https://github.com/opencv/cvat/blob/develop/datumaro/) - -### Task synchronization with a repository - -1. At the end of the annotation process, a task is synchronized by clicking - ` Synchronize` on the task page. Notice: this feature - works only if a git repository was specified when the task was created. - - ![](static/documentation/images/image106.jpg) - -1. After synchronization the button `Sync` is highlighted in green. The - annotation is now in the repository in a temporary branch. - - ![](static/documentation/images/image109.jpg) - -1. The next step is to go to the repository and manually create a pull request to the main branch. -1. After confirming the PR, when the annotation is saved in the main branch, the color of the task changes to blue. - - ![](static/documentation/images/image110.jpg) - -### Vocabulary - -**Label** is a type of an annotated object (e.g. person, car, vehicle, etc.) - -![](static/documentation/images/image032_detrac.jpg) - ---- - -**Attribute** is a property of an annotated object (e.g. color, model, -quality, etc.). There are two types of attributes: - -- **Unique**: immutable and can't be changed from frame to frame (e.g. age, gender, color, etc.) - - ![](static/documentation/images/image073.jpg) - -- **Temporary**: mutable and can be changed on any frame (e.g. quality, pose, truncated, etc.) - - ![](static/documentation/images/image072.jpg) - ---- - -**Track** is a set of shapes on different frames which corresponds to one object. -Tracks are created in `Track mode` - -![](static/documentation/images/gif003_detrac.gif) - ---- - -**Annotation** is a set of shapes and tracks. There are several types of annotations: - -- _Manual_ which is created by a person -- _Semi-automatic_ which is created mainly automatically, but the user provides some data (e.g. interpolation) -- _Automatic_ which is created automatically without a person in the loop - ---- - -### Workspace - -This is the main field in which drawing and editing objects takes place. -In addition the workspace also has the following functions: - -- Right-clicking on an object calls up the `Object card` - this is an element containing - the necessary controls for changing the label and attributes of the object, as well as the action menu. - - ![](static/documentation/images/image138_mapillary_vistas.jpg) - -- Right-clicking a point deletes it. - - ![](static/documentation/images/image139_mapillary_vistas.jpg) - -- `Z-axis slider` - Allows you to switch annotation layers hiding the upper layers - (slider is enabled if several z layers are on a frame). - This element has a button for adding a new layer. When pressed, a new layer is added and switched to it. - You can move objects in layers using the `+` and `-` keys. - - ![](static/documentation/images/image140.jpg) - -- `Image settings panel` -  used to set up the grid and set up image brightness contrast saturation. - - - Show `Grid`, change grid size, choose color and transparency: - - ![](static/documentation/images/image068_mapillary_vistas.jpg) - - - Adjust `Brightness`/`Contrast`/`Saturation` of too exposed or too - dark images using `F3` — color settings (changes displaying settings and not the - image itself). - - Shortcuts: - - - `Shift+B+=`/`Shift+B+-` for brightness. - - `Shift+C+=`/`Shift+C+-` for contrast. - - `Shift+S+=`/`Shift+S+-` for saturation. - - ![](static/documentation/images/image164_mapillary_vistas.jpg) - - - `Reset color settings` to default values. - ---- - -### Settings - -To open the settings open the user menu in the header and select the settings item or press `F2`. - -![](static/documentation/images/image067.jpg) - -`Settings` have two tabs: - -In tab `Player` you can: - -- Control step of `C` and `V` shortcuts. -- Control speed of `Space`/`Play` button. -- Select canvas background color. You can choose a background color or enter manually (in RGB or HEX format). -- `Reset zoom` Show every image in full size or zoomed out like previous - (it is enabled by default for interpolation mode and disabled for annotation mode). -- `Rotate all images` checkbox — switch the rotation of all frames or an individual frame. - ---- - -In tab `Workspace` you can: - -![](static/documentation/images/image155.jpg) - -- `Enable auto save` checkbox — turned off by default. -- `Auto save interval (min)` input box — 15 minutes by default. -- `Show all interpolation tracks` checkbox — shows hidden objects on the - side panel for every interpolated object (turned off by default). -- `Always show object details` - show text for an object on the canvas not only when the object is activated: - - ![](static/documentation/images/image152_detrac.jpg) - -- `Automatic bordering` - enable automatic bordering for polygons and polylines during drawing/editing. - For more information To find out more, go to the section [annotation with polygons](#Annotation-with-polygons). - -- `Intelligent polygon cropping` - activates intelligent cropping when editing the polygon (read more in the section [edit polygon](#edit-polygon) - -- `Attribute annotation mode (AAM) zoom margin` input box — defines margins (in px) - for shape in the attribute annotation mode. -- Click `Save` to save settings (settings will be saved on the server and will not change after the page is refreshed). Click `Cancel` or press `F2` to return to the annotation. - ---- - -### Top Panel - -![](static/documentation/images/image035.jpg) - ---- - -#### Menu button - -It is the main menu of the annotation tool. It can be used to download, upload and remove annotations. - -![](static/documentation/images/image051.jpg) - -Button assignment: - -- `Dump Annotations` — downloads annotations from a task. -- `Upload Annotations` — uploads annotations into a task. -- `Remove Annotations` — removes annotations from the current job. -- `Export as a dataset` — download a data set from a task. Several formats are available: - - [Datumaro](https://github.com/opencv/cvat/blob/develop/datumaro/docs/design.md) - - [Pascal VOC 2012](http://host.robots.ox.ac.uk/pascal/VOC/) - - [MS COCO](http://cocodataset.org/#format-data) - - [YOLO](https://pjreddie.com/darknet/yolo/) -- `Open the task` — opens a page with details about the task. -- `Request a review` - calls up the form to submit the job for a review, read more in the [review](#review) section. -- `Finish the job` - changes the status of the job to `completed` and returns to the task page without review. -- `Submit the review` - (available during the review) calls up the form to submit a review, read more in the [review](#review) section. - -#### Save Work - -Saves annotations for the current job. The button has an indication of the saving process. - -![](static/documentation/images/image141.jpg) - -#### Undo-redo buttons - -Use buttons to undo actions or redo them. - -![](static/documentation/images/image061.jpg) - ---- - -#### Player - -Go to the first /the latest frames. - -![](static/documentation/images/image036.jpg) - -Go to the next/previous frame with a predefined step. Shortcuts: -`V` — step backward, `C` — step forward. By default the step is `10` frames -(change at `Account Menu` —> `Settings` —> `Player Step`). - -![](static/documentation/images/image037.jpg) - -The button to go to the next / previous frame has the customization possibility. To customize, right-click on the button and select one of three options: - -1. The default option - go to the next / previous frame (the step is 1 frame). -2. Go to the next / previous frame that has any objects (in particular filtered). Read the [filter](#filter) section to know the details how to use it. -3. Go to the next / previous frame without annotation at all. Use this option in cases when you need to find missed frames quickly. - -Shortcuts: `D` - previous, `F` - next. - -![](static/documentation/images/image040.jpg) - -Play the sequence of frames or the set of images. -Shortcut: `Space` (change at `Account Menu` —> `Settings` —> `Player Speed`). - -![](static/documentation/images/image041.jpg) - -Go to a specific frame. Press `~` to focus on the element. - -![](static/documentation/images/image060.jpg) - ---- - -#### Fullscreen Player - -The fullscreen player mode. The keyboard shortcut is `F11`. - -![](static/documentation/images/image143.jpg) - -#### Info - -Open the job info. - -![](static/documentation/images/image144_detrac.jpg) - -_Overview_: - -- `Assinger` - the one to whom the job is assigned. -- `Reviewer` – a user assigned to carry out the review, read more in the [review](#review) section. -- `Start Frame` - the number of the first frame in this job. -- `End Frame` - the number of the last frame in this job. -- `Frames` - the total number of all frames in the job. - -_Annotations statistics_: - -This is a table number of created shapes, sorted by labels (e.g. vehicle, person) -and type of annotation (shape, track). As well as the number of manual and interpolated frames. - -#### UI switcher - -Switching between user interface modes. - -![](static/documentation/images/image145.jpg) - ---- - -### Controls sidebar - -**Navigation block** - contains tools for moving and rotating images. -|Icon |Description | -|-- |-- | -|![](static/documentation/images/image148.jpg)|`Cursor` (`Esc`)- a basic annotation pedacting tool. | -|![](static/documentation/images/image149.jpg)|`Move the image`- a tool for moving around the image without
    the possibility of editing.| -|![](static/documentation/images/image102.jpg)|`Rotate`- two buttons to rotate the current frame
    a clockwise (`Ctrl+R`) and anticlockwise (`Ctrl+Shift+R`).
    You can enable `Rotate all images` in the settings to rotate all the images in the job - -**Zoom block** - contains tools for image zoom. -|Icon |Description | -|-- |-- | -|![](static/documentation/images/image151.jpg)|`Fit image`- fits image into the workspace size.
    Shortcut - double click on an image| -|![](static/documentation/images/image166.jpg)|`Select a region of interest`- zooms in on a selected region.
    You can use this tool to quickly zoom in on a specific part of the frame.| - -**Shapes block** - contains all the tools for creating shapes. -|Icon |Description |Links to section | -|-- |-- |-- | -|![](static/documentation/images/image189.jpg)|`AI Tools` |[AI Tools](#ai-tools)| -|![](static/documentation/images/image201.jpg)|`OpenCV` |[OpenCV](#opencv)| -|![](static/documentation/images/image167.jpg)|`Rectangle`|[Shape mode](#shape-mode-basics); [Track mode](#track-mode-basics);
    [Drawing by 4 points](#annotation-with-rectangle-by-4-points)| -|![](static/documentation/images/image168.jpg)|`Polygon` |[Annotation with polygons](#annotation-with-polygons); [Track mode with polygons](#track-mode-with-polygons) | -|![](static/documentation/images/image169.jpg)|`Polyline` |[Annotation with polylines](#annotation-with-polylines)| -|![](static/documentation/images/image170.jpg)|`Points` |[Annotation with points](#annotation-with-points) | -|![](static/documentation/images/image176.jpg)|`Cuboid` |[Annotation with cuboids](#annotation-with-cuboids) | -|![](static/documentation/images/image171.jpg)|`Tag` |[Annotation with tags](#annotation-with-tag) | -|![](static/documentation/images/image195.jpg)|`Open an issue` |[Review](#review) (available only in review mode) | - -**Edit block** - contains tools for editing tracks and shapes. -|Icon |Description |Links to section | -|-- |-- |-- | -|![](static/documentation/images/image172.jpg)|`Merge Shapes`(`M`) — starts/stops the merging shapes mode. |[Track mode (basics)](#track-mode-basics)| -|![](static/documentation/images/image173.jpg)|`Group Shapes` (`G`) — starts/stops the grouping shapes mode.|[Shape grouping](#shape-grouping)| -|![](static/documentation/images/image174.jpg)|`Split` — splits a track. |[Track mode (advanced)](#track-mode-advanced)| - ---- - -### Objects sidebar - -`Hide` - the button hides the object's sidebar. - -![](static/documentation/images/image146.jpg) - -#### Objects - -**Filter** input box - -![](static/documentation/images/image059.jpg) - -The way how to use filters is described in the advanced guide [here](#filter). - -**List of objects** - -![](static/documentation/images/image147.jpg) - -- Switch lock property for all - switches lock property of all objects in the frame. -- Switch hidden property for all - switches hide property of all objects in the frame. -- Expand/collapse all - collapses/expands the details field of all objects in the frame. -- Sorting - sort the list of objects: updated time, ID - accent, ID - descent - -In the objects sidebar you can see the list of available objects on the current -frame. The following figure is an example of how the list might look like: - -| Shape mode | Track mode | -| --------------------------------------------- | --------------------------------------------- | -| ![](static/documentation/images/image044.jpg) | ![](static/documentation/images/image045.jpg) | - ---- - -**Objects** on the side bar - -The type of a shape can be changed by selecting **Label** property. For instance, it can look like shown on the figure below: - -![](static/documentation/images/image050.jpg) - -**Object action menu** - -The action menu calls up the button: - -![](static/documentation/images/image047.jpg) - -The action menu contains: - -- `Create object URL` - puts a link to an object on the clipboard. After you open the link, this object will be filtered. -- `Make a copy`- copies an object. The keyboard shortcut is `Ctrl + C` `Ctrl + V`. -- `Propagate` - Сopies the form to several frames, - invokes a dialog box in which you can specify the number of copies - or the frame onto which you want to copy the object. The keyboard shortcut `Ctrl + B`. - - ![](static/documentation/images/image053.jpg) - -- `To background` - moves the object to the background. The keyboard shortcut `-`,`_`. -- `To foreground` - moves the object to the foreground. The keyboard shortcut `+`,`=`. -- `Change instance color`- choosing a color using the color picker (available only in instance mode). - - ![](static/documentation/images/image153.jpg) - -- `Remove` - removes the object. The keyboard shortcut `Del`,`Shift+Del`. - -A shape can be locked to prevent its modification or moving by an accident. Shortcut to lock an object: `L`. - -![](static/documentation/images/image046.jpg) - -A shape can be **Occluded**. Shortcut: `Q`. Such shapes have dashed boundaries. - -![](static/documentation/images/image048.jpg) - -![](static/documentation/images/image049_detrac.jpg) - -You can change the way an object is displayed on a frame (show or hide). - -![](static/documentation/images/image055.jpg) - -`Switch pinned property` - when enabled, a shape cannot be moved by dragging or dropping. - -![](static/documentation/images/image052.jpg) - -By clicking on the `Details` button you can collapse or expand the field with all the attributes of the object. - -![](static/documentation/images/image154.jpg) - ---- - -#### Labels - -In this tab you can lock or hide objects of a certain label. -To change the color for a specific label, -you need to go to the task page and select the color by clicking the edit button, -this way you will change the label color for all jobs in the task. - -![](static/documentation/images/image062.jpg) - -**Fast label change** -You can change the label of an object using hot keys. In order to do it, you need to assign a number (from 0 to 9) to labels. By default numbers 1,2...0 are assigned to the first ten labels. - To assign a number, click on the button placed at the right of a label name on the sidebar. - -![](static/documentation/images/image210.jpg) - -After that you will be able to assign a corresponding label to an object - by hovering your mouse cursor over it and pressing `Ctrl + Num(0..9)`. - -In case you do not point the cursor to the object, pressing `Ctrl + Num(0..9)` will set a chosen label as default, - so that the next object you create (use `N` key) will automatically have this label assigned. - -![](static/documentation/images/image211.jpg) - ---- - -#### Appearance - -**Color By** options - -Change the color scheme of annotation: - -- `Instance` — every shape has random color - - ![](static/documentation/images/image095_detrac.jpg) - -- `Group` — every group of shape has its own random color, ungrouped shapes are white - - ![](static/documentation/images/image094_detrac.jpg) - -- `Label` — every label (e.g. car, person) has its own random color - - ![](static/documentation/images/image093_detrac.jpg) - - You can change any random color pointing to a needed box on a frame or on an - object sidebar. - -**Fill Opacity** slider - -Change the opacity of every shape in the annotation. - -![](static/documentation/images/image086_detrac.jpg) - -**Selected Fill Opacity** slider - -Change the opacity of the selected object's fill. - -![](static/documentation/images/image089_detrac.jpg) - -**Outlines borders** checkbox - -You can change a special shape border color by clicking on the `Eyedropper` icon. - -![](static/documentation/images/image088_detrac.jpg) - -**Show bitmap** checkbox - -If enabled all shapes are displayed in white and the background is black. - -![](static/documentation/images/image087_detrac.jpg) - -**Show projections** checkbox - -Enables / disables the display of auxiliary perspective lines. Only relevant for cuboids - -![](static/documentation/images/image090_detrac.jpg) - -## Shape mode (advanced) - -Basic operations in the mode were described in section [shape mode (basics)](#shape-mode-basics). - -**Occluded** -Occlusion is an attribute used if an object is occluded by another object or -isn't fully visible on the frame. Use `Q` shortcut to set the property -quickly. - -![](static/documentation/images/image065.jpg) - -Example: the three cars on the figure below should be labeled as **occluded**. - -![](static/documentation/images/image054_mapillary_vistas.jpg) - -If a frame contains too many objects and it is difficult to annotate them -due to many shapes placed mostly in the same place, it makes sense -to lock them. Shapes for locked objects are transparent, and it is easy to -annotate new objects. Besides, you can't change previously annotated objects -by accident. Shortcut: `L`. - -![](static/documentation/images/image066.jpg) - -## Track mode (advanced) - -Basic operations in the mode were described in section [track mode (basics)](#track-mode-basics). - -Shapes that were created in the track mode, have extra navigation buttons. - -- These buttons help to jump to the previous/next keyframe. - - ![](static/documentation/images/image056.jpg) - -- The button helps to jump to the initial frame and to the last keyframe. - - ![](static/documentation/images/image057.jpg) - -You can use the `Split` function to split one track into two tracks: - -![](static/documentation/images/gif010_detrac.gif) - -## Attribute annotation mode (advanced) - -Basic operations in the mode were described in section [attribute annotation mode (basics)](#attribute-annotation-mode-basics). - -It is possible to handle lots of objects on the same frame in the mode. - -![](static/documentation/images/image058_detrac.jpg) - -It is more convenient to annotate objects of the same type. In this case you can apply -the appropriate filter. For example, the following filter will -hide all objects except person: `label=="Person"`. - -To navigate between objects (person in this case), -use the following buttons `switch between objects in the frame` on the special panel: - -![](static/documentation/images/image026.jpg) - -or shortcuts: - -- `Tab` — go to the next object -- `Shift+Tab` — go to the previous object. - -In order to change the zoom level, go to settings (press `F3`) -in the workspace tab and set the value Attribute annotation mode (AAM) zoom margin in px. - -## AI Tools - -The tool is designed for semi-automatic and automatic annotation using DL models. -The tool is available only if there is a corresponding model. -For more details about DL models read the [Models](#models) section. - -### Interactors - -Interactors are used to create a polygon semi-automatically. -Supported DL models are not bound to the label and can be used for any objects. -To create a polygon usually you need to use regular or positive points. -For some kinds of segmentation negative points are available. -Positive points are the points related to the object. -Negative points should be placed outside the boundary of the object. -In most cases specifying positive points alone is enough to build a polygon. - -- Before you start, select the magic wand on the controls sidebar and go to the `Interactors` tab. - Then select a label for the polygon and a required DL model. - - ![](static/documentation/images/image114.jpg) - -- Click `Interact` to enter the interaction mode. Now you can place positive and/or negative points. - Left click creates a positive point and right click creates a negative point. - `Deep extreme cut` model requires a minimum of 4 points. After you set 4 positive points, - a request will be sent to the server and when the process is complete a polygon will be created. - If you are not satisfied with the result, you can set additional points or remove points by left-clicking on it. - If you want to postpone the request and create a few more points, hold down `Ctrl` and continue, - the request will be sent after the key is released. - - ![](static/documentation/images/image188_detrac.jpg) - -- To finish interaction, click on the icon on the controls sidebar or press `N` on your keyboard. - -- When the object is finished, you can edit it like a polygon. - You can read about editing polygons in the [Annotation with polygons](#annotation-with-polygons) section. - -### Detectors - -Detectors are used to automatically annotate one frame. Supported DL models are suitable only for certain labels. - -- Before you start, click the magic wand on the controls sidebar and select the Detectors icon tab. - You need to match the labels of the DL model (left column) with the labels in your task (right column). - Then click `Annotate`. - - ![](static/documentation/images/image187.jpg) - -- This action will automatically annotates one frame. - In the [Automatic annotation](#automatic-annotation) section you can read how to make automatic annotation of all frames. - -## OpenCV tools - -The tool based on [Open CV](https://opencv.org/) Computer Vision library which is an open-source product that includes many CV algorithms. Some of these algorithms can be used to simplify the annotation process. - -First step to work with OpenCV is to load it into CVAT. Click on the toolbar icon, then click `Load OpenCV`. - -![](static/documentation/images/image198.jpg) - -Once it is loaded, the tool's functionality will be available. - -### Intelligent scissors - -Intelligent scissors is an CV method of creating a polygon by placing points with automatic drawing of a line between them. -The distance between the adjacent points is limited by the threshold of action, -displayed as a red square which is tied to the cursor. - -- First, select the label and then click on the `intelligent scissors` button. - - ![](static/documentation/images/image199.jpg) - -- Create the first point on the boundary of the allocated object. - You will see a line repeating the outline of the object. -- Place the second point, so that the previous point is within the restrictive threshold. - After that a line repeating the object boundary will be automatically created between the points. - - ![](static/documentation/images/image200_detrac.jpg) - - To increase or lower the action threshold, hold `Ctrl` and scroll the mouse wheel. - Increasing action threshold will affect the performance. - During the drawing process you can remove the last point by clicking on it with the left mouse button. - -- Once all the points are placed, you can complete the creation of the object by clicking on the icon or clicking `N`. - As a result, a polygon will be created (read more about the polygons in the [annoation with polygons](#annotation-with-polygons)). - -## Annotation with rectangle by 4 points - -It is an efficient method of bounding box annotation, proposed -[here](https://arxiv.org/pdf/1708.02750.pdf). -Before starting, you need to make sure that the drawing method by 4 points is selected. - -![](static/documentation/images/image134.jpg) - -Press `Shape` or `Track` for entering drawing mode. Click on four extreme points: -the top, bottom, left- and right-most physical points on the object. -Drawing will be automatically completed right after clicking the fourth point. -Press `Esc` to cancel editing. - -![](static/documentation/images/gif016_mapillary_vistas.gif) - -## Annotation with polygons - -### Manual drawing - -It is used for semantic / instance segmentation. - -Before starting, you need to select `Polygon` on the controls sidebar and choose the correct Label. - -![](static/documentation/images/image084.jpg) - -- Click `Shape` to enter drawing mode. - There are two ways to draw a polygon: either create points by clicking or - by dragging the mouse on the screen while holding `Shift`. - -| Clicking points | Holding Shift+Dragging | -| -------------------------------------------------- | -------------------------------------------------- | -| ![](static/documentation/images/gif005_detrac.gif) | ![](static/documentation/images/gif006_detrac.gif) | - -- When `Shift` isn't pressed, you can zoom in/out (when scrolling the mouse - wheel) and move (when clicking the mouse wheel and moving the mouse), you can also - delete the previous point by right-clicking on it. -- Press `N` again for completing the shape. -- After creating the polygon, you can move the points or delete them by right-clicking and selecting `Delete point` - or clicking with pressed `Alt` key in the context menu. - -### Drawing using automatic borders - -![](static/documentation/images/gif025_mapillary_vistas.gif) - -You can use auto borders when drawing a polygon. Using automatic borders allows you to automatically trace -the outline of polygons existing in the annotation. - -- To do this, go to settings -> workspace tab and enable `Automatic Bordering` - or press `Ctrl` while drawing a polygon. - - ![](static/documentation/images/image161.jpg) - -- Start drawing / editing a polygon. -- Points of other shapes will be highlighted, which means that the polygon can be attached to them. -- Define the part of the polygon path that you want to repeat. - - ![](static/documentation/images/image157_mapillary_vistas.jpg) - -- Click on the first point of the contour part. - - ![](static/documentation/images/image158_mapillary_vistas.jpg) - -- Then click on any point located on part of the path. The selected point will be highlighted in purple. - - ![](static/documentation/images/image159_mapillary_vistas.jpg) - -- Сlick on the last point and the outline to this point will be built automatically. - - ![](static/documentation/images/image160_mapillary_vistas.jpg) - -Besides, you can set a fixed number of points in the `Number of points` field, then -drawing will be stopped automatically. To enable dragging you should right-click -inside the polygon and choose `Switch pinned property`. - -Below you can see results with opacity and black stroke: - -![](static/documentation/images/image064_mapillary_vistas.jpg) - -If you need to annotate small objects, increase `Image Quality` to -`95` in `Create task` dialog for your convenience. - -### Edit polygon - -To edit a polygon you have to click on it while holding `Shift`, it will open the polygon editor. - -- In the editor you can create new points or delete part of a polygon by closing the line on another point. -- When `Intelligent polygon cropping` option is activated in the settings, СVAT considers two criteria to decide which part of a polygon should be cut off during automatic editing. - - The first criteria is a number of cut points. - - The second criteria is a length of a cut curve. - - If both criteria recommend to cut the same part, algorithm works automatically, and if not, a user has to make the decision. - If you want to choose manually which part of a polygon should be cut off, disable `Intelligent polygon cropping` in the settings. In this case after closing the polygon, you can select the part of the polygon you want to leave. - - ![](static/documentation/images/image209.jpg) - -- You can press `Esc` to cancel editing. - - ![](static/documentation/images/gif007_mapillary_vistas.gif) - -### Cutting holes in polygons - -Currently, CVAT does not support cutting transparent holes in polygons. However, -it is poissble to generate holes in exported instance and class masks. -To do this, one needs to define a background class in the task and draw holes -with it as additional shapes above the shapes needed to have holes: - -The editor window: - ![The editor](static/documentation/images/mask_export_example1_editor.png) - -Remember to use z-axis ordering for shapes by \[\-\] and \[\+\, \=\] keys. - -Exported masks: - ![A class mask](static/documentation/images/mask_export_example1_cls_mask.png) ![An instance mask](static/documentation/images/mask_export_example1_inst_mask.png) - -Notice that it is currently impossible to have a single instance number for -internal shapes (they will be merged into the largest one and then covered by -"holes"). - -### Creating masks - -There are several formats in CVAT that can be used to export masks: -- `Segmentation Mask` (PASCAL VOC masks) -- `CamVid` -- `MOTS` -- `ICDAR` -- `COCO` (RLE-encoded instance masks, [guide](https://github.com/openvinotoolkit/cvat/blob/develop/cvat/apps/dataset_manager/formats/README.md#coco)) -- `TFRecord` ([over Datumaro](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/user_manual.md), [guide](https://github.com/openvinotoolkit/cvat/blob/develop/cvat/apps/dataset_manager/formats/README.md#tfrecord)): -- `Datumaro` - -An example of exported masks (in the `Segmentation Mask` format): - - ![A class mask](static/documentation/images/exported_cls_masks_example.png) ![An instance mask](static/documentation/images/exported_inst_masks_example.png) - -Important notices: -- Both boxes and polygons are converted into masks -- Grouped objects are considered as a single instance and exported as a single - mask (label and attributes are taken from the largest object in the group) - -#### Class colors - -All the labels have associated colors, which are used in the generated masks. -These colors can be changed in the task label properties: - - ![](static/documentation/images/label_color_picker.jpg) - -Label colors are also displayed in the annotation window on the right panel, -where you can show or hide specific labels -(only the presented labels are displayed): - - ![](static/documentation/images/label_panel_anno_window.jpg) - -A background class can be: -- A default class, which is implicitly-added, of black color (RGB 0, 0, 0) -- `background` class with any color (has a priority, name is case-insensitive) -- Any class of black color (RGB 0, 0, 0) - -To change backgound color in generated masks (default is black), -change `background` class color to the desired one. - - -## Annotation with polylines - -It is used for road markup annotation etc. - -Before starting, you need to select the `Polyline`. You can set a fixed number of points -in the `Number of points` field, then drawing will be stopped automatically. - -![](static/documentation/images/image085.jpg) - -Click `Shape` to enter drawing mode. There are two ways to draw a polyline — -you either create points by clicking or by dragging a mouse on the screen while holding `Shift`. -When `Shift` isn't pressed, you can zoom in/out (when scrolling the mouse wheel) -and move (when clicking the mouse wheel and moving the mouse), you can delete -previous points by right-clicking on it. Press `N` again to complete the shape. -You can delete a point by clicking on it with pressed `Ctrl` or right-clicking on a point -and selecting `Delete point`. Click with pressed `Shift` will open a polyline editor. -There you can create new points(by clicking or dragging) or delete part of a polygon closing -the red line on another point. Press `Esc` to cancel editing. - -![](static/documentation/images/image039_mapillary_vistas.jpg) - -## Annotation with points - -### Points in shape mode - -It is used for face, landmarks annotation etc. - -Before you start you need to select the `Points`. If necessary you can set a fixed number of points -in the `Number of points` field, then drawing will be stopped automatically. - -![](static/documentation/images/image042.jpg) - -Click `Shape` to entering the drawing mode. Now you can start annotation of the necessary area. -Points are automatically grouped — all points will be considered linked between each start and finish. -Press `N` again to finish marking the area. You can delete a point by clicking with pressed `Ctrl` -or right-clicking on a point and selecting `Delete point`. Clicking with pressed `Shift` will open the points -shape editor. There you can add new points into an existing shape. You can zoom in/out (when scrolling the mouse wheel) -and move (when clicking the mouse wheel and moving the mouse) while drawing. You can drag an object after -it has been drawn and change the position of individual points after finishing an object. - -![](static/documentation/images/image063_affectnet.jpg) - -### Linear interpolation with one point - -You can use linear interpolation for points to annotate a moving object: - -1. Before you start, select the `Points`. -1. Linear interpolation works only with one point, so you need to set `Number of points` to 1. -1. After that select the `Track`. - - ![](static/documentation/images/image122.jpg) - -1. Click `Track` to enter the drawing mode left-click to create a point and after that shape will be automatically completed. - - ![](static/documentation/images/image163_detrac.jpg) - -1. Move forward a few frames and move the point to the desired position, - this way you will create a keyframe and intermediate frames will be drawn automatically. - You can work with this object as with an interpolated track: you can hide it using the `Outside`, - move around keyframes, etc. - - ![](static/documentation/images/image165_detrac.jpg) - -1. This way you'll get linear interpolation using the ` Points`. - - ![](static/documentation/images/gif013_detrac.gif) - -## Annotation with cuboids - -It is used to annotate 3 dimensional objects such as cars, boxes, etc... -Currently the feature supports one point perspective and has the constraint -where the vertical edges are exactly parallel to the sides. - -### Creating the cuboid - -Before you start, you have to make sure that Cuboid is selected -and choose a drawing method ”from rectangle” or “by 4 points”. - -![](static/documentation/images/image091.jpg) - -#### Drawing cuboid by 4 points - -Choose a drawing method “by 4 points” and click Shape to enter the drawing mode. There are many ways to draw a cuboid. -You can draw the cuboid by placing 4 points, after that the drawing will be completed automatically. -The first 3 points determine the plane of the cuboid while the last point determines the depth of that plane. -For the first 3 points, it is recommended to only draw the 2 closest side faces, as well as the top and bottom face. - -A few examples: - -![](static/documentation/images/image177_mapillary_vistas.jpg) - -### Drawing cuboid from rectangle - -Choose a drawing method “from rectangle” and click Shape to enter the drawing mode. -When you draw using the rectangle method, you must select the frontal plane of the object using the bounding box. -The depth and perspective of the resulting cuboid can be edited. - -Example: - -![](static/documentation/images/image182_mapillary_vistas.jpg) - -### Editing the cuboid - -![](static/documentation/images/image178_mapillary_vistas.jpg) - -The cuboid can be edited in multiple ways: by dragging points, by dragging certain faces or by dragging planes. -First notice that there is a face that is painted with gray lines only, let us call it the front face. - -You can move the cuboid by simply dragging the shape behind the front face. -The cuboid can be extended by dragging on the point in the middle of the edges. -The cuboid can also be extended up and down by dragging the point at the vertices. - -![](static/documentation/images/gif017_mapillary_vistas.gif) - -To draw with perspective effects it should be assumed that the front face is the closest to the camera. -To begin simply drag the points on the vertices that are not on the gray/front face while holding `Shift`. -The cuboid can then be edited as usual. - -![](static/documentation/images/gif018_mapillary_vistas.gif) - -If you wish to reset perspective effects, you may right click on the cuboid, -and select `Reset perspective` to return to a regular cuboid. - -![](static/documentation/images/image180_mapillary_vistas.jpg) - -The location of the gray face can be swapped with the adjacent visible side face. -You can do it by right clicking on the cuboid and selecting `Switch perspective orientation`. -Note that this will also reset the perspective effects. - -![](static/documentation/images/image179_mapillary_vistas.jpg) - -Certain faces of the cuboid can also be edited, -these faces are: the left, right and dorsal faces, relative to the gray face. -Simply drag the faces to move them independently from the rest of the cuboid. - -![](static/documentation/images/gif020_mapillary_vistas.gif) - -You can also use cuboids in track mode, similar to rectangles in track mode ([basics](#track-mode-basics) and [advanced](#track-mode-advanced)) or [Track mode with polygons](#track-mode-with-polygons) - -## Annotation with Tags - -It is used to annotate frames, tags are not displayed in the workspace. -Before you start, open the drop-down list in the top panel and select `Tag annotation`. - -![](static/documentation/images/image183.jpg) - -The objects sidebar will be replaced with a special panel for working with tags. -Here you can select a label for a tag and add it by clicking on the `Add tag` button. -You can also customize hotkeys for each label. - -![](static/documentation/images/image181.jpg) - -If you need to use only one label for one frame, then enable the `Automatically go to the next frame` -checkbox, then after you add the tag the frame will automatically switch to the next. - -## Track mode with polygons - -Polygons in the track mode allow you to mark moving objects more accurately other than using a rectangle -([Tracking mode (basic)](#track-mode-basics); [Tracking mode (advanced)](#track-mode-advanced)). - -1. To create a polygon in the track mode, click the `Track` button. - - ![](static/documentation/images/image184.jpg) - -1. Create a polygon the same way as in the case of [Annotation with polygons](#annotation-with-polygons). - Press `N` to complete the polygon. - -1. Pay attention to the fact that the created polygon has a starting point and a direction, - these elements are important for annotation of the following frames. - -1. After going a few frames forward press `Shift+N`, the old polygon will disappear and you can create a new polygon. - The new starting point should match the starting point of the previously created polygon - (in this example, the top of the left mirror). The direction must also match (in this example, clockwise). - After creating the polygon, press `N` and the intermediate frames will be interpolated automatically. - - ![](static/documentation/images/image185_detrac.jpg) - -1. If you need to change the starting point, right-click on the desired point and select `Set starting point`. - To change the direction, right-click on the desired point and select switch orientation. - - ![](static/documentation/images/image186_detrac.jpg) - -There is no need to redraw the polygon every time using `Shift+N`, -instead you can simply move the points or edit a part of the polygon by pressing `Shift+Click`. - -## Review - -A special mode to check the annotation allows you to point to an object or area in the frame containing an error. -To go into review mode, you need to select `Request a review` in the menu and assign the user to run a check. - -![](static/documentation/images/image194.jpg) - -After that, the job status will be changed to `validation` -and the reviewer will be able to open the task in review mode. -Review mode is a UI mode, there is a special "issue" tool which you can use to identify objects -or areas in the frame and describe the problem. - -- To do this, first click `open an issue` icon on the controls sidebar: - - ![](static/documentation/images/image195.jpg) - -- Then click on an object in the frame to highlight the object or highlight the area by holding the left mouse button - and describe the problem. The object or area will be shaded in red. -- The created issue will appear in the workspace and in the `issues` tab on the objects sidebar. -- After you save the annotation, other users will be able to see the problem, comment on each issue - and change the status of the problem to `resolved`. -- You can use the arrows on the issues tab to navigate the frames that contain problems. - - ![](static/documentation/images/image196_detrac.jpg) - -- Once all the problems are marked, save the annotation, open the menu and select "submit the review". After that you'll see a form containing the verification statistics, here you can give an assessment of the job and choose further actions: - - - Accept - changes the status of the job to `completed`. - - Review next – passes the job to another user for re-review. - - Reject - changes the status of the job to `annotation`. - - ![](static/documentation/images/image197.jpg) - -## Automatic annotation - -Automatic Annotation is used for creating preliminary annotations. -To use Automatic Annotation you need a DL model. You can use primary models or models uploaded by a user. -You can find the list of available models in the `Models` section. - -1. To launch automatic annotation, you should open the dashboard and find a task which you want to annotate. - Then click the `Actions` button and choose option `Automatic Annotation` from the dropdown menu. - - ![](static/documentation/images/image119_detrac.jpg) - -1. In the dialog window select a model you need. DL models are created for specific labels, e.g. - the Crossroad model was taught using footage from cameras located above the highway and it is best to - use this model for the tasks with similar camera angles. - If it's necessary select the `Clean old annotations` checkbox. - Adjust the labels so that the task labels will correspond to the labels of the DL model. - For example, let’s consider a task where you have to annotate labels “car” and “person”. - You should connect the “person” label from the model to the “person” label in the task. - As for the “car” label, you should choose the most fitting label available in the model - the “vehicle” label. - The task requires to annotate cars only and choosing the “vehicle” label implies annotation of all vehicles, - in this case using auto annotation will help you complete the task faster. - Click `Submit` to begin the automatic annotation process. - - ![](static/documentation/images/image120.jpg) - -1. At runtime - you can see the percentage of completion. - You can cancel the automatic annotation by clicking on the `Cancel`button. - - ![](static/documentation/images/image121_detrac.jpg) - -1. The end result of an automatic annotation is an annotation with separate rectangles (or other shapes) - - ![](static/documentation/images/gif014_detrac.gif) - -1. You can combine separate bounding boxes into tracks using the `Person reidentification ` model. - To do this, click on the automatic annotation item in the action menu again and select the model - of the `ReID` type (in this case the `Person reidentification` model). - You can set the following parameters: - - - Model `Threshold` is a maximum cosine distance between objects’ embeddings. - - `Maximum distance` defines a maximum radius that an object can diverge between adjacent frames. - - ![](static/documentation/images/image133.jpg) - -1. You can remove false positives and edit tracks using `Split` and `Merge` functions. - - ![](static/documentation/images/gif015_detrac.gif) - -## Shape grouping - -This feature allows us to group several shapes. - -You may use the `Group Shapes` button or shortcuts: - -- `G` — start selection / end selection in group mode -- `Esc` — close group mode -- `Shift+G` — reset group for selected shapes - -You may select shapes clicking on them or selecting an area. - -Grouped shapes will have `group_id` filed in dumped annotation. - -Also you may switch color distribution from an instance (default) to a group. -You have to switch `Color By Group` checkbox for that. - -Shapes that don't have `group_id`, will be highlighted in white. - -![](static/documentation/images/image078_detrac.jpg) - -![](static/documentation/images/image077_detrac.jpg) - -## Filter - -There are some reasons to use the feature: - -1. When you use a filter, objects that don't match the filter will be hidden. -1. The fast navigation between frames which have an object of interest. - Use the `Left Arrow` / `Right Arrow` keys for this purpose - or customize the UI buttons by right-clicking and select `switching by filter`. - If there are no objects which correspond to the filter, - you will go to the previous / next frame which contains any annotated objects. - -To apply filters you need to click on the button on the top panel. - -![](static/documentation/images/image059.jpg) - -It will open a window for filter input. Here you will find two buttons: `Add rule` and `Add group`. - -![](static/documentation/images/image202.jpg) - -### Rules - -The "Add rule" button adds a rule for objects display. A rule may use the following properties: - -![](static/documentation/images/image204.jpg) - -**Supported properties:** - -| Properties | Supported values | Description | -| ------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -| `Label` | all the label names that are in the task | label name | -| `Type` | shape, track or tag | type of object | -| `Shape` | all shape types | type of shape | -| `Occluded` | true or false | occluded ([read more](#shape-mode-advanced)) | -| `Width` | number of px or field | shape width | -| `Height` | number of px or field | shape height | -| `ServerID` | number or field | ID of the object on the server
    (You can find out by forming a link to the object through the Action menu) | -| `ObjectID` | number or field | ID of the object in your client
    (indicated on the objects sidebar) | -| `Attributes` | some other fields including attributes with a
    similar type or a specific attribute value | any fields specified by a label | - -**Supported operators for properties:** - -`==` - Equally; `!=` - Not equal; `>` - More; `>=` - More or equal; `<` - Less; `<=` - Less or equal; - -`Any in`; `Not in` - these operators allow you to set multiple values in one rule; - -![](static/documentation/images/image203.jpg) - -`Is empty`; `is not empty` – these operators don't require to input a value. - -`Between`; `Not between` – these operators allow you to choose a range between two values. - -Some properties support two types of values that you can choose: - -![](static/documentation/images/image205.jpg) - -You can add multiple rules, to do so click the add rule button and set another rule. Once you've set a new rule, you'll be able to choose which operator they will be connected by: `And` or `Or`. - -![](static/documentation/images/image206.jpg) - -All subsequent rules will be joined by the chosen operator. Click `Submit` to apply the filter or if you want multiple rules to be connected by different operators, use groups. - -### Groups - -To add a group, click the "add group" button. Inside the group you can create rules or groups. - -![](static/documentation/images/image207.jpg) - -If there is more than one rule in the group, they can be connected by `And` or `Or` operators. -The rule group will work as well as a separate rule outside the group and will be joined by an -operator outside the group. -You can create groups within other groups, to do so you need to click the add group button within the group. - -You can move rules and groups. To move the rule or group, drag it by the button. -To remove the rule or group, click on the `Delete` button. - -![](static/documentation/images/image208.jpg) - -If you activate the `Not` button, objects that don't match the group will be filtered out. -Click `Submit` to apply the filter. -The "Cancel" button undoes the filter. The `Clear filter` button removes the filter. - -Once applied filter automatically appears in `Recent used` list. Maximum length of the list is 10. - ---- - -## Analytics - -If your CVAT instance was created with analytics support, you can press the `Analytics` button in the dashboard -and analytics and journals will be opened in a new tab. - -![](static/documentation/images/image113.jpg) - -The analytics allows you to see how much time every user spends on each task -and how much work they did over any time range. - -![](static/documentation/images/image097.jpg) - -It also has an activity graph which can be modified with a number of users shown and a timeframe. - -![](static/documentation/images/image096.jpg) - -## Shortcuts - -Many UI elements have shortcut hints. Put your pointer to a required element to see it. - -![](static/documentation/images/image075.jpg) - -| Shortcut | Common | -| -------------------------- | -------------------------------------------------------------------------------------------------------- | -| | _Main functions_ | -| `F1` | Open/hide the list of available shortcuts | -| `F2` | Go to the settings page or go back | -| `Ctrl+S` | Go to the settings page or go back | -| `Ctrl+Z` | Cancel the latest action related with objects | -| `Ctrl+Shift+Z` or `Ctrl+Y` | Cancel undo action | -| Hold `Mouse Wheel` | To move an image frame (for example, while drawing) | -| | _Player_ | -| `F` | Go to the next frame | -| `D` | Go to the previous frame | -| `V` | Go forward with a step | -| `C` | Go backward with a step | -| `Right` | Search the next frame that satisfies to the filters
    or next frame which contain any objects | -| `Left` | Search the previous frame that satisfies to the filters
    or previous frame which contain any objects | -| `Space` | Start/stop automatic changing frames | -| `` ` `` or `~` | Focus on the element to change the current frame | -| | _Modes_ | -| `N` | Repeat the latest procedure of drawing with the same parameters | -| `M` | Activate or deactivate mode to merging shapes | -| `Alt+M` | Activate or deactivate mode to spliting shapes | -| `G` | Activate or deactivate mode to grouping shapes | -| `Shift+G` | Reset group for selected shapes (in group mode) | -| `Esc` | Cancel any active canvas mode | -| | _Image operations_ | -| `Ctrl+R` | Change image angle (add 90 degrees) | -| `Ctrl+Shift+R` | Change image angle (substract 90 degrees) | -| `Shift+B+=` | Increase brightness level for the image | -| `Shift+B+-` | Decrease brightness level for the image | -| `Shift+C+=` | Increase contrast level for the image | -| `Shift+C+-` | Decrease contrast level for the image | -| `Shift+S+=` | Increase saturation level for the image | -| `Shift+S+-` | Increase contrast level for the image | -| `Shift+G+=` | Make the grid more visible | -| `Shift+G+-` | Make the grid less visible | -| `Shift+G+Enter` | Set another color for the image grid | -| | _Operations with objects_ | -| `Ctrl` | Switch automatic bordering for polygons and polylines during drawing/editing | -| Hold `Ctrl` | When the shape is active and fix it | -| `Alt+Click` on point | Deleting a point (used when hovering over a point of polygon, polyline, points) | -| `Shift+Click` on point | Editing a shape (used when hovering over a point of polygon, polyline or points) | -| `Right-Click` on shape | Display of an object element from objects sidebar | -| `T+L` | Change locked state for all objects in the sidebar | -| `L` | Change locked state for an active object | -| `T+H` | Change hidden state for objects in the sidebar | -| `H` | Change hidden state for an active object | -| `Q` or `/` | Change occluded property for an active object | -| `Del` or `Shift+Del` | Delete an active object. Use shift to force delete of locked objects | -| `-` or `_` | Put an active object "farther" from the user (decrease z axis value) | -| `+` or `=` | Put an active object "closer" to the user (increase z axis value) | -| `Ctrl+C` | Copy shape to CVAT internal clipboard | -| `Ctrl+V` | Paste a shape from internal CVAT clipboard | -| Hold `Ctrl` while pasting | When pasting shape from the buffer for multiple pasting. | -| `Crtl+B` | Make a copy of the object on the following frames | -| `Ctrl+Num(0..9)` | Сhanges the object label if pressed while the cursor is pointed on the object 
    / changes default label if pressed while the cursor is not pointed on an object| -| | _Operations are available only for track_ | -| `K` | Change keyframe property for an active track | -| `O` | Change outside property for an active track | -| `R` | Go to the next keyframe of an active track | -| `E` | Go to the previous keyframe of an active track | -| | _Attribute annotation mode_ | -| `Up Arrow` | Go to the next attribute (up) | -| `Down Arrow` | Go to the next attribute (down) | -| `Tab` | Go to the next annotated object in current frame | -| `Shift+Tab` | Go to the previous annotated object in current frame | -| `` | Assign a corresponding value to the current attribute | diff --git a/cvat/apps/documentation/views.py b/cvat/apps/documentation/views.py deleted file mode 100644 index 0b40287f..00000000 --- a/cvat/apps/documentation/views.py +++ /dev/null @@ -1,21 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.shortcuts import render -import os - -def UserGuideView(request): - module_dir = os.path.dirname(__file__) - doc_path = os.path.join(module_dir, 'user_guide.md') - - return render(request, 'documentation/user_guide.html', - context={"user_guide": open(doc_path, "r").read()}) - -def XmlFormatView(request): - module_dir = os.path.dirname(__file__) - doc_path = os.path.join(module_dir, 'xml_format.md') - - return render(request, 'documentation/xml_format.html', - context={"xml_format": open(doc_path, "r").read()}) diff --git a/cvat/apps/engine/admin.py b/cvat/apps/engine/admin.py index ddacf69a..0dab80a8 100644 --- a/cvat/apps/engine/admin.py +++ b/cvat/apps/engine/admin.py @@ -4,14 +4,14 @@ # SPDX-License-Identifier: MIT from django.contrib import admin -from .models import Task, Segment, Job, Label, AttributeSpec, Project +from .models import Task, Segment, Job, Label, AttributeSpec, Project, CloudStorage class JobInline(admin.TabularInline): model = Job can_delete = False # Don't show extra lines to add an object - def has_add_permission(self, request, object=None): + def has_add_permission(self, request, obj): return False class SegmentInline(admin.TabularInline): @@ -21,7 +21,7 @@ class SegmentInline(admin.TabularInline): can_delete = False # Don't show extra lines to add an object - def has_add_permission(self, request, object=None): + def has_add_permission(self, request, obj): return False @@ -84,8 +84,20 @@ class TaskAdmin(admin.ModelAdmin): def has_add_permission(self, request): return False +class CloudStorageAdmin(admin.ModelAdmin): + date_hierarchy = 'updated_date' + readonly_fields = ('created_date', 'updated_date', 'provider_type') + list_display = ('__str__', 'resource', 'owner', 'created_date', 'updated_date') + search_fields = ('provider_type', 'display_name', 'resource', 'owner__username', 'owner__first_name', + 'owner__last_name', 'owner__email',) + + empty_value_display = 'unknown' + + def has_add_permission(self, request): + return False admin.site.register(Task, TaskAdmin) admin.site.register(Segment, SegmentAdmin) admin.site.register(Label, LabelAdmin) admin.site.register(Project, ProjectAdmin) +admin.site.register(CloudStorage, CloudStorageAdmin) diff --git a/cvat/apps/engine/backup.py b/cvat/apps/engine/backup.py new file mode 100644 index 00000000..da42cab6 --- /dev/null +++ b/cvat/apps/engine/backup.py @@ -0,0 +1,550 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import io +import os +from enum import Enum +import shutil +from zipfile import ZipFile + +from django.conf import settings +from django.db import transaction +from rest_framework.parsers import JSONParser +from rest_framework.renderers import JSONRenderer + +import cvat.apps.dataset_manager as dm +from cvat.apps.engine import models +from cvat.apps.engine.log import slogger +from cvat.apps.engine.serializers import (AttributeSerializer, DataSerializer, + LabeledDataSerializer, SegmentSerializer, SimpleJobSerializer, TaskSerializer, + ReviewSerializer, IssueSerializer, CommentSerializer) +from cvat.apps.engine.utils import av_scan_paths +from cvat.apps.engine.models import StorageChoice, StorageMethodChoice, DataChoice +from cvat.apps.engine.task import _create_thread + + +class Version(Enum): + V1 = '1.0' + +class _TaskBackupBase(): + MANIFEST_FILENAME = 'task.json' + ANNOTATIONS_FILENAME = 'annotations.json' + DATA_DIRNAME = 'data' + TASK_DIRNAME = 'task' + + def _prepare_meta(self, allowed_keys, meta): + keys_to_drop = set(meta.keys()) - allowed_keys + if keys_to_drop: + logger = slogger.task[self._db_task.id] if hasattr(self, '_db_task') else slogger.glob + + logger.warning('the following keys are dropped {}'.format(keys_to_drop)) + for key in keys_to_drop: + del meta[key] + + return meta + + def _prepare_task_meta(self, task): + allowed_fields = { + 'name', + 'bug_tracker', + 'status', + 'subset', + 'labels', + } + + return self._prepare_meta(allowed_fields, task) + + def _prepare_data_meta(self, data): + allowed_fields = { + 'chunk_size', + 'image_quality', + 'start_frame', + 'stop_frame', + 'frame_filter', + 'chunk_type', + 'storage_method', + 'storage', + } + + self._prepare_meta(allowed_fields, data) + if 'frame_filter' in data and not data['frame_filter']: + data.pop('frame_filter') + + return data + + def _prepare_job_meta(self, job): + allowed_fields = { + 'status', + } + return self._prepare_meta(allowed_fields, job) + + def _prepare_attribute_meta(self, attribute): + allowed_fields = { + 'name', + 'mutable', + 'input_type', + 'default_value', + 'values', + } + return self._prepare_meta(allowed_fields, attribute) + + def _prepare_label_meta(self, labels): + allowed_fields = { + 'name', + 'color', + 'attributes', + } + return self._prepare_meta(allowed_fields, labels) + + def _prepare_annotations(self, annotations, label_mapping): + allowed_fields = { + 'label', + 'label_id', + 'type', + 'occluded', + 'outside', + 'z_order', + 'points', + 'frame', + 'group', + 'source', + 'attributes', + 'shapes', + } + + def _update_attribute(attribute, label): + if 'name' in attribute: + source, dest = attribute.pop('name'), 'spec_id' + else: + source, dest = attribute.pop('spec_id'), 'name' + attribute[dest] = label_mapping[label]['attributes'][source] + + def _update_label(shape): + if 'label_id' in shape: + source, dest = shape.pop('label_id'), 'label' + elif 'label' in shape: + source, dest = shape.pop('label'), 'label_id' + shape[dest] = label_mapping[source]['value'] + + return source + + for tag in annotations['tags']: + label = _update_label(tag) + for attr in tag['attributes']: + _update_attribute(attr, label) + self._prepare_meta(allowed_fields, tag) + + for shape in annotations['shapes']: + label = _update_label(shape) + for attr in shape['attributes']: + _update_attribute(attr, label) + self._prepare_meta(allowed_fields, shape) + + for track in annotations['tracks']: + label = _update_label(track) + for shape in track['shapes']: + for attr in shape['attributes']: + _update_attribute(attr, label) + self._prepare_meta(allowed_fields, shape) + + for attr in track['attributes']: + _update_attribute(attr, label) + self._prepare_meta(allowed_fields, track) + + return annotations + + def _prepare_review_meta(self, review): + allowed_fields = { + 'estimated_quality', + 'status', + 'issues', + } + return self._prepare_meta(allowed_fields, review) + + def _prepare_issue_meta(self, issue): + allowed_fields = { + 'frame', + 'position', + 'created_date', + 'resolved_date', + 'comments', + } + return self._prepare_meta(allowed_fields, issue) + + def _prepare_comment_meta(self, comment): + allowed_fields = { + 'message', + 'created_date', + 'updated_date', + } + return self._prepare_meta(allowed_fields, comment) + + def _get_db_jobs(self): + if self._db_task: + db_segments = list(self._db_task.segment_set.all().prefetch_related('job_set')) + db_segments.sort(key=lambda i: i.job_set.first().id) + db_jobs = (s.job_set.first() for s in db_segments) + return db_jobs + return () + +class TaskExporter(_TaskBackupBase): + def __init__(self, pk, version=Version.V1): + self._db_task = models.Task.objects.prefetch_related('data__images').select_related('data__video').get(pk=pk) + self._db_data = self._db_task.data + self._version = version + + db_labels = (self._db_task.project if self._db_task.project_id else self._db_task).label_set.all().prefetch_related( + 'attributespec_set') + + self._label_mapping = {} + self._label_mapping = {db_label.id: db_label.name for db_label in db_labels} + self._attribute_mapping = {} + for db_label in db_labels: + self._label_mapping[db_label.id] = { + 'value': db_label.name, + 'attributes': {}, + } + for db_attribute in db_label.attributespec_set.all(): + self._label_mapping[db_label.id]['attributes'][db_attribute.id] = db_attribute.name + + def _write_files(self, source_dir, zip_object, files, target_dir): + for filename in files: + arcname = os.path.normpath( + os.path.join( + target_dir, + os.path.relpath(filename, source_dir), + ) + ) + zip_object.write(filename=filename, arcname=arcname) + + def _write_directory(self, source_dir, zip_object, target_dir, recursive=True, exclude_files=None): + for root, dirs, files in os.walk(source_dir, topdown=True): + if not recursive: + dirs.clear() + + if files: + self._write_files( + source_dir=source_dir, + zip_object=zip_object, + files=(os.path.join(root, f) for f in files if not exclude_files or f not in exclude_files), + target_dir=target_dir, + ) + + def _write_data(self, zip_object): + if self._db_data.storage == StorageChoice.LOCAL: + self._write_directory( + source_dir=self._db_data.get_upload_dirname(), + zip_object=zip_object, + target_dir=self.DATA_DIRNAME, + ) + elif self._db_data.storage == StorageChoice.SHARE: + data_dir = settings.SHARE_ROOT + if hasattr(self._db_data, 'video'): + media_files = (os.path.join(data_dir, self._db_data.video.path), ) + else: + media_files = (os.path.join(data_dir, im.path) for im in self._db_data.images.all().order_by('frame')) + + self._write_files( + source_dir=data_dir, + zip_object=zip_object, + files=media_files, + target_dir=self.DATA_DIRNAME + ) + + upload_dir = self._db_data.get_upload_dirname() + self._write_files( + source_dir=upload_dir, + zip_object=zip_object, + files=(os.path.join(upload_dir, f) for f in ('manifest.jsonl',)), + target_dir=self.DATA_DIRNAME + ) + else: + raise NotImplementedError() + + def _write_task(self, zip_object): + task_dir = self._db_task.get_task_dirname() + self._write_directory( + source_dir=task_dir, + zip_object=zip_object, + target_dir=self.TASK_DIRNAME, + recursive=False, + ) + + def _write_manifest(self, zip_object): + def serialize_task(): + task_serializer = TaskSerializer(self._db_task) + task_serializer.fields.pop('url') + task_serializer.fields.pop('owner') + task_serializer.fields.pop('assignee') + task_serializer.fields.pop('segments') + + task = self._prepare_task_meta(task_serializer.data) + task['labels'] = [self._prepare_label_meta(l) for l in task['labels']] + for label in task['labels']: + label['attributes'] = [self._prepare_attribute_meta(a) for a in label['attributes']] + + return task + + def serialize_comment(db_comment): + comment_serializer = CommentSerializer(db_comment) + comment_serializer.fields.pop('author') + + return self._prepare_comment_meta(comment_serializer.data) + + def serialize_issue(db_issue): + issue_serializer = IssueSerializer(db_issue) + issue_serializer.fields.pop('owner') + issue_serializer.fields.pop('resolver') + + issue = issue_serializer.data + issue['comments'] = (serialize_comment(c) for c in db_issue.comment_set.order_by('id')) + + return self._prepare_issue_meta(issue) + + def serialize_review(db_review): + review_serializer = ReviewSerializer(db_review) + review_serializer.fields.pop('reviewer') + review_serializer.fields.pop('assignee') + + review = review_serializer.data + review['issues'] = (serialize_issue(i) for i in db_review.issue_set.order_by('id')) + + return self._prepare_review_meta(review) + + def serialize_segment(db_segment): + db_job = db_segment.job_set.first() + job_serializer = SimpleJobSerializer(db_job) + job_serializer.fields.pop('url') + job_serializer.fields.pop('assignee') + job_serializer.fields.pop('reviewer') + job_data = self._prepare_job_meta(job_serializer.data) + + segment_serailizer = SegmentSerializer(db_segment) + segment_serailizer.fields.pop('jobs') + segment = segment_serailizer.data + segment.update(job_data) + + db_reviews = db_job.review_set.order_by('id') + segment['reviews'] = (serialize_review(r) for r in db_reviews) + + return segment + + def serialize_jobs(): + db_segments = list(self._db_task.segment_set.all()) + db_segments.sort(key=lambda i: i.job_set.first().id) + return (serialize_segment(s) for s in db_segments) + + def serialize_data(): + data_serializer = DataSerializer(self._db_data) + data = data_serializer.data + data['chunk_type'] = data.pop('compressed_chunk_type') + return self._prepare_data_meta(data) + + task = serialize_task() + task['version'] = self._version.value + task['data'] = serialize_data() + task['jobs'] = serialize_jobs() + + zip_object.writestr(self.MANIFEST_FILENAME, data=JSONRenderer().render(task)) + + def _write_annotations(self, zip_object): + def serialize_annotations(): + job_annotations = [] + db_jobs = self._get_db_jobs() + db_job_ids = (j.id for j in db_jobs) + for db_job_id in db_job_ids: + annotations = dm.task.get_job_data(db_job_id) + annotations_serializer = LabeledDataSerializer(data=annotations) + annotations_serializer.is_valid(raise_exception=True) + job_annotations.append(self._prepare_annotations(annotations_serializer.data, self._label_mapping)) + + return job_annotations + + annotations = serialize_annotations() + zip_object.writestr(self.ANNOTATIONS_FILENAME, data=JSONRenderer().render(annotations)) + + def export_to(self, filename): + if self._db_task.data.storage_method == StorageMethodChoice.FILE_SYSTEM and \ + self._db_task.data.storage == StorageChoice.SHARE: + raise Exception('The task cannot be exported because it does not contain any raw data') + with ZipFile(filename, 'w') as output_file: + self._write_data(output_file) + self._write_task(output_file) + self._write_manifest(output_file) + self._write_annotations(output_file) + +class TaskImporter(_TaskBackupBase): + def __init__(self, filename, user_id): + self._filename = filename + self._user_id = user_id + self._manifest, self._annotations = self._read_meta() + self._version = self._read_version() + self._labels_mapping = {} + self._db_task = None + + def _read_meta(self): + with ZipFile(self._filename, 'r') as input_file: + manifest = JSONParser().parse(io.BytesIO(input_file.read(self.MANIFEST_FILENAME))) + annotations = JSONParser().parse(io.BytesIO(input_file.read(self.ANNOTATIONS_FILENAME))) + + return manifest, annotations + + def _read_version(self): + version = self._manifest.pop('version') + try: + return Version(version) + except ValueError: + raise ValueError('{} version is not supported'.format(version)) + + @staticmethod + def _prepare_dirs(filepath): + target_dir = os.path.dirname(filepath) + if not os.path.exists(target_dir): + os.makedirs(target_dir) + + def _create_labels(self, db_task, labels): + label_mapping = {} + + for label in labels: + label_name = label['name'] + attributes = label.pop('attributes', []) + db_label = models.Label.objects.create(task=db_task, **label) + label_mapping[label_name] = { + 'value': db_label.id, + 'attributes': {}, + } + + for attribute in attributes: + attribute_name = attribute['name'] + attribute_serializer = AttributeSerializer(data=attribute) + attribute_serializer.is_valid(raise_exception=True) + db_attribute = attribute_serializer.save(label=db_label) + label_mapping[label_name]['attributes'][attribute_name] = db_attribute.id + + return label_mapping + + def _create_annotations(self, db_job, annotations): + self._prepare_annotations(annotations, self._labels_mapping) + + serializer = LabeledDataSerializer(data=annotations) + serializer.is_valid(raise_exception=True) + dm.task.put_job_data(db_job.id, serializer.data) + + @staticmethod + def _calculate_segment_size(jobs): + segment_size = jobs[0]['stop_frame'] - jobs[0]['start_frame'] + 1 + overlap = 0 if len(jobs) == 1 else jobs[0]['stop_frame'] - jobs[1]['start_frame'] + 1 + + return segment_size, overlap + + def _import_task(self): + + def _create_comment(comment, db_issue): + comment['issue'] = db_issue.id + comment_serializer = CommentSerializer(data=comment) + comment_serializer.is_valid(raise_exception=True) + db_comment = comment_serializer.save() + return db_comment + + def _create_issue(issue, db_review, db_job): + issue['review'] = db_review.id + issue['job'] = db_job.id + comments = issue.pop('comments') + + issue_serializer = IssueSerializer(data=issue) + issue_serializer.is_valid( raise_exception=True) + db_issue = issue_serializer.save() + + for comment in comments: + _create_comment(comment, db_issue) + + return db_issue + + def _create_review(review, db_job): + review['job'] = db_job.id + issues = review.pop('issues') + + review_serializer = ReviewSerializer(data=review) + review_serializer.is_valid(raise_exception=True) + db_review = review_serializer.save() + + for issue in issues: + _create_issue(issue, db_review, db_job) + + return db_review + + data = self._manifest.pop('data') + labels = self._manifest.pop('labels') + jobs = self._manifest.pop('jobs') + + self._prepare_task_meta(self._manifest) + self._manifest['segment_size'], self._manifest['overlap'] = self._calculate_segment_size(jobs) + self._manifest["owner_id"] = self._user_id + + self._db_task = models.Task.objects.create(**self._manifest) + task_path = self._db_task.get_task_dirname() + if os.path.isdir(task_path): + shutil.rmtree(task_path) + + os.makedirs(self._db_task.get_task_logs_dirname()) + os.makedirs(self._db_task.get_task_artifacts_dirname()) + + self._labels_mapping = self._create_labels(self._db_task, labels) + + self._prepare_data_meta(data) + data_serializer = DataSerializer(data=data) + data_serializer.is_valid(raise_exception=True) + db_data = data_serializer.save() + self._db_task.data = db_data + self._db_task.save() + + data_path = self._db_task.data.get_upload_dirname() + uploaded_files = [] + with ZipFile(self._filename, 'r') as input_file: + for f in input_file.namelist(): + if f.startswith(self.DATA_DIRNAME + os.path.sep): + target_file = os.path.join(data_path, os.path.relpath(f, self.DATA_DIRNAME)) + self._prepare_dirs(target_file) + with open(target_file, "wb") as out: + out.write(input_file.read(f)) + uploaded_files.append(os.path.relpath(f, self.DATA_DIRNAME)) + elif f.startswith(self.TASK_DIRNAME + os.path.sep): + target_file = os.path.join(task_path, os.path.relpath(f, self.TASK_DIRNAME)) + self._prepare_dirs(target_file) + with open(target_file, "wb") as out: + out.write(input_file.read(f)) + + data['use_zip_chunks'] = data.pop('chunk_type') == DataChoice.IMAGESET + data = data_serializer.data + data['client_files'] = uploaded_files + _create_thread(self._db_task.pk, data.copy(), True) + db_data.start_frame = data['start_frame'] + db_data.stop_frame = data['stop_frame'] + db_data.frame_filter = data['frame_filter'] + db_data.storage = StorageChoice.LOCAL + db_data.save(update_fields=['start_frame', 'stop_frame', 'frame_filter', 'storage']) + + for db_job, job in zip(self._get_db_jobs(), jobs): + db_job.status = job['status'] + db_job.save() + + for review in job['reviews']: + _create_review(review, db_job) + + def _import_annotations(self): + db_jobs = self._get_db_jobs() + for db_job, annotations in zip(db_jobs, self._annotations): + self._create_annotations(db_job, annotations) + + def import_task(self): + self._import_task() + self._import_annotations() + return self._db_task + +@transaction.atomic +def import_task(filename, user): + av_scan_paths(filename) + task_importer = TaskImporter(filename, user) + db_task = task_importer.import_task() + return db_task.id diff --git a/cvat/apps/engine/cache.py b/cvat/apps/engine/cache.py index 077e6ef1..75196ee8 100644 --- a/cvat/apps/engine/cache.py +++ b/cvat/apps/engine/cache.py @@ -7,13 +7,16 @@ from io import BytesIO from diskcache import Cache from django.conf import settings +from tempfile import NamedTemporaryFile +from cvat.apps.engine.log import slogger from cvat.apps.engine.media_extractors import (Mpeg4ChunkWriter, Mpeg4CompressedChunkWriter, ZipChunkWriter, ZipCompressedChunkWriter, ImageDatasetManifestReader, VideoDatasetManifestReader) from cvat.apps.engine.models import DataChoice, StorageChoice from cvat.apps.engine.models import DimensionType - +from cvat.apps.engine.cloud_provider import get_cloud_storage_instance, Credentials +from cvat.apps.engine.utils import md5_hash class CacheInteraction: def __init__(self, dimension=DimensionType.DIM_2D): self._cache = Cache(settings.CACHE_ROOT) @@ -49,10 +52,12 @@ class CacheInteraction: buff = BytesIO() upload_dir = { StorageChoice.LOCAL: db_data.get_upload_dirname(), - StorageChoice.SHARE: settings.SHARE_ROOT + StorageChoice.SHARE: settings.SHARE_ROOT, + StorageChoice.CLOUD_STORAGE: db_data.get_upload_dirname(), }[db_data.storage] if hasattr(db_data, 'video'): source_path = os.path.join(upload_dir, db_data.video.path) + reader = VideoDatasetManifestReader(manifest_path=db_data.get_manifest_path(), source_path=source_path, chunk_number=chunk_number, chunk_size=db_data.chunk_size, start=db_data.start_frame, @@ -64,12 +69,44 @@ class CacheInteraction: chunk_number=chunk_number, chunk_size=db_data.chunk_size, start=db_data.start_frame, stop=db_data.stop_frame, step=db_data.get_frame_step()) - for item in reader: - source_path = os.path.join(upload_dir, f"{item['name']}{item['extension']}") - images.append((source_path, source_path, None)) - + if db_data.storage == StorageChoice.CLOUD_STORAGE: + db_cloud_storage = db_data.cloud_storage + credentials = Credentials() + credentials.convert_from_db({ + 'type': db_cloud_storage.credentials_type, + 'value': db_cloud_storage.credentials, + }) + details = { + 'resource': db_cloud_storage.resource, + 'credentials': credentials, + 'specific_attributes': db_cloud_storage.get_specific_attributes() + } + cloud_storage_instance = get_cloud_storage_instance(cloud_provider=db_cloud_storage.provider_type, **details) + cloud_storage_instance.initialize_content() + for item in reader: + name = f"{item['name']}{item['extension']}" + if name not in cloud_storage_instance: + raise Exception('{} file was not found on a {} storage'.format(name, cloud_storage_instance.name)) + with NamedTemporaryFile(mode='w+b', prefix='cvat', suffix=name, delete=False) as temp_file: + source_path = temp_file.name + buf = cloud_storage_instance.download_fileobj(name) + temp_file.write(buf.getvalue()) + checksum = item.get('checksum', None) + if not checksum: + slogger.glob.warning('A manifest file does not contain checksum for image {}'.format(item.get('name'))) + if checksum and not md5_hash(source_path) == checksum: + slogger.glob.warning('Hash sums of files {} do not match'.format(name)) + images.append((source_path, source_path, None)) + else: + for item in reader: + source_path = os.path.join(upload_dir, f"{item['name']}{item['extension']}") + images.append((source_path, source_path, None)) writer.save_as_chunk(images, buff) buff.seek(0) + if db_data.storage == StorageChoice.CLOUD_STORAGE: + images = [image[0] for image in images if os.path.exists(image[0])] + for image_path in images: + os.remove(image_path) return buff, mime_type def save_chunk(self, db_data_id, chunk_number, quality, buff, mime_type): diff --git a/cvat/apps/engine/cloud_provider.py b/cvat/apps/engine/cloud_provider.py new file mode 100644 index 00000000..017d5f7d --- /dev/null +++ b/cvat/apps/engine/cloud_provider.py @@ -0,0 +1,296 @@ +#from dataclasses import dataclass +from abc import ABC, abstractmethod, abstractproperty +from io import BytesIO + +import boto3 +from boto3.s3.transfer import TransferConfig +from botocore.exceptions import WaiterError +from botocore.handlers import disable_signing + +from azure.storage.blob import BlobServiceClient +from azure.core.exceptions import ResourceExistsError +from azure.storage.blob import PublicAccess + +from cvat.apps.engine.log import slogger +from cvat.apps.engine.models import CredentialsTypeChoice, CloudProviderChoice + +class _CloudStorage(ABC): + + def __init__(self): + self._files = [] + + @abstractproperty + def name(self): + pass + + @abstractmethod + def create(self): + pass + + @abstractmethod + def exists(self): + pass + + @abstractmethod + def initialize_content(self): + pass + + @abstractmethod + def download_fileobj(self, key): + pass + + def download_file(self, key, path): + file_obj = self.download_fileobj(key) + if isinstance(file_obj, BytesIO): + with open(path, 'wb') as f: + f.write(file_obj.getvalue()) + else: + raise NotImplementedError("Unsupported type {} was found".format(type(file_obj))) + + @abstractmethod + def upload_file(self, file_obj, file_name): + pass + + def __contains__(self, file_name): + return file_name in (item['name'] for item in self._files) + + def __len__(self): + return len(self._files) + + @property + def content(self): + return list(map(lambda x: x['name'] , self._files)) + +def get_cloud_storage_instance(cloud_provider, resource, credentials, specific_attributes=None): + instance = None + if cloud_provider == CloudProviderChoice.AWS_S3: + instance = AWS_S3( + bucket=resource, + access_key_id=credentials.key, + secret_key=credentials.secret_key, + session_token=credentials.session_token, + region=specific_attributes.get('region', 'us-east-2') + ) + elif cloud_provider == CloudProviderChoice.AZURE_CONTAINER: + instance = AzureBlobContainer( + container=resource, + account_name=credentials.account_name, + sas_token=credentials.session_token + ) + else: + raise NotImplementedError() + return instance + +class AWS_S3(_CloudStorage): + waiter_config = { + 'Delay': 5, # The amount of time in seconds to wait between attempts. Default: 5 + 'MaxAttempts': 3, # The maximum number of attempts to be made. Default: 20 + } + transfer_config = { + 'max_io_queue': 10, + } + def __init__(self, + bucket, + region, + access_key_id=None, + secret_key=None, + session_token=None): + super().__init__() + if all([access_key_id, secret_key, session_token]): + self._s3 = boto3.resource( + 's3', + aws_access_key_id=access_key_id, + aws_secret_access_key=secret_key, + aws_session_token=session_token, + region_name=region + ) + elif any([access_key_id, secret_key, session_token]): + raise Exception('Insufficient data for authorization') + # anonymous access + if not any([access_key_id, secret_key, session_token]): + self._s3 = boto3.resource('s3', region_name=region) + self._s3.meta.client.meta.events.register('choose-signer.s3.*', disable_signing) + self._client_s3 = self._s3.meta.client + self._bucket = self._s3.Bucket(bucket) + self.region = region + + @property + def bucket(self): + return self._bucket + + @property + def name(self): + return self._bucket.name + + def exists(self): + waiter = self._client_s3.get_waiter('bucket_exists') + try: + waiter.wait( + Bucket=self.name, + WaiterConfig=self.waiter_config + ) + except WaiterError: + raise Exception('A resource {} unavailable'.format(self.name)) + + def is_object_exist(self, key_object): + waiter = self._client_s3.get_waiter('object_exists') + try: + waiter.wait( + Bucket=self._bucket, + Key=key_object, + WaiterConfig=self.waiter_config + ) + except WaiterError: + raise Exception('A file {} unavailable'.format(key_object)) + + def upload_file(self, file_obj, file_name): + self._bucket.upload_fileobj( + Fileobj=file_obj, + Key=file_name, + Config=TransferConfig(max_io_queue=self.transfer_config['max_io_queue']) + ) + + def initialize_content(self): + files = self._bucket.objects.all() + self._files = [{ + 'name': item.key, + } for item in files] + + def download_fileobj(self, key): + buf = BytesIO() + self.bucket.download_fileobj( + Key=key, + Fileobj=buf, + Config=TransferConfig(max_io_queue=self.transfer_config['max_io_queue']) + ) + buf.seek(0) + return buf + + def create(self): + try: + responce = self._bucket.create( + ACL='private', + CreateBucketConfiguration={ + 'LocationConstraint': self.region, + }, + ObjectLockEnabledForBucket=False + ) + slogger.glob.info( + 'Bucket {} has been created on {} region'.format( + self.name, + responce['Location'] + )) + except Exception as ex: + msg = str(ex) + slogger.glob.info(msg) + raise Exception(msg) + +class AzureBlobContainer(_CloudStorage): + MAX_CONCURRENCY = 3 + def __init__(self, container, account_name, sas_token=None): + super().__init__() + self._account_name = account_name + if sas_token: + self._blob_service_client = BlobServiceClient(account_url=self.account_url, credential=sas_token) + else: + self._blob_service_client = BlobServiceClient(account_url=self.account_url) + self._container_client = self._blob_service_client.get_container_client(container) + + @property + def container(self): + return self._container_client + + @property + def name(self): + return self._container_client.container_name + + @property + def account_url(self): + return "{}.blob.core.windows.net".format(self._account_name) + + def create(self): + try: + self._container_client.create_container( + metadata={ + 'type' : 'created by CVAT', + }, + public_access=PublicAccess.OFF + ) + except ResourceExistsError: + msg = f"{self._container_client.container_name} already exists" + slogger.glob.info(msg) + raise Exception(msg) + + def exists(self): + return self._container_client.exists(timeout=5) + + def is_object_exist(self, file_name): + blob_client = self._container_client.get_blob_client(file_name) + return blob_client.exists() + + def upload_file(self, file_obj, file_name): + self._container_client.upload_blob(name=file_name, data=file_obj) + + + # TODO: + # def multipart_upload(self, file_obj): + # pass + + def initialize_content(self): + files = self._container_client.list_blobs() + self._files = [{ + 'name': item.name + } for item in files] + + def download_fileobj(self, key): + buf = BytesIO() + storage_stream_downloader = self._container_client.download_blob( + blob=key, + offset=None, + length=None, + ) + storage_stream_downloader.download_to_stream(buf, max_concurrency=self.MAX_CONCURRENCY) + buf.seek(0) + return buf + +class GOOGLE_DRIVE(_CloudStorage): + pass + +class Credentials: + __slots__ = ('key', 'secret_key', 'session_token', 'account_name', 'credentials_type') + + def __init__(self, **credentials): + self.key = credentials.get('key', '') + self.secret_key = credentials.get('secret_key', '') + self.session_token = credentials.get('session_token', '') + self.account_name = credentials.get('account_name', '') + self.credentials_type = credentials.get('credentials_type', None) + + def convert_to_db(self): + converted_credentials = { + CredentialsTypeChoice.TEMP_KEY_SECRET_KEY_TOKEN_SET : \ + " ".join([self.key, self.secret_key, self.session_token]), + CredentialsTypeChoice.ACCOUNT_NAME_TOKEN_PAIR : " ".join([self.account_name, self.session_token]), + CredentialsTypeChoice.ANONYMOUS_ACCESS: "", + } + return converted_credentials[self.credentials_type] + + def convert_from_db(self, credentials): + self.credentials_type = credentials.get('type') + if self.credentials_type == CredentialsTypeChoice.TEMP_KEY_SECRET_KEY_TOKEN_SET: + self.key, self.secret_key, self.session_token = credentials.get('value').split() + elif self.credentials_type == CredentialsTypeChoice.ACCOUNT_NAME_TOKEN_PAIR: + self.account_name, self.session_token = credentials.get('value').split() + else: + self.account_name, self.session_token, self.key, self.secret_key = ('', '', '', '') + self.credentials_type = None + + def mapping_with_new_values(self, credentials): + self.credentials_type = credentials.get('credentials_type', self.credentials_type) + self.key = credentials.get('key', self.key) + self.secret_key = credentials.get('secret_key', self.secret_key) + self.session_token = credentials.get('session_token', self.session_token) + self.account_name = credentials.get('account_name', self.account_name) + + def values(self): + return [self.key, self.secret_key, self.session_token, self.account_name] diff --git a/cvat/apps/engine/log.py b/cvat/apps/engine/log.py index 98d5c8e2..dfa7dc99 100644 --- a/cvat/apps/engine/log.py +++ b/cvat/apps/engine/log.py @@ -5,7 +5,7 @@ import logging import sys from cvat.settings.base import LOGGING -from .models import Job, Task, Project +from .models import Job, Task, Project, CloudStorage def _get_project(pid): try: @@ -25,6 +25,12 @@ def _get_job(jid): except Exception: raise Exception('{} key must be a job identifier'.format(jid)) +def _get_storage(storage_id): + try: + return CloudStorage.objects.get(pk=storage_id) + except Exception: + raise Exception('{} key must be a cloud storage identifier'.format(storage_id)) + def get_logger(logger_name, log_file): logger = logging.getLogger(name=logger_name) logger.setLevel(logging.INFO) @@ -91,6 +97,27 @@ class JobLoggerStorage: job = _get_job(jid) return slogger.task[job.segment.task.id] +class CloudSourceLoggerStorage: + def __init__(self): + self._storage = dict() + + def __getitem__(self, sid): + """Get ceratain storage object for some cloud storage.""" + if sid not in self._storage: + self._storage[sid] = self._create_cloud_storage_logger(sid) + return self._storage[sid] + + def _create_cloud_storage_logger(self, sid): + cloud_storage = _get_storage(sid) + + logger = logging.getLogger('cvat.server.cloud_storage_{}'.format(sid)) + server_file = logging.FileHandler(filename=cloud_storage.get_log_path()) + formatter = logging.Formatter(LOGGING['formatters']['standard']['format']) + server_file.setFormatter(formatter) + logger.addHandler(server_file) + + return logger + class ProjectClientLoggerStorage: def __init__(self): self._storage = dict() @@ -156,5 +183,6 @@ slogger = dotdict({ 'project': ProjectLoggerStorage(), 'task': TaskLoggerStorage(), 'job': JobLoggerStorage(), + 'cloud_storage': CloudSourceLoggerStorage(), 'glob': logging.getLogger('cvat.server'), }) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index dda3956e..109fd7ba 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -9,7 +9,6 @@ import zipfile import io import itertools import struct -import re from abc import ABC, abstractmethod from contextlib import closing @@ -49,11 +48,12 @@ def files_to_ignore(directory): return False class IMediaReader(ABC): - def __init__(self, source_path, step, start, stop): + def __init__(self, source_path, step, start, stop, dimension): self._source_path = sorted(source_path) self._step = step self._start = start self._stop = stop + self._dimension = dimension @abstractmethod def __iter__(self): @@ -90,7 +90,7 @@ class IMediaReader(ABC): return range(self._start, self._stop, self._step) class ImageListReader(IMediaReader): - def __init__(self, source_path, step=1, start=0, stop=None): + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): if not source_path: raise Exception('No image found') @@ -106,12 +106,24 @@ class ImageListReader(IMediaReader): step=step, start=start, stop=stop, + dimension=dimension ) def __iter__(self): for i in range(self._start, self._stop, self._step): yield (self.get_image(i), self.get_path(i), i) + def filter(self, callback): + source_path = list(filter(callback, self._source_path)) + ImageListReader.__init__( + self, + source_path, + step=self._step, + start=self._start, + stop=self._stop, + dimension=self._dimension + ) + def get_path(self, i): return self._source_path[i] @@ -122,19 +134,36 @@ class ImageListReader(IMediaReader): return (pos - self._start + 1) / (self._stop - self._start) def get_preview(self): - fp = open(self._source_path[0], "rb") + if self._dimension == DimensionType.DIM_3D: + fp = open(os.path.join(os.path.dirname(__file__), 'assets/3d_preview.jpeg'), "rb") + else: + fp = open(self._source_path[0], "rb") return self._get_preview(fp) def get_image_size(self, i): + if self._dimension == DimensionType.DIM_3D: + with open(self.get_path(i), 'rb') as f: + properties = ValidateDimension.get_pcd_properties(f) + return int(properties["WIDTH"]), int(properties["HEIGHT"]) img = Image.open(self._source_path[i]) return img.width, img.height + def reconcile(self, source_files, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + # FIXME + ImageListReader.__init__(self, + source_path=source_files, + step=step, + start=start, + stop=stop + ) + self._dimension = dimension + @property def absolute_source_paths(self): return [self.get_path(idx) for idx, _ in enumerate(self._source_path)] class DirectoryReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None): + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): image_paths = [] for source in source_path: for root, _, files in os.walk(source): @@ -146,10 +175,11 @@ class DirectoryReader(ImageListReader): step=step, start=start, stop=stop, + dimension=dimension, ) class ArchiveReader(DirectoryReader): - def __init__(self, source_path, step=1, start=0, stop=None): + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): self._archive_source = source_path[0] extract_dir = source_path[1] if len(source_path) > 1 else os.path.dirname(source_path[0]) Archive(self._archive_source).extractall(extract_dir) @@ -160,10 +190,11 @@ class ArchiveReader(DirectoryReader): step=step, start=start, stop=stop, + dimension=dimension ) class PdfReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None): + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): if not source_path: raise Exception('No PDF found') @@ -191,21 +222,22 @@ class PdfReader(ImageListReader): step=step, start=start, stop=stop, + dimension=dimension, ) class ZipReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None): - self._dimension = DimensionType.DIM_2D - self._zip_source = zipfile.ZipFile(source_path[0], mode='a') + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + self._zip_source = zipfile.ZipFile(source_path[0], mode='r') self.extract_dir = source_path[1] if len(source_path) > 1 else None file_list = [f for f in self._zip_source.namelist() if files_to_ignore(f) and get_mime(f) == 'image'] - super().__init__(file_list, step, start, stop) + super().__init__(file_list, step=step, start=start, stop=stop, dimension=dimension) def __del__(self): self._zip_source.close() def get_preview(self): if self._dimension == DimensionType.DIM_3D: + # TODO fp = open(os.path.join(os.path.dirname(__file__), 'assets/3d_preview.jpeg'), "rb") return self._get_preview(fp) io_image = io.BytesIO(self._zip_source.read(self._source_path[0])) @@ -213,32 +245,20 @@ class ZipReader(ImageListReader): def get_image_size(self, i): if self._dimension == DimensionType.DIM_3D: - with self._zip_source.open(self._source_path[i], "r") as file: - properties = ValidateDimension.get_pcd_properties(file) + with open(self.get_path(i), 'rb') as f: + properties = ValidateDimension.get_pcd_properties(f) return int(properties["WIDTH"]), int(properties["HEIGHT"]) img = Image.open(io.BytesIO(self._zip_source.read(self._source_path[i]))) return img.width, img.height def get_image(self, i): + if self._dimension == DimensionType.DIM_3D: + return self.get_path(i) return io.BytesIO(self._zip_source.read(self._source_path[i])) - def add_files(self, source_path): - root_path = os.path.split(self._zip_source.filename)[0] - for path in source_path: - self._zip_source.write(path, path.replace(root_path, "")) - def get_zip_filename(self): return self._zip_source.filename - def reconcile(self, source_files, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): - self._dimension = dimension - super().__init__( - source_path=source_files, - step=step, - start=start, - stop=stop - ) - def get_path(self, i): if self._zip_source.filename: return os.path.join(os.path.dirname(self._zip_source.filename), self._source_path[i]) \ @@ -246,18 +266,28 @@ class ZipReader(ImageListReader): else: # necessary for mime_type definition return self._source_path[i] + def reconcile(self, source_files, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + super().reconcile( + source_files=source_files, + step=step, + start=start, + stop=stop, + dimension=dimension, + ) + def extract(self): self._zip_source.extractall(self.extract_dir if self.extract_dir else os.path.dirname(self._zip_source.filename)) if not self.extract_dir: os.remove(self._zip_source.filename) class VideoReader(IMediaReader): - def __init__(self, source_path, step=1, start=0, stop=None): + def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): super().__init__( source_path=source_path, step=step, start=start, stop=stop + 1 if stop is not None else stop, + dimension=dimension, ) def _has_frame(self, i): @@ -295,16 +325,30 @@ class VideoReader(IMediaReader): return self._decode(container) def get_progress(self, pos): - container = self._get_av_container() - # Not for all containers return real value - stream = container.streams.video[0] - return pos / stream.duration if stream.duration else None + duration = self._get_duration() + return pos / duration if duration else None def _get_av_container(self): if isinstance(self._source_path[0], io.BytesIO): self._source_path[0].seek(0) # required for re-reading return av.open(self._source_path[0]) + def _get_duration(self): + container = self._get_av_container() + stream = container.streams.video[0] + duration = None + if stream.duration: + duration = stream.duration + else: + # may have a DURATION in format like "01:16:45.935000000" + duration_str = stream.metadata.get("DURATION", None) + tb_denominator = stream.time_base.denominator + if duration_str and tb_denominator: + _hour, _min, _sec = duration_str.split(':') + duration_sec = 60*60*float(_hour) + 60*float(_min) + float(_sec) + duration = duration_sec * tb_denominator + return duration + def get_preview(self): container = self._get_av_container() stream = container.streams.video[0] @@ -740,15 +784,15 @@ class ValidateDimension: pcd_files = {} for file in files: - file_name, file_extension = file.rsplit('.', maxsplit=1) + file_name, file_extension = os.path.splitext(file) file_path = os.path.abspath(os.path.join(root, file)) - if file_extension == "bin": + if file_extension == ".bin": path = self.bin_operation(file_path, actual_path) pcd_files[file_name] = path self.related_files[path] = [] - elif file_extension == "pcd": + elif file_extension == ".pcd": path = ValidateDimension.pcd_operation(file_path, actual_path) if path == file_path: self.image_files[file_name] = file_path @@ -756,69 +800,10 @@ class ValidateDimension: pcd_files[file_name] = path self.related_files[path] = [] else: - self.image_files[file_name] = file_path + if _is_image(file_path): + self.image_files[file_name] = file_path return pcd_files - def validate_velodyne_points(self, *args): - root, actual_path, files = args - velodyne_files = self.process_files(root, actual_path, files) - related_path = os.path.split(os.path.split(root)[0])[0] - - path_list = [re.search(r'image_\d.*', path, re.IGNORECASE) for path in os.listdir(related_path) if - os.path.isdir(os.path.join(related_path, path))] - - for path_ in path_list: - if path_: - path = os.path.join(path_.group(), "data") - path = os.path.abspath(os.path.join(related_path, path)) - - files = [file for file in os.listdir(path) if - os.path.isfile(os.path.abspath(os.path.join(path, file)))] - for file in files: - - f_name = file.split(".")[0] - if velodyne_files.get(f_name, None): - self.related_files[velodyne_files[f_name]].append( - os.path.abspath(os.path.join(path, file))) - - def validate_pointcloud(self, *args): - root, actual_path, files = args - pointcloud_files = self.process_files(root, actual_path, files) - related_path = root.rsplit("/pointcloud", 1)[0] - related_images_path = os.path.join(related_path, "related_images") - - if os.path.isdir(related_images_path): - paths = [path for path in os.listdir(related_images_path) if - os.path.isdir(os.path.abspath(os.path.join(related_images_path, path)))] - - for k in pointcloud_files: - for path in paths: - - if k == path.rsplit("_", 1)[0]: - file_path = os.path.abspath(os.path.join(related_images_path, path)) - files = [file for file in os.listdir(file_path) if - os.path.isfile(os.path.join(file_path, file))] - for related_image in files: - self.related_files[pointcloud_files[k]].append(os.path.join(file_path, related_image)) - - def validate_default(self, *args): - root, actual_path, files = args - pcd_files = self.process_files(root, actual_path, files) - if len(list(pcd_files.keys())): - - for image in self.image_files.keys(): - if pcd_files.get(image, None): - self.related_files[pcd_files[image]].append(self.image_files[image]) - - current_directory_name = os.path.split(root) - - if len(pcd_files.keys()) == 1: - pcd_name = list(pcd_files.keys())[0].rsplit(".", 1)[0] - if current_directory_name[1] == pcd_name: - for related_image in self.image_files.values(): - if root == os.path.split(related_image)[0]: - self.related_files[pcd_files[pcd_name]].append(related_image) - def validate(self): """ Validate the directory structure for kitty and point cloud format. @@ -830,15 +815,7 @@ class ValidateDimension: if not files_to_ignore(root): continue - if root.endswith("data"): - if os.path.split(os.path.split(root)[0])[1] == "velodyne_points": - self.validate_velodyne_points(root, actual_path, files) - - elif os.path.split(root)[-1] == "pointcloud": - self.validate_pointcloud(root, actual_path, files) - - else: - self.validate_default(root, actual_path, files) + self.process_files(root, actual_path, files) if len(self.related_files.keys()): self.dimension = DimensionType.DIM_3D diff --git a/cvat/apps/engine/migrations/0040_cloud_storage.py b/cvat/apps/engine/migrations/0040_cloud_storage.py new file mode 100644 index 00000000..c73609fd --- /dev/null +++ b/cvat/apps/engine/migrations/0040_cloud_storage.py @@ -0,0 +1,47 @@ +# Generated by Django 3.1.8 on 2021-05-07 06:42 + +import cvat.apps.engine.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('engine', '0039_auto_training'), + ] + + operations = [ + migrations.AlterField( + model_name='data', + name='storage', + field=models.CharField(choices=[('cloud_storage', 'CLOUD_STORAGE'), ('local', 'LOCAL'), ('share', 'SHARE')], default=cvat.apps.engine.models.StorageChoice['LOCAL'], max_length=15), + ), + migrations.CreateModel( + name='CloudStorage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('provider_type', models.CharField(choices=[('AWS_S3_BUCKET', 'AWS_S3'), ('AZURE_CONTAINER', 'AZURE_CONTAINER'), ('GOOGLE_DRIVE', 'GOOGLE_DRIVE')], max_length=20)), + ('resource', models.CharField(max_length=63)), + ('display_name', models.CharField(max_length=63)), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('credentials', models.CharField(max_length=500)), + ('credentials_type', models.CharField(choices=[('TEMP_KEY_SECRET_KEY_TOKEN_SET', 'TEMP_KEY_SECRET_KEY_TOKEN_SET'), ('ACCOUNT_NAME_TOKEN_PAIR', 'ACCOUNT_NAME_TOKEN_PAIR'), ('ANONYMOUS_ACCESS', 'ANONYMOUS_ACCESS')], max_length=29)), + ('specific_attributes', models.CharField(blank=True, max_length=50)), + ('description', models.TextField(blank=True)), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cloud_storages', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'default_permissions': (), + 'unique_together': {('provider_type', 'resource', 'credentials')}, + }, + ), + migrations.AddField( + model_name='data', + name='cloud_storage', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='data', to='engine.cloudstorage'), + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index bcc46738..78ec751a 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -68,7 +68,7 @@ class StorageMethodChoice(str, Enum): return self.value class StorageChoice(str, Enum): - #AWS_S3 = 'aws_s3_bucket' + CLOUD_STORAGE = 'cloud_storage' LOCAL = 'local' SHARE = 'share' @@ -92,6 +92,7 @@ class Data(models.Model): default=DataChoice.IMAGESET) storage_method = models.CharField(max_length=15, choices=StorageMethodChoice.choices(), default=StorageMethodChoice.FILE_SYSTEM) storage = models.CharField(max_length=15, choices=StorageChoice.choices(), default=StorageChoice.LOCAL) + cloud_storage = models.ForeignKey('CloudStorage', on_delete=models.SET_NULL, null=True, related_name='data') class Meta: default_permissions = () @@ -274,7 +275,8 @@ class MyFileSystemStorage(FileSystemStorage): return name def upload_path_handler(instance, filename): - return os.path.join(instance.data.get_upload_dirname(), filename) + # relative path is required since Django 3.1.11 + return os.path.join(os.path.relpath(instance.data.get_upload_dirname(), settings.BASE_DIR), filename) # For client files which the user is uploaded class ClientFile(models.Model): @@ -535,3 +537,80 @@ class Comment(models.Model): message = models.TextField(default='') created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now=True) + +class CloudProviderChoice(str, Enum): + AWS_S3 = 'AWS_S3_BUCKET' + AZURE_CONTAINER = 'AZURE_CONTAINER' + GOOGLE_DRIVE = 'GOOGLE_DRIVE' + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + + @classmethod + def list(cls): + return list(map(lambda x: x.value, cls)) + + def __str__(self): + return self.value + +class CredentialsTypeChoice(str, Enum): + # ignore bandit issues because false positives + TEMP_KEY_SECRET_KEY_TOKEN_SET = 'TEMP_KEY_SECRET_KEY_TOKEN_SET' # nosec + ACCOUNT_NAME_TOKEN_PAIR = 'ACCOUNT_NAME_TOKEN_PAIR' # nosec + ANONYMOUS_ACCESS = 'ANONYMOUS_ACCESS' + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + + @classmethod + def list(cls): + return list(map(lambda x: x.value, cls)) + + def __str__(self): + return self.value + +class CloudStorage(models.Model): + # restrictions: + # AWS bucket name, Azure container name - 63 + # AWS access key id - 20 + # AWS secret access key - 40 + # AWS temporary session tocken - None + # The size of the security token that AWS STS API operations return is not fixed. + # We strongly recommend that you make no assumptions about the maximum size. + # The typical token size is less than 4096 bytes, but that can vary. + provider_type = models.CharField(max_length=20, choices=CloudProviderChoice.choices()) + resource = models.CharField(max_length=63) + display_name = models.CharField(max_length=63) + owner = models.ForeignKey(User, null=True, blank=True, + on_delete=models.SET_NULL, related_name="cloud_storages") + created_date = models.DateTimeField(auto_now_add=True) + updated_date = models.DateTimeField(auto_now=True) + credentials = models.CharField(max_length=500) + credentials_type = models.CharField(max_length=29, choices=CredentialsTypeChoice.choices())#auth_type + specific_attributes = models.CharField(max_length=50, blank=True) + description = models.TextField(blank=True) + + class Meta: + default_permissions = () + unique_together = (('provider_type', 'resource', 'credentials'),) + + def __str__(self): + return "{} {} {}".format(self.provider_type, self.display_name, self.id) + + def get_storage_dirname(self): + return os.path.join(settings.CLOUD_STORAGE_ROOT, str(self.id)) + + def get_storage_logs_dirname(self): + return os.path.join(self.get_storage_dirname(), 'logs') + + def get_log_path(self): + return os.path.join(self.get_storage_dirname(), "storage.log") + + def get_specific_attributes(self): + specific_attributes = self.specific_attributes + return { + item.split('=')[0].strip(): item.split('=')[1].strip() + for item in specific_attributes.split('&') + } if specific_attributes else dict() diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index dfbe4fa6..0c0b1130 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 Intel Corporation +# Copyright (C) 2019-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -9,12 +9,11 @@ import shutil from rest_framework import serializers, exceptions from django.contrib.auth.models import User, Group - from cvat.apps.dataset_manager.formats.utils import get_label_color from cvat.apps.engine import models +from cvat.apps.engine.cloud_provider import get_cloud_storage_instance, Credentials from cvat.apps.engine.log import slogger - class BasicUserSerializer(serializers.ModelSerializer): def validate(self, data): if hasattr(self, 'initial_data'): @@ -273,12 +272,13 @@ class DataSerializer(serializers.ModelSerializer): remote_files = RemoteFileSerializer(many=True, default=[]) use_cache = serializers.BooleanField(default=False) copy_data = serializers.BooleanField(default=False) + cloud_storage_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) class Meta: model = models.Data fields = ('chunk_size', 'size', 'image_quality', 'start_frame', 'stop_frame', 'frame_filter', 'compressed_chunk_type', 'original_chunk_type', 'client_files', 'server_files', 'remote_files', 'use_zip_chunks', - 'use_cache', 'copy_data') + 'cloud_storage_id', 'use_cache', 'copy_data', 'storage_method', 'storage') # pylint: disable=no-self-use def validate_frame_filter(self, value): @@ -405,17 +405,78 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer): instance.bug_tracker) instance.subset = validated_data.get('subset', instance.subset) labels = validated_data.get('label_set', []) - for label in labels: - LabelSerializer.update_instance(label, instance) + if instance.project_id is None: + for label in labels: + LabelSerializer.update_instance(label, instance) + validated_project_id = validated_data.get('project_id', None) + if validated_project_id is not None and validated_project_id != instance.project_id: + project = models.Project.objects.get(id=validated_data.get('project_id', None)) + if instance.project_id is None: + for old_label in instance.label_set.all(): + try: + new_label = project.label_set.filter(name=old_label.name).first() + except ValueError: + raise serializers.ValidationError(f'Target project does not have label with name "{old_label.name}"') + old_label.attributespec_set.all().delete() + for model in (models.LabeledTrack, models.LabeledShape, models.LabeledImage): + model.objects.filter(job__segment__task=instance, label=old_label).update( + label=new_label + ) + instance.label_set.all().delete() + else: + for old_label in instance.project.label_set.all(): + new_label_for_name = list(filter(lambda x: x.get('id', None) == old_label.id, labels)) + if len(new_label_for_name): + old_label.name = new_label_for_name[0].get('name', old_label.name) + try: + new_label = project.label_set.filter(name=old_label.name).first() + except ValueError: + raise serializers.ValidationError(f'Target project does not have label with name "{old_label.name}"') + for (model, attr, attr_name) in ( + (models.LabeledTrack, models.LabeledTrackAttributeVal, 'track'), + (models.LabeledShape, models.LabeledShapeAttributeVal, 'shape'), + (models.LabeledImage, models.LabeledImageAttributeVal, 'image') + ): + attr.objects.filter(**{ + f'{attr_name}__job__segment__task': instance, + f'{attr_name}__label': old_label + }).delete() + model.objects.filter(job__segment__task=instance, label=old_label).update( + label=new_label + ) + instance.project = project instance.save() return instance - def validate_labels(self, value): - label_names = [label['name'] for label in value] - if len(label_names) != len(set(label_names)): - raise serializers.ValidationError('All label names must be unique for the task') - return value + def validate(self, attrs): + # When moving task labels can be mapped to one, but when not names must be unique + if 'project_id' in attrs.keys() and self.instance is not None: + project_id = attrs.get('project_id') + if project_id is not None and not models.Project.objects.filter(id=project_id).count(): + raise serializers.ValidationError(f'Cannot find project with ID {project_id}') + # Check that all labels can be mapped + new_label_names = set() + old_labels = self.instance.project.label_set.all() if self.instance.project_id else self.instance.label_set.all() + for old_label in old_labels: + new_labels = tuple(filter(lambda x: x.get('id') == old_label.id, attrs.get('label_set', []))) + if len(new_labels): + new_label_names.add(new_labels[0].get('name', old_label.name)) + else: + new_label_names.add(old_label.name) + target_project = models.Project.objects.get(id=project_id) + target_project_label_names = set() + for label in target_project.label_set.all(): + target_project_label_names.add(label.name) + if not new_label_names.issubset(target_project_label_names): + raise serializers.ValidationError('All task or project label names must be mapped to the target project') + else: + if 'label_set' in attrs.keys(): + label_names = [label['name'] for label in attrs.get('label_set')] + if len(label_names) != len(set(label_names)): + raise serializers.ValidationError('All label names must be unique for the task') + + return attrs class ProjectSearchSerializer(serializers.ModelSerializer): @@ -451,11 +512,9 @@ class ProjectWithoutTaskSerializer(serializers.ModelSerializer): def to_representation(self, instance): response = super().to_representation(instance) - subsets = set() - for task in instance.tasks.all(): - if task.subset: - subsets.add(task.subset) - response['task_subsets'] = list(subsets) + task_subsets = set(instance.tasks.values_list('subset', flat=True)) + task_subsets.discard('') + response['task_subsets'] = list(task_subsets) return response class ProjectSerializer(ProjectWithoutTaskSerializer): @@ -545,6 +604,7 @@ class FrameMetaSerializer(serializers.Serializer): width = serializers.IntegerField() height = serializers.IntegerField() name = serializers.CharField(max_length=1024) + has_related_context = serializers.BooleanField() class PluginsSerializer(serializers.Serializer): GIT_INTEGRATION = serializers.BooleanField() @@ -647,6 +707,9 @@ class LogEventSerializer(serializers.Serializer): class AnnotationFileSerializer(serializers.Serializer): annotation_file = serializers.FileField() +class TaskFileSerializer(serializers.Serializer): + task_file = serializers.FileField() + class ReviewSerializer(serializers.ModelSerializer): assignee = BasicUserSerializer(allow_null=True, required=False) assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) @@ -707,3 +770,99 @@ class CombinedReviewSerializer(ReviewSerializer): models.Comment.objects.create(**comment) return db_review + +class BaseCloudStorageSerializer(serializers.ModelSerializer): + owner = BasicUserSerializer(required=False) + class Meta: + model = models.CloudStorage + exclude = ['credentials'] + read_only_fields = ('created_date', 'updated_date', 'owner') + +class CloudStorageSerializer(serializers.ModelSerializer): + owner = BasicUserSerializer(required=False) + session_token = serializers.CharField(max_length=440, allow_blank=True, required=False) + key = serializers.CharField(max_length=20, allow_blank=True, required=False) + secret_key = serializers.CharField(max_length=40, allow_blank=True, required=False) + account_name = serializers.CharField(max_length=24, allow_blank=True, required=False) + + class Meta: + model = models.CloudStorage + fields = ( + 'provider_type', 'resource', 'display_name', 'owner', 'credentials_type', + 'created_date', 'updated_date', 'session_token', 'account_name', 'key', + 'secret_key', 'specific_attributes', 'description' + ) + read_only_fields = ('created_date', 'updated_date', 'owner') + + # pylint: disable=no-self-use + def validate_specific_attributes(self, value): + if value: + attributes = value.split('&') + for attribute in attributes: + if not len(attribute.split('=')) == 2: + raise serializers.ValidationError('Invalid specific attributes') + return value + + def validate(self, attrs): + if attrs.get('provider_type') == models.CloudProviderChoice.AZURE_CONTAINER: + if not attrs.get('account_name', ''): + raise serializers.ValidationError('Account name for Azure container was not specified') + return attrs + + def create(self, validated_data): + provider_type = validated_data.get('provider_type') + should_be_created = validated_data.pop('should_be_created', None) + credentials = Credentials( + account_name=validated_data.pop('account_name', ''), + key=validated_data.pop('key', ''), + secret_key=validated_data.pop('secret_key', ''), + session_token=validated_data.pop('session_token', ''), + credentials_type = validated_data.get('credentials_type') + ) + if should_be_created: + details = { + 'resource': validated_data.get('resource'), + 'credentials': credentials, + 'specific_attributes': { + item.split('=')[0].strip(): item.split('=')[1].strip() + for item in validated_data.get('specific_attributes').split('&') + } if len(validated_data.get('specific_attributes', '')) + else dict() + } + storage = get_cloud_storage_instance(cloud_provider=provider_type, **details) + try: + storage.create() + except Exception as ex: + slogger.glob.warning("Failed with creating storage\n{}".format(str(ex))) + raise + + db_storage = models.CloudStorage.objects.create( + credentials=credentials.convert_to_db(), + **validated_data + ) + db_storage.save() + return db_storage + + # pylint: disable=no-self-use + def update(self, instance, validated_data): + credentials = Credentials() + credentials.convert_from_db({ + 'type': instance.credentials_type, + 'value': instance.credentials, + }) + tmp = {k:v for k,v in validated_data.items() if k in {'key', 'secret_key', 'account_name', 'session_token', 'credentials_type'}} + credentials.mapping_with_new_values(tmp) + instance.credentials = credentials.convert_to_db() + instance.credentials_type = validated_data.get('credentials_type', instance.credentials_type) + instance.resource = validated_data.get('resource', instance.resource) + instance.display_name = validated_data.get('display_name', instance.display_name) + + instance.save() + return instance + +class RelatedFileSerializer(serializers.ModelSerializer): + + class Meta: + model = models.RelatedFile + fields = '__all__' + read_only_fields = ('path',) diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index e24865c7..cc6a5ffa 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -7,26 +7,27 @@ import itertools import os import sys import rq +import re import shutil +from distutils.dir_util import copy_tree from traceback import print_exception 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 -from cvat.apps.engine.utils import av_scan_paths -from cvat.apps.engine.models import DimensionType -from utils.dataset_manifest import ImageManifestManager, VideoManifestManager -from utils.dataset_manifest.core import VideoManifestValidator - import django_rq + from django.conf import settings from django.db import transaction -from distutils.dir_util import copy_tree -from . import models -from .log import slogger +from cvat.apps.engine import models +from cvat.apps.engine.log import slogger +from cvat.apps.engine.media_extractors import (MEDIA_TYPES, Mpeg4ChunkWriter, Mpeg4CompressedChunkWriter, + ValidateDimension, ZipChunkWriter, ZipCompressedChunkWriter, get_mime) +from cvat.apps.engine.utils import av_scan_paths +from utils.dataset_manifest import ImageManifestManager, VideoManifestManager +from utils.dataset_manifest.core import VideoManifestValidator +from utils.dataset_manifest.utils import detect_related_images +from .cloud_provider import get_cloud_storage_instance, Credentials ############################# Low Level server API @@ -38,13 +39,14 @@ def create(tid, data): @transaction.atomic def rq_handler(job, exc_type, exc_value, traceback): - splitted = job.id.split('/') - tid = int(splitted[splitted.index('tasks') + 1]) + split = job.id.split('/') + tid = split[split.index('tasks') + 1] try: + tid = int(tid) db_task = models.Task.objects.select_for_update().get(pk=tid) with open(db_task.get_log_path(), "wt") as log_file: print_exception(exc_type, exc_value, traceback, file=log_file) - except models.Task.DoesNotExist: + except (models.Task.DoesNotExist, ValueError): pass # skip exceptions in the code return False @@ -74,8 +76,9 @@ def _save_task_to_db(db_task): segment_size = db_task.segment_size segment_step = segment_size - if segment_size == 0: + if segment_size == 0 or segment_size > db_task.data.size: segment_size = db_task.data.size + db_task.segment_size = segment_size # Segment step must be more than segment_size + overlap in single-segment tasks # Otherwise a task contains an extra segment @@ -207,32 +210,51 @@ def _download_data(urls, upload_dir): return list(local_files.keys()) +def _get_manifest_frame_indexer(start_frame=0, frame_step=1): + return lambda frame_id: start_frame + frame_id * frame_step + @transaction.atomic -def _create_thread(tid, data): +def _create_thread(tid, data, isImport=False): slogger.glob.info("create task #{}".format(tid)) db_task = models.Task.objects.select_for_update().get(pk=tid) db_data = db_task.data - if db_task.data.size != 0: - raise NotImplementedError("Adding more data is not implemented") - upload_dir = db_data.get_upload_dirname() if data['remote_files']: - data['remote_files'] = _download_data(data['remote_files'], upload_dir) + if db_data.storage != models.StorageChoice.CLOUD_STORAGE: + data['remote_files'] = _download_data(data['remote_files'], upload_dir) manifest_file = [] media = _count_files(data, manifest_file) media, task_mode = _validate_data(media, manifest_file) if manifest_file: - assert settings.USE_CACHE and db_data.storage_method == StorageMethodChoice.CACHE, \ + assert settings.USE_CACHE and db_data.storage_method == models.StorageMethodChoice.CACHE, \ "File with meta information can be uploaded if 'Use cache' option is also selected" if data['server_files']: - if db_data.storage == StorageChoice.LOCAL: + if db_data.storage == models.StorageChoice.LOCAL: _copy_data_from_share(data['server_files'], upload_dir) - else: + elif db_data.storage == models.StorageChoice.SHARE: upload_dir = settings.SHARE_ROOT + else: # cloud storage + if not manifest_file: raise Exception('A manifest file not found') + db_cloud_storage = db_data.cloud_storage + credentials = Credentials() + credentials.convert_from_db({ + 'type': db_cloud_storage.credentials_type, + 'value': db_cloud_storage.credentials, + }) + + details = { + 'resource': db_cloud_storage.resource, + 'credentials': credentials, + 'specific_attributes': db_cloud_storage.get_specific_attributes() + } + cloud_storage_instance = get_cloud_storage_instance(cloud_provider=db_cloud_storage.provider_type, **details) + cloud_storage_instance.download_file(manifest_file[0], db_data.get_manifest_path()) + first_sorted_media_image = sorted(media['image'])[0] + cloud_storage_instance.download_file(first_sorted_media_image, os.path.join(upload_dir, first_sorted_media_image)) av_scan_paths(upload_dir) @@ -242,16 +264,35 @@ def _create_thread(tid, data): db_images = [] extractor = None + manifest_index = _get_manifest_frame_indexer() + + # If upload from server_files image and directories + # need to update images list by all found images in directories + if (data['server_files']) and len(media['directory']) and len(media['image']): + media['image'].extend( + [os.path.relpath(image, upload_dir) for image in + MEDIA_TYPES['directory']['extractor']( + source_path=[os.path.join(upload_dir, f) for f in media['directory']], + ).absolute_source_paths + ] + ) + media['directory'] = [] for media_type, media_files in media.items(): if media_files: if extractor is not None: raise Exception('Combined data types are not supported') source_paths=[os.path.join(upload_dir, f) for f in media_files] - if media_type in {'archive', 'zip'} and db_data.storage == StorageChoice.SHARE: + if media_type in {'archive', 'zip'} and db_data.storage == models.StorageChoice.SHARE: source_paths.append(db_data.get_upload_dirname()) upload_dir = db_data.get_upload_dirname() - db_data.storage = StorageChoice.LOCAL + db_data.storage = models.StorageChoice.LOCAL + if isImport and media_type == 'image' and db_data.storage == models.StorageChoice.SHARE: + manifest_index = _get_manifest_frame_indexer(db_data.start_frame, db_data.get_frame_step()) + db_data.start_frame = 0 + data['stop_frame'] = None + db_data.frame_filter = '' + extractor = MEDIA_TYPES[media_type]['extractor']( source_path=source_paths, step=db_data.get_frame_step(), @@ -259,23 +300,32 @@ def _create_thread(tid, data): stop=data['stop_frame'], ) + validate_dimension = ValidateDimension() - if extractor.__class__ == MEDIA_TYPES['zip']['extractor']: + if isinstance(extractor, MEDIA_TYPES['zip']['extractor']): extractor.extract() - validate_dimension.set_path(os.path.split(extractor.get_zip_filename())[0]) + + if db_data.storage == models.StorageChoice.LOCAL or \ + (db_data.storage == models.StorageChoice.SHARE and \ + isinstance(extractor, MEDIA_TYPES['zip']['extractor'])): + validate_dimension.set_path(upload_dir) validate_dimension.validate() - if validate_dimension.dimension == DimensionType.DIM_3D: - db_task.dimension = DimensionType.DIM_3D - extractor.reconcile( - source_files=list(validate_dimension.related_files.keys()), - step=db_data.get_frame_step(), - start=db_data.start_frame, - stop=data['stop_frame'], - dimension=DimensionType.DIM_3D, + if validate_dimension.dimension == models.DimensionType.DIM_3D: + db_task.dimension = models.DimensionType.DIM_3D - ) - extractor.add_files(validate_dimension.converted_files) + extractor.reconcile( + source_files=[os.path.join(upload_dir, f) for f in validate_dimension.related_files.keys()], + step=db_data.get_frame_step(), + start=db_data.start_frame, + stop=data['stop_frame'], + dimension=models.DimensionType.DIM_3D, + ) + + related_images = {} + if isinstance(extractor, MEDIA_TYPES['image']['extractor']): + extractor.filter(lambda x: not re.search(r'(^|{0})related_images{0}'.format(os.sep), x)) + related_images = detect_related_images(extractor.absolute_source_paths, upload_dir) db_task.mode = task_mode db_data.compressed_chunk_type = models.DataChoice.VIDEO if task_mode == 'interpolation' and not data['use_zip_chunks'] else models.DataChoice.IMAGESET @@ -295,8 +345,8 @@ def _create_thread(tid, data): job.save_meta() 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 - if db_data.original_chunk_type == DataChoice.VIDEO: + compressed_chunk_writer_class = Mpeg4CompressedChunkWriter if db_data.compressed_chunk_type == models.DataChoice.VIDEO else ZipCompressedChunkWriter + if db_data.original_chunk_type == models.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. @@ -306,7 +356,7 @@ def _create_thread(tid, data): original_quality = 100 kwargs = {} - if validate_dimension.dimension == DimensionType.DIM_3D: + if validate_dimension.dimension == models.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(original_quality) @@ -314,13 +364,18 @@ def _create_thread(tid, data): # calculate chunk size if it isn't specified if db_data.chunk_size is None: if isinstance(compressed_chunk_writer, ZipCompressedChunkWriter): - w, h = extractor.get_image_size(0) + if not (db_data.storage == models.StorageChoice.CLOUD_STORAGE): + w, h = extractor.get_image_size(0) + else: + manifest = ImageManifestManager(db_data.get_manifest_path()) + manifest.init_index() + img_properties = manifest[0] + w, h = img_properties['width'], img_properties['height'] area = h * w db_data.chunk_size = max(2, min(72, 36 * 1920 * 1080 // area)) else: db_data.chunk_size = 36 - video_path = "" video_size = (0, 0) @@ -328,7 +383,7 @@ def _create_thread(tid, data): job.meta['status'] = msg job.save_meta() - if settings.USE_CACHE and db_data.storage_method == StorageMethodChoice.CACHE: + if settings.USE_CACHE and db_data.storage_method == models.StorageMethodChoice.CACHE: for media_type, media_files in media.items(): if not media_files: @@ -353,8 +408,8 @@ def _create_thread(tid, data): manifest.validate_frame_numbers() assert len(manifest) > 0, 'No key frames.' - all_frames = manifest['properties']['length'] - video_size = manifest['properties']['resolution'] + all_frames = manifest.video_length + video_size = manifest.video_resolution manifest_is_prepared = True except Exception as ex: if os.path.exists(db_data.get_index_path()): @@ -386,7 +441,7 @@ def _create_thread(tid, data): if data['stop_frame'] else all_frames, all_frames), db_data.get_frame_step())) video_path = os.path.join(upload_dir, media_files[0]) except Exception as ex: - db_data.storage_method = StorageMethodChoice.FILE_SYSTEM + db_data.storage_method = models.StorageMethodChoice.FILE_SYSTEM if os.path.exists(db_data.get_manifest_path()): os.remove(db_data.get_manifest_path()) if os.path.exists(db_data.get_index_path()): @@ -394,13 +449,14 @@ def _create_thread(tid, data): base_msg = str(ex) if isinstance(ex, AssertionError) \ else "Uploaded video does not support a quick way of task creating." _update_status("{} The task will be created using the old method".format(base_msg)) - else:# images, archive, pdf + else: # images, archive, pdf db_data.size = len(extractor) manifest = ImageManifestManager(db_data.get_manifest_path()) if not manifest_file: - if db_task.dimension == DimensionType.DIM_2D: + if db_task.dimension == models.DimensionType.DIM_2D: meta_info = manifest.prepare_meta( sources=extractor.absolute_source_paths, + meta={ k: {'related_images': related_images[k] } for k in related_images }, data_dir=upload_dir ) content = meta_info.content @@ -410,6 +466,7 @@ def _create_thread(tid, data): name, ext = os.path.splitext(os.path.relpath(source, upload_dir)) content.append({ 'name': name, + 'meta': { 'related_images': related_images[''.join((name, ext))] }, 'extension': ext }) manifest.create(content) @@ -420,8 +477,8 @@ def _create_thread(tid, data): img_sizes = [] for _, frame_id in chunk_paths: - properties = manifest[frame_id] - if db_task.dimension == DimensionType.DIM_2D: + properties = manifest[manifest_index(frame_id)] + if db_task.dimension == models.DimensionType.DIM_2D: resolution = (properties['width'], properties['height']) else: resolution = extractor.get_image_size(frame_id) @@ -434,7 +491,7 @@ def _create_thread(tid, data): for (path, frame), (w, h) in zip(chunk_paths, img_sizes) ]) - if db_data.storage_method == StorageMethodChoice.FILE_SYSTEM or not settings.USE_CACHE: + if db_data.storage_method == models.StorageMethodChoice.FILE_SYSTEM or not settings.USE_CACHE: counter = itertools.count() generator = itertools.groupby(extractor, lambda x: next(counter) // db_data.chunk_size) for chunk_idx, chunk_data in generator: @@ -465,27 +522,15 @@ def _create_thread(tid, data): update_progress(progress) if db_task.mode == 'annotation': - if validate_dimension.dimension == DimensionType.DIM_2D: - models.Image.objects.bulk_create(db_images) - else: - related_file = [] - for image_data in db_images: - image_model = models.Image( - data=image_data.data, - path=image_data.path, - frame=image_data.frame, - width=image_data.width, - height=image_data.height - ) - - image_model.save() - image_data = models.Image.objects.get(id=image_model.id) - - if validate_dimension.related_files.get(image_data.path, None): - for related_image_file in validate_dimension.related_files[image_data.path]: - related_file.append( - RelatedFile(data=db_data, primary_image_id=image_data.id, path=related_image_file)) - RelatedFile.objects.bulk_create(related_file) + models.Image.objects.bulk_create(db_images) + created_images = models.Image.objects.filter(data_id=db_data.id) + + db_related_files = [ + models.RelatedFile(data=image.data, primary_image=image, path=os.path.join(upload_dir, related_file_path)) + for image in created_images + for related_file_path in related_images.get(image.path, []) + ] + models.RelatedFile.objects.bulk_create(db_related_files) db_images = [] else: models.Video.objects.create( diff --git a/cvat/apps/engine/tests/assets/test_canvas3d.zip b/cvat/apps/engine/tests/assets/test_canvas3d.zip new file mode 100644 index 00000000..cc7d7842 Binary files /dev/null and b/cvat/apps/engine/tests/assets/test_canvas3d.zip differ diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index c55e70d3..340b74e8 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 Intel Corporation +# Copyright (C) 2020-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -28,6 +28,7 @@ from pycocotools import coco as coco_loader from rest_framework import status from rest_framework.test import APIClient, APITestCase +from datumaro.util.test_utils import TestDir from cvat.apps.engine.models import (AttributeSpec, AttributeType, Data, Job, Project, Segment, StatusChoice, Task, Label, StorageMethodChoice, StorageChoice) from cvat.apps.engine.media_extractors import ValidateDimension @@ -1776,6 +1777,190 @@ class TaskUpdateLabelsAPITestCase(UpdateLabelsAPITestCase): } self._check_api_v1_task(data) +class TaskMoveAPITestCase(APITestCase): + + def setUp(self): + self.client = APIClient() + + self._run_api_v1_job_id_annotation(self.task.segment_set.first().job_set.first().id, self.annotation_data) + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + projects = [] + + project_data = { + "name": "Project for task move 1", + "owner": cls.admin, + "labels": [{ + "name": "car" + }, { + "name": "person" + }] + } + db_project = create_db_project(project_data) + projects.append(db_project) + + project_data = { + "name": "Project for task move 2", + "owner": cls.admin, + "labels": [{ + "name": "car", + "attributes": [{ + "name": "color", + "mutable": False, + "input_type": AttributeType.SELECT, + "default_value": "white", + "values": ["white", "yellow", "green", "red"] + }] + }, { + "name": "test" + }, { + "name": "other.label" + }] + } + + db_project = create_db_project(project_data) + projects.append(db_project) + + cls.projects = projects + + task_data = { + "name": "Task for moving", + "owner": cls.admin, + "overlap": 0, + "segment_size": 100, + "image_quality": 75, + "size": 100, + "project": None, + "labels": [{ + "name": "car", + "attributes": [{ + "name": "color", + "mutable": False, + "input_type": AttributeType.SELECT, + "default_value": "white", + "values": ["white", "yellow", "green", "red"] + }] + }] + } + db_task = create_db_task(task_data) + cls.task = db_task + + cls.annotation_data = { + "version": 1, + "tags": [ + { + "frame": 0, + "label_id": cls.task.label_set.first().id, + "group": None, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "frame": 0, + "label_id": cls.task.label_set.first().id, + "group": None, + "source": "manual", + "attributes": [ + { + "spec_id": cls.task.label_set.first().attributespec_set.first().id, + "value": cls.task.label_set.first().attributespec_set.first().values.split('\'')[1] + } + ], + "points": [1.0, 2.1, 100, 300.222], + "type": "rectangle", + "occluded": False + } + ], + "tracks": [ + { + "frame": 0, + "label_id": cls.task.label_set.first().id, + "group": None, + "source": "manual", + "attributes": [ + { + "spec_id": cls.task.label_set.first().attributespec_set.first().id, + "value": cls.task.label_set.first().attributespec_set.first().values.split('\'')[1] + } + ], + "shapes": [ + { + "frame": 0, + "attributes": [], + "points": [1.0, 2.1, 100, 300.222], + "type": "rectangle", + "occluded": False, + "outside": False + }, + { + "frame": 2, + "attributes": [], + "points": [2.0, 2.1, 100, 300.222], + "type": "rectangle", + "occluded": True, + "outside": True + }, + ] + } + ] + } + + def _run_api_v1_tasks_id(self, tid, data): + with ForceLogin(self.admin, self.client): + response = self.client.patch('/api/v1/tasks/{}'.format(tid), + data=data, format="json") + + return response + + def _run_api_v1_job_id_annotation(self, jid, data): + with ForceLogin(self.admin, self.client): + response = self.client.patch('/api/v1/jobs/{}/annotations?action=create'.format(jid), + data=data, format="json") + + return response + + def _check_response(self, response, data): + self.assertEqual(response.data["project_id"], data["project_id"]) + + def _check_api_v1_tasks(self, tid, data, expected_status=status.HTTP_200_OK): + response = self._run_api_v1_tasks_id(tid, data) + self.assertEqual(response.status_code, expected_status) + if (expected_status == status.HTTP_200_OK): + self._check_response(response, data) + + def test_move_task_bad_request(self): + # Try to move task without proper label mapping + data = { + "project_id": self.projects[0].id, + "labels": [{ + "id": self.task.label_set.first().id, + "name": "some.other.label" + }] + } + self._check_api_v1_tasks(self.task.id, data, status.HTTP_400_BAD_REQUEST) + + def test_move_task(self): + # Try to move single task to the project + data = { + "project_id": self.projects[0].id + } + self._check_api_v1_tasks(self.task.id, data) + + # Try to move task from project to the other project + data = { + "project_id": self.projects[1].id, + "labels": [{ + "id": self.projects[0].label_set.all()[1].id, + "name": "test" + }] + } + self._check_api_v1_tasks(self.task.id, data) + class TaskCreateAPITestCase(APITestCase): def setUp(self): self.client = APIClient() @@ -1887,6 +2072,348 @@ class TaskCreateAPITestCase(APITestCase): } self._check_api_v1_tasks(None, data) + + +class TaskImportExportAPITestCase(APITestCase): + + def setUp(self): + self.client = APIClient() + self.tasks = [] + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + cls.media_data = [] + + image_count = 10 + imagename_pattern = "test_{}.jpg" + for i in range(image_count): + filename = imagename_pattern.format(i) + path = os.path.join(settings.SHARE_ROOT, filename) + _, data = generate_image_file(filename) + with open(path, "wb") as image: + image.write(data.read()) + + cls.media_data.append( + { + **{"image_quality": 75, + "copy_data": True, + "start_frame": 2, + "stop_frame": 9, + "frame_filter": "step=2", + }, + **{"server_files[{}]".format(i): imagename_pattern.format(i) for i in range(image_count)}, + } + ) + + filename = "test_video_1.mp4" + path = os.path.join(settings.SHARE_ROOT, filename) + _, data = generate_video_file(filename, width=1280, height=720) + with open(path, "wb") as video: + video.write(data.read()) + cls.media_data.append( + { + "image_quality": 75, + "copy_data": True, + "start_frame": 2, + "stop_frame": 24, + "frame_filter": "step=2", + "server_files[0]": filename, + } + ) + + filename = os.path.join("test_archive_1.zip") + path = os.path.join(settings.SHARE_ROOT, filename) + _, data = generate_zip_archive_file(filename, count=5) + with open(path, "wb") as zip_archive: + zip_archive.write(data.read()) + cls.media_data.append( + { + "image_quality": 75, + "server_files[0]": filename, + } + ) + + filename = "test_pointcloud_pcd.zip" + source_path = os.path.join(os.path.dirname(__file__), 'assets', filename) + path = os.path.join(settings.SHARE_ROOT, filename) + shutil.copyfile(source_path, path) + cls.media_data.append( + { + "image_quality": 75, + "server_files[0]": filename, + } + ) + + filename = "test_velodyne_points.zip" + source_path = os.path.join(os.path.dirname(__file__), 'assets', filename) + path = os.path.join(settings.SHARE_ROOT, filename) + shutil.copyfile(source_path, path) + cls.media_data.append( + { + "image_quality": 75, + "server_files[0]": filename, + } + ) + + filename = os.path.join("videos", "test_video_1.mp4") + path = os.path.join(settings.SHARE_ROOT, filename) + os.makedirs(os.path.dirname(path)) + _, data = generate_video_file(filename, width=1280, height=720) + with open(path, "wb") as video: + video.write(data.read()) + + generate_manifest_file(data_type='video', manifest_path=os.path.join(settings.SHARE_ROOT, 'videos', 'manifest.jsonl'), + sources=[path]) + + cls.media_data.append( + { + "image_quality": 70, + "copy_data": True, + "server_files[0]": filename, + "server_files[1]": os.path.join("videos", "manifest.jsonl"), + "use_cache": True, + } + ) + + generate_manifest_file(data_type='images', manifest_path=os.path.join(settings.SHARE_ROOT, 'manifest.jsonl'), + sources=[os.path.join(settings.SHARE_ROOT, imagename_pattern.format(i)) for i in range(1, 8)]) + cls.media_data.append( + { + **{"image_quality": 70, + "copy_data": True, + "use_cache": True, + "frame_filter": "step=2", + "server_files[0]": "manifest.jsonl", + }, + **{ + **{"server_files[{}]".format(i): imagename_pattern.format(i) for i in range(1, 8)}, + } + } + ) + + cls.media_data.extend([ + # image list local + { + "client_files[0]": generate_image_file("test_1.jpg")[1], + "client_files[1]": generate_image_file("test_2.jpg")[1], + "client_files[2]": generate_image_file("test_3.jpg")[1], + "image_quality": 75, + }, + # video local + { + "client_files[0]": generate_video_file("test_video.mp4")[1], + "image_quality": 75, + }, + # zip archive local + { + "client_files[0]": generate_zip_archive_file("test_archive_1.zip", 10)[1], + "image_quality": 50, + }, + # pdf local + { + "client_files[0]": generate_pdf_file("test_pdf_1.pdf", 7)[1], + "image_quality": 54, + }, + ]) + + def tearDown(self): + for task in self.tasks: + shutil.rmtree(os.path.join(settings.TASKS_ROOT, str(task["id"]))) + shutil.rmtree(os.path.join(settings.MEDIA_DATA_ROOT, str(task["data_id"]))) + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + path = os.path.join(settings.SHARE_ROOT, "test_1.jpg") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "test_2.jpg") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "test_3.jpg") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "test_video_1.mp4") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "videos", "test_video_1.mp4") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "videos", "manifest.jsonl") + os.remove(path) + os.rmdir(os.path.dirname(path)) + + path = os.path.join(settings.SHARE_ROOT, "test_pointcloud_pcd.zip") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "test_velodyne_points.zip") + os.remove(path) + + path = os.path.join(settings.SHARE_ROOT, "manifest.jsonl") + os.remove(path) + + def _create_tasks(self): + self.tasks = [] + + def _create_task(task_data, media_data): + response = self.client.post('/api/v1/tasks', data=task_data, format="json") + assert response.status_code == status.HTTP_201_CREATED + tid = response.data["id"] + + for media in media_data.values(): + if isinstance(media, io.BytesIO): + media.seek(0) + response = self.client.post("/api/v1/tasks/{}/data".format(tid), data=media_data) + assert response.status_code == status.HTTP_202_ACCEPTED + response = self.client.get("/api/v1/tasks/{}".format(tid)) + data_id = response.data["data"] + self.tasks.append({ + "id": tid, + "data_id": data_id, + }) + + task_data = [ + { + "name": "my task #1", + "owner_id": self.owner.id, + "assignee_id": self.assignee.id, + "overlap": 0, + "segment_size": 100, + "labels": [{ + "name": "car", + "color": "#ff00ff", + "attributes": [{ + "name": "bool_attribute", + "mutable": True, + "input_type": AttributeType.CHECKBOX, + "default_value": "true" + }], + }, { + "name": "person", + }, + ] + }, + { + "name": "my task #2", + "owner_id": self.owner.id, + "assignee_id": self.assignee.id, + "overlap": 1, + "segment_size": 3, + "labels": [{ + "name": "car", + "color": "#ff00ff", + "attributes": [{ + "name": "bool_attribute", + "mutable": True, + "input_type": AttributeType.CHECKBOX, + "default_value": "true" + }], + }, { + "name": "person", + }, + ] + }, + ] + + with ForceLogin(self.owner, self.client): + for data in task_data: + for media in self.media_data: + _create_task(data, media) + + def _run_api_v1_tasks_id_export(self, tid, user, query_params=""): + with ForceLogin(user, self.client): + response = self.client.get('/api/v1/tasks/{}?{}'.format(tid, query_params), format="json") + + return response + + def _run_api_v1_tasks_id_import(self, user, data): + with ForceLogin(user, self.client): + response = self.client.post('/api/v1/tasks?action=import', data=data, format="multipart") + + return response + + def _run_api_v1_tasks_id(self, tid, user): + with ForceLogin(user, self.client): + response = self.client.get('/api/v1/tasks/{}'.format(tid), format="json") + + return response.data + + def _run_api_v1_tasks_id_export_import(self, user): + if user: + if user is self.user or user is self.annotator: + HTTP_200_OK = status.HTTP_403_FORBIDDEN + HTTP_202_ACCEPTED = status.HTTP_403_FORBIDDEN + HTTP_201_CREATED = status.HTTP_403_FORBIDDEN + else: + HTTP_200_OK = status.HTTP_200_OK + HTTP_202_ACCEPTED = status.HTTP_202_ACCEPTED + HTTP_201_CREATED = status.HTTP_201_CREATED + else: + HTTP_200_OK = status.HTTP_401_UNAUTHORIZED + HTTP_202_ACCEPTED = status.HTTP_401_UNAUTHORIZED + HTTP_201_CREATED = status.HTTP_401_UNAUTHORIZED + + self._create_tasks() + for task in self.tasks: + tid = task["id"] + response = self._run_api_v1_tasks_id_export(tid, user, "action=export") + self.assertEqual(response.status_code, HTTP_202_ACCEPTED) + + response = self._run_api_v1_tasks_id_export(tid, user, "action=export") + self.assertEqual(response.status_code, HTTP_201_CREATED) + + response = self._run_api_v1_tasks_id_export(tid, user, "action=download") + self.assertEqual(response.status_code, HTTP_200_OK) + + if user and user is not self.observer and user is not self.user and user is not self.annotator: + self.assertTrue(response.streaming) + content = io.BytesIO(b"".join(response.streaming_content)) + content.seek(0) + + uploaded_data = { + "task_file": content, + } + response = self._run_api_v1_tasks_id_import(user, uploaded_data) + self.assertEqual(response.status_code, HTTP_202_ACCEPTED) + if user is not self.observer and user is not self.user and user is not self.annotator: + rq_id = response.data["rq_id"] + response = self._run_api_v1_tasks_id_import(user, {"rq_id": rq_id}) + self.assertEqual(response.status_code, HTTP_201_CREATED) + original_task = self._run_api_v1_tasks_id(tid, user) + imported_task = self._run_api_v1_tasks_id(response.data["id"], user) + compare_objects( + self=self, + obj1=original_task, + obj2=imported_task, + ignore_keys=( + "id", + "url", + "owner", + "project_id", + "assignee", + "created_date", + "updated_date", + "data", + ), + ) + + def test_api_v1_tasks_id_export_admin(self): + self._run_api_v1_tasks_id_export_import(self.admin) + + def test_api_v1_tasks_id_export_user(self): + self._run_api_v1_tasks_id_export_import(self.user) + + def test_api_v1_tasks_id_export_annotator(self): + self._run_api_v1_tasks_id_export_import(self.annotator) + + def test_api_v1_tasks_id_export_observer(self): + self._run_api_v1_tasks_id_export_import(self.observer) + + def test_api_v1_tasks_id_export_no_auth(self): + self._run_api_v1_tasks_id_export_import(None) + def generate_image_file(filename): f = BytesIO() gen = random.SystemRandom() @@ -2142,6 +2669,7 @@ class TaskDataAPITestCase(APITestCase): path = os.path.join(settings.SHARE_ROOT, "videos", "manifest.jsonl") os.remove(path) + os.rmdir(os.path.dirname(path)) path = os.path.join(settings.SHARE_ROOT, "manifest.jsonl") os.remove(path) @@ -2803,28 +3331,35 @@ class TaskDataAPITestCase(APITestCase): response = self._create_task(None, data) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) -def compare_objects(self, obj1, obj2, ignore_keys, fp_tolerance=.001): +def compare_objects(self, obj1, obj2, ignore_keys, fp_tolerance=.001, + current_key=None): + key_info = "{}: ".format(current_key) if current_key else "" + if isinstance(obj1, dict): - self.assertTrue(isinstance(obj2, dict), "{} != {}".format(obj1, obj2)) + self.assertTrue(isinstance(obj2, dict), + "{}{} != {}".format(key_info, obj1, obj2)) for k, v1 in obj1.items(): if k in ignore_keys: continue v2 = obj2[k] if k == 'attributes': - key = lambda a: a['spec_id'] + key = lambda a: a['spec_id'] if 'spec_id' in a else a['id'] v1.sort(key=key) v2.sort(key=key) - compare_objects(self, v1, v2, ignore_keys) + compare_objects(self, v1, v2, ignore_keys, current_key=k) elif isinstance(obj1, list): - self.assertTrue(isinstance(obj2, list), "{} != {}".format(obj1, obj2)) - self.assertEqual(len(obj1), len(obj2), "{} != {}".format(obj1, obj2)) + self.assertTrue(isinstance(obj2, list), + "{}{} != {}".format(key_info, obj1, obj2)) + self.assertEqual(len(obj1), len(obj2), + "{}{} != {}".format(key_info, obj1, obj2)) for v1, v2 in zip(obj1, obj2): - compare_objects(self, v1, v2, ignore_keys) + compare_objects(self, v1, v2, ignore_keys, current_key=current_key) else: if isinstance(obj1, float) or isinstance(obj2, float): - self.assertAlmostEqual(obj1, obj2, delta=fp_tolerance) + self.assertAlmostEqual(obj1, obj2, delta=fp_tolerance, + msg=current_key) else: - self.assertEqual(obj1, obj2) + self.assertEqual(obj1, obj2, msg=current_key) class JobAnnotationAPITestCase(APITestCase): def setUp(self): @@ -2835,6 +3370,7 @@ class JobAnnotationAPITestCase(APITestCase): create_db_users(cls) def _create_task(self, owner, assignee, annotation_format=""): + dimension = DimensionType.DIM_2D data = { "name": "my task #1", "owner_id": owner.id, @@ -2926,6 +3462,12 @@ class JobAnnotationAPITestCase(APITestCase): }, ] }] + elif annotation_format in ['Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0']: + data["labels"] = [{ + "name": "car"}, + {"name": "bus"} + ] + dimension = DimensionType.DIM_3D elif annotation_format == "ICDAR Segmentation 1.0": data["labels"] = [{ "name": "icdar", @@ -2975,6 +3517,15 @@ class JobAnnotationAPITestCase(APITestCase): "image_quality": 75, "frame_filter": "step=3", } + if dimension == DimensionType.DIM_3D: + images = { + "client_files[0]": open( + os.path.join(os.path.dirname(__file__), 'assets', 'test_pointcloud_pcd.zip' + if annotation_format == 'Sly Point Cloud Format 1.0' else 'test_velodyne_points.zip'), + 'rb'), + "image_quality": 100, + } + response = self.client.post("/api/v1/tasks/{}/data".format(tid), data=images) assert response.status_code == status.HTTP_202_ACCEPTED @@ -3936,7 +4487,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): def _get_initial_annotation(annotation_format): if annotation_format not in ["Market-1501 1.0", "ICDAR Recognition 1.0", - "ICDAR Localization 1.0", "ICDAR Segmentation 1.0"]: + "ICDAR Localization 1.0", "ICDAR Segmentation 1.0", + 'Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0']: rectangle_tracks_with_attrs = [{ "frame": 0, "label_id": task["labels"][0]["id"], @@ -4280,7 +4832,32 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], }] annotations["tags"] = tags_with_attrs - + elif annotation_format in ['Kitti Raw Format 1.0','Sly Point Cloud Format 1.0']: + velodyne_wo_attrs = [{ + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "source": "manual", + "attributes": [ + ], + "points": [-3.62, 7.95, -1.03, 0.0, 0.0, 0.0, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "type": "cuboid_3d", + "occluded": False, + }, + { + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "points": [23.01, 8.34, -0.76, 0.0, 0.0, 0.0, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "type": "cuboid_3d", + "occluded": False, + } + ] + annotations["shapes"] = velodyne_wo_attrs elif annotation_format == "ICDAR Recognition 1.0": tags_with_attrs = [{ "frame": 1, @@ -4498,6 +5075,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): data["version"] += 2 # upload is delete + put self._check_response(response, data) + break def _check_dump_content(self, content, task, jobs, data, format_name): def etree_to_dict(t): d = {t.tag: {} if t.attrib else None} @@ -4533,6 +5111,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): self.assertTrue(zipfile.is_zipfile(content)) elif format_name == "YOLO 1.1": self.assertTrue(zipfile.is_zipfile(content)) + elif format_name in ['Kitti Raw Format 1.0','Sly Point Cloud Format 1.0']: + self.assertTrue(zipfile.is_zipfile(content)) elif format_name == "COCO 1.0": with tempfile.TemporaryDirectory() as tmp_dir: zipfile.ZipFile(content).extractall(tmp_dir) @@ -4756,3 +5336,182 @@ class ServerShareAPITestCase(APITestCase): def test_api_v1_server_share_no_auth(self): response = self._run_api_v1_server_share(None, "/") self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class ServerShareDifferentTypesAPITestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + @staticmethod + def _create_shared_files(shared_images): + image = Image.new('RGB', size=(100, 50)) + for img in shared_images: + img_path = os.path.join(settings.SHARE_ROOT, img) + if not osp.exists(osp.dirname(img_path)): + os.makedirs(osp.dirname(img_path)) + image.save(img_path, img_path.split(".")[1:][0]) + + def _get_request(self, path): + with ForceLogin(self.user, self.client): + response = self.client.get(path) + return response + + def _run_api_v1_server_share(self, directory): + with ForceLogin(self.user, self.client): + response = self.client.get( + '/api/v1/server/share?directory={}'.format(directory)) + + 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") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + def test_api_v1_combined_image_and_directory_extractors(self): + shared_images = ["data1/street.png", "data1/people.jpeg", "data1/street_1.jpeg", "data1/street_2.jpeg", + "data1/street_3.jpeg", "data1/subdir/image_4.jpeg", "data1/subdir/image_5.jpeg", + "data1/subdir/image_6.jpeg"] + images_count = len(shared_images) + self._create_shared_files(shared_images) + response = self._run_api_v1_server_share("/data1") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + shared_images = [img for img in shared_images if os.path.dirname(img) != "/data1/subdir"] + shared_images.append("/data1/subdir/") + shared_images.append("/data1/") + remote_files = {"server_files[%d]" % i: shared_images[i] for i in range(len(shared_images))} + + task = { + "name": "task combined image and directory extractors", + "overlap": 0, + "segment_size": 0, + "labels": [ + {"name": "car"}, + {"name": "person"}, + ] + } + image_data = { + "size": 0, + "image_quality": 70, + "compressed_chunk_type": "imageset", + "original_chunk_type": "imageset", + "client_files": [], + "remote_files": [], + "use_zip_chunks": False, + "use_cache": False, + "copy_data": False + } + image_data.update(remote_files) + # create task with server + task = self._create_task(task, image_data) + response = self._get_request("/api/v1/tasks/%s/data/meta" % task["id"]) + self.assertEqual(len(response.data["frames"]), images_count) + + +class TaskAnnotation2DContext(APITestCase): + def setUp(self): + self.client = APIClient() + self.task = { + "name": "my archive task without copying #11", + "overlap": 0, + "segment_size": 0, + "labels": [ + {"name": "car"}, + {"name": "person"}, + ] + } + + @classmethod + def setUpTestData(cls): + create_db_users(cls) + + def _get_request_with_data(self, path, data, user): + with ForceLogin(user, self.client): + response = self.client.get(path, data) + return response + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + 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") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + def create_zip_archive_with_related_images(self, file_name, test_dir, context_images_info): + with tempfile.TemporaryDirectory() as tmp_dir: + for img in context_images_info: + image = Image.new('RGB', size=(100, 50)) + image.save(osp.join(tmp_dir, img), 'png') + if context_images_info[img]: + related_path = osp.join(tmp_dir, "related_images", img.replace(".", "_")) + os.makedirs(related_path) + image.save(osp.join(related_path, f"related_{img}"), 'png') + + zip_file_path = osp.join(test_dir, file_name) + shutil.make_archive(zip_file_path, 'zip', tmp_dir) + return f"{zip_file_path}.zip" + + def test_check_flag_has_related_context(self): + with TestDir() as test_dir: + test_cases = { + "All images with context": {"image_1.png": True, "image_2.png": True}, + "One image with context": {"image_1.png": True, "image_2.png": False} + } + for test_case, context_img_data in test_cases.items(): + filename = self.create_zip_archive_with_related_images(test_case, test_dir, context_img_data) + img_data = { + "client_files[0]": open(filename, 'rb'), + "image_quality": 75, + } + task = self._create_task(self.task, img_data) + task_id = task["id"] + + response = self._get_request("/api/v1/tasks/%s/data/meta" % task_id, self.admin) + for frame in response.data["frames"]: + self.assertEqual(context_img_data[frame["name"]], frame["has_related_context"]) + + def test_fetch_related_image_from_server(self): + test_name = self._testMethodName + context_img_data ={"image_1.png": True} + with TestDir() as test_dir: + filename = self.create_zip_archive_with_related_images(test_name, test_dir, context_img_data) + img_data = { + "client_files[0]": open(filename, 'rb'), + "image_quality": 75, + } + task = self._create_task(self.task , img_data) + task_id = task["id"] + data = { + "quality": "original", + "type": "context_image", + "number": 0 + } + response = self._get_request_with_data("/api/v1/tasks/%s/data" % task_id, data, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/cvat/apps/engine/tests/test_rest_api_3D.py b/cvat/apps/engine/tests/test_rest_api_3D.py new file mode 100644 index 00000000..913fb4a6 --- /dev/null +++ b/cvat/apps/engine/tests/test_rest_api_3D.py @@ -0,0 +1,750 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +import io +import os +import os.path as osp +import tempfile +import xml.etree.ElementTree as ET +import zipfile +from collections import defaultdict +from glob import glob +from io import BytesIO +import copy +from shutil import copyfile +import itertools + +from django.contrib.auth.models import Group, User +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +from cvat.apps.engine.media_extractors import ValidateDimension +from cvat.apps.dataset_manager.task import TaskAnnotation +from datumaro.util.test_utils import TestDir + +CREATE_ACTION = "create" +UPDATE_ACTION = "update" +DELETE_ACTION = "delete" + + +class ForceLogin: + def __init__(self, user, client): + self.user = user + self.client = client + + def __enter__(self): + if self.user: + self.client.force_login(self.user, + backend='django.contrib.auth.backends.ModelBackend') + + return self + + def __exit__(self, exception_type, exception_value, traceback): + if self.user: + self.client.logout() + +class _DbTestBase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + cls.create_db_users() + + @classmethod + def create_db_users(cls): + (group_admin, _) = Group.objects.get_or_create(name="admin") + (group_user, _) = Group.objects.get_or_create(name="user") + + user_admin = User.objects.create_superuser(username="admin", email="", + password="admin") + user_admin.groups.add(group_admin) + user_dummy = User.objects.create_user(username="user", password="user") + user_dummy.groups.add(group_user) + + cls.admin = user_admin + cls.user = user_dummy + + def _put_api_v1_task_id_annotations(self, tid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/tasks/%s/annotations" % tid, + data=data, format="json") + + return response + + def _put_api_v1_job_id_annotations(self, jid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/jobs/%s/annotations" % jid, + data=data, format="json") + + return response + + def _patch_api_v1_task_id_annotations(self, tid, data, action, user): + with ForceLogin(user, self.client): + response = self.client.patch( + "/api/v1/tasks/{}/annotations?action={}".format(tid, action), + data=data, format="json") + + return response + + def _patch_api_v1_job_id_annotations(self, jid, data, action, user): + with ForceLogin(user, self.client): + response = self.client.patch( + "/api/v1/jobs/{}/annotations?action={}".format(jid, action), + 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") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + @staticmethod + def _get_tmp_annotation(task, annotation): + tmp_annotations = copy.deepcopy(annotation) + for item in tmp_annotations: + if item in ["tags", "shapes", "tracks"]: + for index_elem, _ in enumerate(tmp_annotations[item]): + tmp_annotations[item][index_elem]["label_id"] = task["labels"][0]["id"] + + for index_attribute, attribute in enumerate(task["labels"][0]["attributes"]): + spec_id = task["labels"][0]["attributes"][index_attribute]["id"] + + value = attribute["default_value"] + + if item == "tracks" and attribute["mutable"]: + for index_shape, _ in enumerate(tmp_annotations[item][index_elem]["shapes"]): + tmp_annotations[item][index_elem]["shapes"][index_shape]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + else: + tmp_annotations[item][index_elem]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + return tmp_annotations + + def _get_jobs(self, task_id): + with ForceLogin(self.admin, self.client): + response = self.client.get("/api/v1/tasks/{}/jobs".format(task_id)) + return response.data + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + return response + + def _get_request_with_data(self, path, data, user): + with ForceLogin(user, self.client): + response = self.client.get(path, data) + return response + + def _delete_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.delete(path) + return response + + def _download_file(self, url, data, user, file_name): + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + content = BytesIO(b"".join(response.streaming_content)) + with open(file_name, "wb") as f: + f.write(content.getvalue()) + + def _upload_file(self, url, data, user): + response = self._put_request_with_data(url, {"annotation_file": data}, user) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def _generate_url_dump_tasks_annotations(self, task_id): + return f"/api/v1/tasks/{task_id}/annotations" + + def _generate_url_upload_tasks_annotations(self, task_id, upload_format_name): + return f"/api/v1/tasks/{task_id}/annotations?format={upload_format_name}" + + def _generate_url_dump_job_annotations(self, job_id): + return f"/api/v1/jobs/{job_id}/annotations" + + def _generate_url_upload_job_annotations(self, job_id, upload_format_name): + return f"/api/v1/jobs/{job_id}/annotations?format={upload_format_name}" + + def _generate_url_dump_dataset(self, task_id): + return f"/api/v1/tasks/{task_id}/dataset" + + def _remove_annotations(self, tid): + response = self._delete_request(f"/api/v1/tasks/{tid}/annotations", self.admin) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + return response + + def _put_request_with_data(self, url, data, user): + with ForceLogin(user, self.client): + response = self.client.put(url, data) + return response + + def _delete_task(self, tid): + response = self._delete_request('/api/v1/tasks/{}'.format(tid), self.admin) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + return response + + def _check_dump_content(self, content, task_data, format_name, related_files=True): + def etree_to_dict(t): + d = {t.tag: {} if t.attrib else None} + children = list(t) + if children: + dd = defaultdict(list) + for dc in map(etree_to_dict, children): + for k, v in dc.items(): + dd[k].append(v) + d = {t.tag: {k: v[0] if len(v) == 1 else v + for k, v in dd.items()}} + if t.attrib: + d[t.tag].update(('@' + k, v) + for k, v in t.attrib.items()) + if t.text: + text = t.text.strip() + if children or t.attrib: + if text: + d[t.tag]['#text'] = text + else: + d[t.tag] = text + return d + if format_name == "Kitti Raw Format 1.0": + with tempfile.TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(content).extractall(tmp_dir) + xmls = glob(osp.join(tmp_dir, '**', '*.xml'), recursive=True) + self.assertTrue(xmls) + for xml in xmls: + xmlroot = ET.parse(xml).getroot() + self.assertEqual(xmlroot.tag, "boost_serialization") + items = xmlroot.findall("./tracklets/item") + self.assertEqual(len(items), len(task_data["shapes"])) + elif format_name == "Sly Point Cloud Format 1.0": + with tempfile.TemporaryDirectory() as tmp_dir: + checking_files = [osp.join(tmp_dir, "key_id_map.json"), + osp.join(tmp_dir, "meta.json"), + osp.join(tmp_dir, "ds0", "ann", "000001.pcd.json"), + osp.join(tmp_dir, "ds0", "ann", "000002.pcd.json"), + osp.join(tmp_dir, "ds0", "ann","000003.pcd.json")] + if related_files: + checking_files.extend([osp.join(tmp_dir, "ds0", "related_images", "000001.pcd_pcd", "000001.png.json"), + osp.join(tmp_dir, "ds0", "related_images", "000002.pcd_pcd", "000002.png.json"), + osp.join(tmp_dir, "ds0", "related_images", "000003.pcd_pcd", "000003.png.json")]) + zipfile.ZipFile(content).extractall(tmp_dir) + jsons = glob(osp.join(tmp_dir, '**', '*.json'), recursive=True) + self.assertTrue(jsons) + self.assertTrue(set(checking_files).issubset(set(jsons))) + + +class Task3DTest(_DbTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.format_names = ["Sly Point Cloud Format 1.0", "Kitti Raw Format 1.0"] + cls._image_sizes = {} + cls.pointcloud_pcd_filename = "test_canvas3d.zip" + cls.pointcloud_pcd_path = osp.join(os.path.dirname(__file__), 'assets', cls.pointcloud_pcd_filename) + image_sizes = [] + zip_file = zipfile.ZipFile(cls.pointcloud_pcd_path ) + for info in zip_file.namelist(): + if info.rsplit(".", maxsplit=1)[-1] == "pcd": + with zip_file.open(info, "r") as file: + data = ValidateDimension.get_pcd_properties(file) + image_sizes.append((int(data["WIDTH"]), int(data["HEIGHT"]))) + cls.task = { + "name": "main task", + "owner_id": 1, + "assignee_id": 2, + "overlap": 0, + "segment_size": 100, + "labels": [ + {"name": "car"}, + {"name": "person"}, + ] + } + cls.task_with_attributes = { + "name": "task with attributes", + "owner_id": 1, + "assignee_id": 2, + "overlap": 0, + "segment_size": 100, + "labels": [ + {"name": "car", + "color": "#2080c0", + "attributes": [ + { + "name": "radio_name", + "mutable": False, + "input_type": "radio", + "default_value": "x1", + "values": ["x1", "x2", "x3"] + }, + { + "name": "check_name", + "mutable": True, + "input_type": "checkbox", + "default_value": "false", + "values": ["false"] + }, + { + "name": "text_name", + "mutable": False, + "input_type": "text", + "default_value": "qwerty", + "values": ["qwerty"] + }, + { + "name": "number_name", + "mutable": False, + "input_type": "number", + "default_value": "-4.0", + "values": ["-4", "4", "1"] + } + ] + }, + {"name": "person", + "color": "#c06060", + "attributes": [] + }, + ] + } + cls.task_many_jobs = { + "name": "task several jobs", + "owner_id": 1, + "assignee_id": 2, + "overlap": 3, + "segment_size": 1, + "labels": [ + { + "name": "car", + "color": "#c06060", + "id": 1, + "attributes": [] + } + ] + } + cls.cuboid_example = { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "cuboid", + "occluded": False, + "z_order": 0, + "points": [0.16, 0.20, -0.26, 0, -0.14, 0, 4.84, 4.48, 4.12, 0, 0, 0, 0, 0, 0, 0], + "frame": 0, + "label_id": None, + "group": 0, + "source": "manual", + "attributes": [] + }, + ], + "tracks": [] + } + cls._image_sizes[cls.pointcloud_pcd_filename] = image_sizes + cls.expected_action = { + cls.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'annotation_changed': True}, + cls.user: {'name': 'user', 'code': status.HTTP_200_OK, 'annotation_changed': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'annotation_changed': False}, + } + cls.expected_dump_upload = { + cls.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + cls.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False, 'annotation_loaded': False}, + } + + def copy_pcd_file_and_get_task_data(self, test_dir): + tmp_file = osp.join(test_dir, self.pointcloud_pcd_filename) + copyfile(self.pointcloud_pcd_path, tmp_file) + task_data = { + "client_files[0]": open(tmp_file, 'rb'), + "image_quality": 100, + } + return task_data + + def test_api_v1_create_annotation_in_job(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + annotation = self._get_tmp_annotation(task, self.cuboid_example) + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + response = self._patch_api_v1_task_id_annotations(task_id, annotation, CREATE_ACTION, user) + self.assertEqual(response.status_code, edata["code"]) + if edata["annotation_changed"]: + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_shape = task_ann.data["shapes"][0] + task_shape.pop("id") + self.assertEqual(task_shape, annotation["shapes"][0]) + self._remove_annotations(task_id) + + def test_api_v1_update_annotation_in_task(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + annotation = self._get_tmp_annotation(task, self.cuboid_example) + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + annotation["shapes"][0]["points"] = [x + 0.1 for x in annotation["shapes"][0]["points"]] + annotation["shapes"][0]["id"] = task_ann_prev.data["shapes"][0]["id"] + response = self._patch_api_v1_task_id_annotations(task_id, annotation, UPDATE_ACTION, user) + self.assertEqual(response.status_code, edata["code"], task_id) + + if edata["annotation_changed"]: + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + self.assertEqual(task_ann.data["shapes"], annotation["shapes"]) + + def test_api_v1_remove_annotation_in_task(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + annotation = self._get_tmp_annotation(task, self.cuboid_example) + + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + response = self._patch_api_v1_task_id_annotations(task_id, annotation, CREATE_ACTION, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + annotation["shapes"][0]["id"] = task_ann_prev.data["shapes"][0]["id"] + + response = self._patch_api_v1_task_id_annotations(task_id, annotation, DELETE_ACTION, user) + self.assertEqual(response.status_code, edata["code"]) + + if edata["annotation_changed"]: + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + self.assertTrue(len(task_ann.data["shapes"]) == 0) + + def test_api_v1_create_annotation_in_jobs(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + annotation = self._get_tmp_annotation(task, self.cuboid_example) + jobs = self._get_jobs(task_id) + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + response = self._patch_api_v1_job_id_annotations(jobs[0]["id"], annotation, CREATE_ACTION, user) + self.assertEqual(response.status_code, edata["code"]) + + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + if len(task_ann.data["shapes"]): + task_shape = task_ann.data["shapes"][0] + task_shape.pop("id") + self.assertEqual(task_shape, annotation["shapes"][0]) + self._remove_annotations(task_id) + + def test_api_v1_update_annotation_in_job(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + jobs = self._get_jobs(task_id) + annotation = self._get_tmp_annotation(task, self.cuboid_example) + + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + + annotation["shapes"][0]["points"] = [x + 0.1 for x in annotation["shapes"][0]["points"]] + annotation["shapes"][0]["id"] = task_ann_prev.data["shapes"][0]["id"] + + response = self._patch_api_v1_job_id_annotations(jobs[0]["id"], annotation, UPDATE_ACTION, user) + self.assertEqual(response.status_code, edata["code"]) + + if edata["annotation_changed"]: + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + self.assertEqual(task_ann.data["shapes"], annotation["shapes"]) + + def test_api_v1_remove_annotation_in_job(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + jobs = self._get_jobs(task_id) + annotation = self._get_tmp_annotation(task, self.cuboid_example) + + for user, edata in list(self.expected_action.items()): + with self.subTest(format=edata["name"]): + response = self._patch_api_v1_job_id_annotations(jobs[0]["id"], annotation, CREATE_ACTION, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + annotation["shapes"][0]["id"] = task_ann_prev.data["shapes"][0]["id"] + response = self._patch_api_v1_job_id_annotations(jobs[0]["id"], annotation, DELETE_ACTION, user) + self.assertEqual(response.status_code, edata["code"]) + + if edata["annotation_changed"]: + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + self.assertTrue(len(task_ann.data["shapes"]) == 0) + + def test_api_v1_dump_and_upload_annotation(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + + for format_name in self.format_names: + annotation = self._get_tmp_annotation(task, self.cuboid_example) + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + + for user, edata in list(self.expected_dump_upload.items()): + with self.subTest(format=f"{format_name}_{edata['name']}_dump"): + url = self._generate_url_dump_tasks_annotations(task_id) + file_name = osp.join(test_dir, f"{format_name}_{edata['name']}.zip") + + data = { + "format": format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = io.BytesIO(b"".join(response.streaming_content)) + with open(file_name, "wb") as f: + f.write(content.getvalue()) + self._check_dump_content(content, task_ann_prev.data, format_name, related_files=False) + self.assertEqual(osp.exists(file_name), edata['file_exists']) + + self._remove_annotations(task_id) + with self.subTest(format=f"{format_name}_upload"): + file_name = osp.join(test_dir, f"{format_name}_admin.zip") + url = self._generate_url_upload_tasks_annotations(task_id, format_name) + + with open(file_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + task_ann_prev.data["shapes"][0].pop("id") + task_ann.data["shapes"][0].pop("id") + self.assertEqual(len(task_ann_prev.data["shapes"]), len(task_ann.data["shapes"])) + self.assertEqual(task_ann_prev.data["shapes"], task_ann.data["shapes"]) + + def test_api_v1_rewrite_annotation(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + for format_name in self.format_names: + with self.subTest(format=f"{format_name}"): + annotation = self._get_tmp_annotation(task, self.cuboid_example) + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + url = self._generate_url_dump_tasks_annotations(task_id) + file_name = osp.join(test_dir, f"{format_name}.zip") + data = { + "format": format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_name) + self.assertTrue(osp.exists(file_name)) + + self._remove_annotations(task_id) + # rewrite annotation + annotation_copy = copy.deepcopy(annotation) + annotation_copy["shapes"][0]["points"] = [0] * 16 + response = self._put_api_v1_task_id_annotations(task_id, annotation_copy) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + file_name = osp.join(test_dir, f"{format_name}.zip") + url = self._generate_url_upload_tasks_annotations(task_id, format_name) + + with open(file_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + task_ann_prev.data["shapes"][0].pop("id") + task_ann.data["shapes"][0].pop("id") + self.assertEqual(len(task_ann_prev.data["shapes"]), len(task_ann.data["shapes"])) + self.assertEqual(task_ann_prev.data["shapes"], task_ann.data["shapes"]) + + def test_api_v1_dump_and_upload_empty_annotation(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + + for format_name in self.format_names: + with self.subTest(format=f"{format_name}"): + url = self._generate_url_dump_tasks_annotations(task_id) + file_name = osp.join(test_dir, f"{format_name}.zip") + data = { + "format": format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_name) + self.assertTrue(osp.exists(file_name)) + + file_name = osp.join(test_dir, f"{format_name}.zip") + url = self._generate_url_upload_tasks_annotations(task_id, format_name) + + with open(file_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + self.assertEqual(len(task_ann.data["shapes"]), 0) + self.assertEqual(task_ann_prev.data["shapes"], task_ann.data["shapes"]) + + def test_api_v1_dump_and_upload_several_jobs(self): + job_test_cases = ["first", "all"] + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task_many_jobs, task_data) + task_id = task["id"] + annotation = self._get_tmp_annotation(task, self.cuboid_example) + + for format_name, job_test_case in itertools.product(self.format_names, job_test_cases): + with self.subTest(format=f"{format_name}_{job_test_case}"): + jobs = self._get_jobs(task_id) + if job_test_case == "all": + for job in jobs: + response = self._put_api_v1_job_id_annotations(job["id"], annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + else: + response = self._put_api_v1_job_id_annotations(jobs[1]["id"], annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + url = self._generate_url_dump_tasks_annotations(task_id) + file_name = osp.join(test_dir, f"{format_name}.zip") + data = { + "format": format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_name) + + self._remove_annotations(task_id) + + def test_api_v1_upload_annotation_with_attributes(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task_with_attributes, task_data) + task_id = task["id"] + + for format_name in self.format_names: + annotation = self._get_tmp_annotation(task, self.cuboid_example) + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + + with self.subTest(format=f"{format_name}_dump"): + url = self._generate_url_dump_tasks_annotations(task_id) + file_name = osp.join(test_dir, f"{format_name}.zip") + data = { + "format": format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_name) + self.assertTrue(osp.exists(file_name)) + + self._remove_annotations(task_id) + with self.subTest(format=f"{format_name}_upload"): + file_name = osp.join(test_dir, f"{format_name}.zip") + url = self._generate_url_upload_tasks_annotations(task_id, format_name) + + with open(file_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + task_ann_prev.data["shapes"][0].pop("id") + task_ann.data["shapes"][0].pop("id") + self.assertEqual(task_ann_prev.data["shapes"][0]["attributes"], + task_ann.data["shapes"][0]["attributes"]) + + def test_api_v1_export_dataset(self): + with TestDir() as test_dir: + task_data = self.copy_pcd_file_and_get_task_data(test_dir) + task = self._create_task(self.task, task_data) + task_id = task["id"] + + for format_name in self.format_names: + annotation = self._get_tmp_annotation(task, self.cuboid_example) + response = self._put_api_v1_task_id_annotations(task_id, annotation) + self.assertEqual(response.status_code, status.HTTP_200_OK) + task_ann_prev = TaskAnnotation(task_id) + task_ann_prev.init_from_db() + + for user, edata in list(self.expected_dump_upload.items()): + with self.subTest(format=f"{format_name}_{edata['name']}_export"): + url = self._generate_url_dump_dataset(task_id) + file_name = osp.join(test_dir, f"{format_name}_{edata['name']}.zip") + + data = { + "format": format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = io.BytesIO(b"".join(response.streaming_content)) + with open(file_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_name), edata['file_exists']) + self._check_dump_content(content, task_ann_prev.data, format_name,related_files=False) + diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index abc91108..e46228ef 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -1,5 +1,5 @@ -# Copyright (C) 2018-2019 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -30,7 +30,7 @@ schema_view = get_schema_view( # drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and # send requests with ?format=openapi suffix instead of ?scheme=openapi. -# We map the required paramater explicitly and add it into query arguments +# We map the required parameter explicitly and add it into query arguments # on the server side. def wrap_swagger(view): @login_required @@ -55,6 +55,7 @@ router.register('issues', views.IssueViewSet) router.register('comments', views.CommentViewSet) router.register('restrictions', RestrictionsViewSet, basename='restrictions') router.register('predict', PredictView, basename='predict') +router.register('cloudstorages', views.CloudStorageViewSet) urlpatterns = [ # Entry point for a client diff --git a/cvat/apps/engine/utils.py b/cvat/apps/engine/utils.py index f3744073..87b7b856 100644 --- a/cvat/apps/engine/utils.py +++ b/cvat/apps/engine/utils.py @@ -12,6 +12,7 @@ import traceback import subprocess import os from av import VideoFrame +from PIL import Image from django.core.exceptions import ValidationError @@ -95,4 +96,6 @@ def rotate_image(image, angle): def md5_hash(frame): if isinstance(frame, VideoFrame): frame = frame.to_image() + elif isinstance(frame, str): + frame = Image.open(frame, 'r') return hashlib.md5(frame.tobytes()).hexdigest() # nosec \ No newline at end of file diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 89751952..3adaa017 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1,30 +1,33 @@ -# Copyright (C) 2018-2020 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT import io +import json import os import os.path as osp import shutil import traceback +import uuid from datetime import datetime from distutils.util import strtobool -from tempfile import mkstemp +from tempfile import mkstemp, TemporaryDirectory import cv2 +from django.db.models.query import Prefetch import django_rq from django.apps import apps from django.conf import settings from django.contrib.auth.models import User from django.db import IntegrityError -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseNotFound, HttpResponseBadRequest from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.decorators import method_decorator from django_filters import rest_framework as filters from django_filters.rest_framework import DjangoFilterBackend from drf_yasg import openapi -from drf_yasg.inspectors import CoreAPICompatInspector, NotHandled +from drf_yasg.inspectors import CoreAPICompatInspector, NotHandled, FieldInspector from drf_yasg.utils import swagger_auto_schema from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action @@ -37,26 +40,30 @@ from sendfile import sendfile import cvat.apps.dataset_manager as dm import cvat.apps.dataset_manager.views # pylint: disable=unused-import from cvat.apps.authentication import auth +from cvat.apps.engine.cloud_provider import get_cloud_storage_instance, Credentials from cvat.apps.dataset_manager.bindings import CvatImportError from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer from cvat.apps.engine.frame_provider import FrameProvider from cvat.apps.engine.models import ( Job, StatusChoice, Task, Project, Review, Issue, - Comment, StorageMethodChoice, ReviewStatus, StorageChoice, DimensionType, Image + Comment, StorageMethodChoice, ReviewStatus, StorageChoice, Image, + CredentialsTypeChoice, CloudProviderChoice ) +from cvat.apps.engine.models import CloudStorage as CloudStorageModel from cvat.apps.engine.serializers import ( AboutSerializer, AnnotationFileSerializer, BasicUserSerializer, DataMetaSerializer, DataSerializer, ExceptionSerializer, FileInfoSerializer, JobSerializer, LabeledDataSerializer, LogEventSerializer, ProjectSerializer, ProjectSearchSerializer, ProjectWithoutTaskSerializer, RqStatusSerializer, TaskSerializer, UserSerializer, PluginsSerializer, ReviewSerializer, - CombinedReviewSerializer, IssueSerializer, CombinedIssueSerializer, CommentSerializer -) + CombinedReviewSerializer, IssueSerializer, CombinedIssueSerializer, CommentSerializer, + CloudStorageSerializer, BaseCloudStorageSerializer, TaskFileSerializer,) +from utils.dataset_manifest import ImageManifestManager from cvat.apps.engine.utils import av_scan_paths +from cvat.apps.engine.backup import import_task from . import models, task from .log import clogger, slogger - class ServerViewSet(viewsets.ViewSet): serializer_class = None @@ -360,20 +367,134 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): return [perm() for perm in permissions] - def perform_create(self, serializer): - def validate_task_limit(owner): - admin_perm = auth.AdminRolePermission() - is_admin = admin_perm.has_permission(self.request, self) - if not is_admin and settings.RESTRICTIONS['task_limit'] is not None and \ - Task.objects.filter(owner=owner).count() >= settings.RESTRICTIONS['task_limit']: - raise serializers.ValidationError('The user has the maximum number of tasks') + def _validate_task_limit(self, owner): + admin_perm = auth.AdminRolePermission() + is_admin = admin_perm.has_permission(self.request, self) + if not is_admin and settings.RESTRICTIONS['task_limit'] is not None and \ + Task.objects.filter(owner=owner).count() >= settings.RESTRICTIONS['task_limit']: + raise serializers.ValidationError('The user has the maximum number of tasks') + + def create(self, request): + action = self.request.query_params.get('action', None) + if action is None: + return super().create(request) + elif action == 'import': + self._validate_task_limit(owner=self.request.user) + if 'rq_id' in request.data: + rq_id = request.data['rq_id'] + else: + rq_id = "{}@/api/v1/tasks/{}/import".format(request.user, uuid.uuid4()) + + queue = django_rq.get_queue("default") + rq_job = queue.fetch_job(rq_id) + + if not rq_job: + serializer = TaskFileSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + task_file = serializer.validated_data['task_file'] + fd, filename = mkstemp(prefix='cvat_') + with open(filename, 'wb+') as f: + for chunk in task_file.chunks(): + f.write(chunk) + rq_job = queue.enqueue_call( + func=import_task, + args=(filename, request.user.id), + job_id=rq_id, + meta={ + 'tmp_file': filename, + 'tmp_file_descriptor': fd, + }, + ) + + else: + if rq_job.is_finished: + task_id = rq_job.return_value + os.close(rq_job.meta['tmp_file_descriptor']) + os.remove(rq_job.meta['tmp_file']) + rq_job.delete() + return Response({'id': task_id}, status=status.HTTP_201_CREATED) + elif rq_job.is_failed: + os.close(rq_job.meta['tmp_file_descriptor']) + os.remove(rq_job.meta['tmp_file']) + exc_info = str(rq_job.exc_info) + rq_job.delete() + + # RQ adds a prefix with exception class name + import_error_prefix = '{}.{}'.format( + CvatImportError.__module__, CvatImportError.__name__) + if exc_info.startswith(import_error_prefix): + exc_info = exc_info.replace(import_error_prefix + ': ', '') + return Response(data=exc_info, + status=status.HTTP_400_BAD_REQUEST) + else: + return Response(data=exc_info, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return Response({'rq_id': rq_id}, status=status.HTTP_202_ACCEPTED) + else: + raise serializers.ValidationError( + "Unexpected action specified for the request") + + def retrieve(self, request, pk=None): + db_task = self.get_object() # force to call check_object_permissions + action = self.request.query_params.get('action', None) + if action is None: + return super().retrieve(request, pk) + elif action in ('export', 'download'): + queue = django_rq.get_queue("default") + rq_id = "/api/v1/tasks/{}/export".format(pk) + + rq_job = queue.fetch_job(rq_id) + if rq_job: + last_task_update_time = timezone.localtime(db_task.updated_date) + request_time = rq_job.meta.get('request_time', None) + if request_time is None or request_time < last_task_update_time: + rq_job.cancel() + rq_job.delete() + else: + if rq_job.is_finished: + file_path = rq_job.return_value + if action == "download" and osp.exists(file_path): + rq_job.delete() + + timestamp = datetime.strftime(last_task_update_time, + "%Y_%m_%d_%H_%M_%S") + filename = "task_{}_backup_{}{}".format( + db_task.name, timestamp, + osp.splitext(file_path)[1]) + return sendfile(request, file_path, attachment=True, + attachment_filename=filename.lower()) + else: + if osp.exists(file_path): + return Response(status=status.HTTP_201_CREATED) + elif rq_job.is_failed: + exc_info = str(rq_job.exc_info) + rq_job.delete() + return Response(exc_info, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + return Response(status=status.HTTP_202_ACCEPTED) + ttl = dm.views.CACHE_TTL.total_seconds() + queue.enqueue_call( + func=dm.views.backup_task, + args=(pk, 'task_dump.zip'), + job_id=rq_id, + meta={ 'request_time': timezone.localtime() }, + result_ttl=ttl, failure_ttl=ttl) + return Response(status=status.HTTP_202_ACCEPTED) + + else: + raise serializers.ValidationError( + "Unexpected action specified for the request") + + def perform_create(self, serializer): owner = self.request.data.get('owner', None) if owner: - validate_task_limit(owner) + self._validate_task_limit(owner) serializer.save() else: - validate_task_limit(self.request.user) + self._validate_task_limit(self.request.user) serializer.save(owner=self.request.user) def perform_destroy(self, instance): @@ -412,8 +533,11 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): ) @action(detail=True, methods=['POST', 'GET']) def data(self, request, pk): + db_task = self.get_object() # call check_object_permissions as well if request.method == 'POST': - db_task = self.get_object() # call check_object_permissions as well + if db_task.data: + return Response(data='Adding more data is not supported', + status=status.HTTP_400_BAD_REQUEST) serializer = DataSerializer(data=request.data) serializer.is_valid(raise_exception=True) db_data = serializer.save() @@ -426,9 +550,12 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): if data['use_cache']: db_task.data.storage_method = StorageMethodChoice.CACHE db_task.data.save(update_fields=['storage_method']) - if data['server_files'] and data.get('copy_data') == False: + if data['server_files'] and not data.get('copy_data'): db_task.data.storage = StorageChoice.SHARE db_task.data.save(update_fields=['storage']) + if db_data.cloud_storage: + db_task.data.storage = StorageChoice.CLOUD_STORAGE + db_task.data.save(update_fields=['storage']) # if the value of stop_frame is 0, then inside the function we cannot know # the value specified by the user or it's default value from the database if 'stop_frame' not in serializer.validated_data: @@ -452,7 +579,6 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): elif data_quality not in possible_quality_values: raise ValidationError(detail='Wrong quality value') - db_task = self.get_object() db_data = db_task.data if not db_data: raise NotFound(detail='Cannot find requested data for the task') @@ -487,21 +613,17 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): return sendfile(request, frame_provider.get_preview()) elif data_type == 'context_image': - if db_task.dimension == DimensionType.DIM_3D: - data_id = int(data_id) - image = Image.objects.get(data_id=db_task.data_id, frame=data_id) - for i in image.related_files.all(): - path = os.path.realpath(str(i.path)) - image = cv2.imread(path) - success, result = cv2.imencode('.JPEG', image) - if not success: - raise Exception("Failed to encode image to '%s' format" % (".jpeg")) - return HttpResponse(io.BytesIO(result.tobytes()), content_type="image/jpeg") - return Response(data='No context image related to the frame', - status=status.HTTP_404_NOT_FOUND) - else: - return Response(data='Only 3D tasks support context images', - status=status.HTTP_400_BAD_REQUEST) + data_id = int(data_id) + image = Image.objects.get(data_id=db_data.id, frame=data_id) + for i in image.related_files.all(): + path = os.path.realpath(str(i.path)) + image = cv2.imread(path) + success, result = cv2.imencode('.JPEG', image) + if not success: + raise Exception('Failed to encode image to ".jpeg" format') + return HttpResponse(io.BytesIO(result.tobytes()), content_type='image/jpeg') + return Response(data='No context image related to the frame', + status=status.HTTP_404_NOT_FOUND) else: return Response(data='unknown data type {}.'.format(data_type), status=status.HTTP_400_BAD_REQUEST) except APIException as e: @@ -636,17 +758,22 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): @action(detail=True, methods=['GET'], serializer_class=DataMetaSerializer, url_path='data/meta') def data_info(request, pk): - db_task = models.Task.objects.prefetch_related('data__images').select_related('data__video').get(pk=pk) + db_task = models.Task.objects.prefetch_related( + Prefetch('data', queryset=models.Data.objects.select_related('video').prefetch_related( + Prefetch('images', queryset=models.Image.objects.prefetch_related('related_files').order_by('frame')) + )) + ).get(pk=pk) if hasattr(db_task.data, 'video'): media = [db_task.data.video] else: - media = list(db_task.data.images.order_by('frame')) + media = list(db_task.data.images.all()) frame_meta = [{ 'width': item.width, 'height': item.height, 'name': item.path, + 'has_related_context': hasattr(item, 'related_files') and item.related_files.exists() } for item in media] db_data = db_task.data @@ -924,11 +1051,12 @@ class CommentViewSet(viewsets.GenericViewSet, class UserFilter(filters.FilterSet): class Meta: model = User - fields = ("id",) + fields = ("id", "is_active") @method_decorator(name='list', decorator=swagger_auto_schema( manual_parameters=[ openapi.Parameter('id',openapi.IN_QUERY,description="A unique number value identifying this user",type=openapi.TYPE_NUMBER), + openapi.Parameter('is_active',openapi.IN_QUERY,description="Returns only active users",type=openapi.TYPE_BOOLEAN), ], operation_summary='Method provides a paginated list of users registered on the server')) @method_decorator(name='retrieve', decorator=swagger_auto_schema( @@ -977,6 +1105,201 @@ class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, serializer = serializer_class(request.user, context={ "request": request }) return Response(serializer.data) +class RedefineDescriptionField(FieldInspector): + # pylint: disable=no-self-use + def process_result(self, result, method_name, obj, **kwargs): + if isinstance(result, openapi.Schema): + if hasattr(result, 'title') and result.title == 'Specific attributes': + result.description = 'structure like key1=value1&key2=value2\n' \ + 'supported: range=aws_range' + return result + +@method_decorator( + name='retrieve', + decorator=swagger_auto_schema( + operation_summary='Method returns details of a specific cloud storage', + responses={ + '200': openapi.Response(description='A details of a storage'), + }, + tags=['cloud storages'] + ) +) +@method_decorator(name='list', decorator=swagger_auto_schema( + operation_summary='Returns a paginated list of storages according to query parameters', + manual_parameters=[ + openapi.Parameter('provider_type', openapi.IN_QUERY, description="A supported provider of cloud storages", + type=openapi.TYPE_STRING, enum=CloudProviderChoice.list()), + openapi.Parameter('display_name', openapi.IN_QUERY, description="A display name of storage", type=openapi.TYPE_STRING), + openapi.Parameter('resource', openapi.IN_QUERY, description="A name of bucket or container", type=openapi.TYPE_STRING), + openapi.Parameter('owner', openapi.IN_QUERY, description="A resource owner", type=openapi.TYPE_STRING), + openapi.Parameter('credentials_type', openapi.IN_QUERY, description="A type of a granting access", type=openapi.TYPE_STRING, enum=CredentialsTypeChoice.list()), + ], + responses={'200': BaseCloudStorageSerializer(many=True)}, + tags=['cloud storages'], + field_inspectors=[RedefineDescriptionField] + ) +) +@method_decorator(name='destroy', decorator=swagger_auto_schema( + operation_summary='Method deletes a specific cloud storage', + tags=['cloud storages'] + ) +) +@method_decorator(name='partial_update', decorator=swagger_auto_schema( + operation_summary='Methods does a partial update of chosen fields in a cloud storage instance', + tags=['cloud storages'], + field_inspectors=[RedefineDescriptionField] + ) +) +class CloudStorageViewSet(auth.CloudStorageGetQuerySetMixin, viewsets.ModelViewSet): + http_method_names = ['get', 'post', 'patch', 'delete'] + queryset = CloudStorageModel.objects.all().prefetch_related('data').order_by('-id') + search_fields = ('provider_type', 'display_name', 'resource', 'owner__username') + filterset_fields = ['provider_type', 'display_name', 'resource', 'credentials_type'] + + def get_permissions(self): + http_method = self.request.method + permissions = [IsAuthenticated] + + if http_method in SAFE_METHODS: + permissions.append(auth.CloudStorageAccessPermission) + elif http_method in ("POST", "PATCH", "DELETE"): + permissions.append(auth.CloudStorageChangePermission) + else: + permissions.append(auth.AdminRolePermission) + return [perm() for perm in permissions] + + def get_serializer_class(self): + if self.request.method in ("POST", "PATCH"): + return CloudStorageSerializer + else: + return BaseCloudStorageSerializer + + def get_queryset(self): + queryset = super().get_queryset() + provider_type = self.request.query_params.get('provider_type', None) + if provider_type: + if provider_type in CloudProviderChoice.list(): + return queryset.filter(provider_type=provider_type) + raise ValidationError('Unsupported type of cloud provider') + return queryset + + def perform_create(self, serializer): + # check that instance of cloud storage exists + provider_type = serializer.validated_data.get('provider_type') + credentials = Credentials( + session_token=serializer.validated_data.get('session_token', ''), + account_name=serializer.validated_data.get('account_name', ''), + key=serializer.validated_data.get('key', ''), + secret_key=serializer.validated_data.get('secret_key', '') + ) + details = { + 'resource': serializer.validated_data.get('resource'), + 'credentials': credentials, + 'specific_attributes': { + item.split('=')[0].strip(): item.split('=')[1].strip() + for item in serializer.validated_data.get('specific_attributes').split('&') + } if len(serializer.validated_data.get('specific_attributes', '')) + else dict() + } + storage = get_cloud_storage_instance(cloud_provider=provider_type, **details) + try: + storage.exists() + except Exception as ex: + message = str(ex) + slogger.glob.error(message) + raise + + owner = self.request.data.get('owner') + if owner: + serializer.save() + else: + serializer.save(owner=self.request.user) + + def perform_destroy(self, instance): + cloud_storage_dirname = instance.get_storage_dirname() + super().perform_destroy(instance) + shutil.rmtree(cloud_storage_dirname, ignore_errors=True) + + @method_decorator(name='create', decorator=swagger_auto_schema( + operation_summary='Method creates a cloud storage with a specified characteristics', + responses={ + '201': openapi.Response(description='A storage has beed created') + }, + tags=['cloud storages'], + field_inspectors=[RedefineDescriptionField], + ) + ) + def create(self, request, *args, **kwargs): + try: + response = super().create(request, *args, **kwargs) + except IntegrityError: + response = HttpResponseBadRequest('Same storage already exists') + except ValidationError as exceptions: + msg_body = "" + for ex in exceptions.args: + for field, ex_msg in ex.items(): + msg_body += ": ".join([field, str(ex_msg[0])]) + msg_body += '\n' + return HttpResponseBadRequest(msg_body) + except APIException as ex: + return Response(data=ex.get_full_details(), status=ex.status_code) + except Exception as ex: + response = HttpResponseBadRequest(str(ex)) + return response + + @swagger_auto_schema( + method='get', + operation_summary='Method returns a mapped names of an available files from a storage and a manifest content', + manual_parameters=[ + openapi.Parameter('manifest_path', openapi.IN_QUERY, + description="Path to the manifest file in a cloud storage", + type=openapi.TYPE_STRING) + ], + responses={ + '200': openapi.Response(description='Mapped names of an available files from a storage and a manifest content'), + }, + tags=['cloud storages'] + ) + @action(detail=True, methods=['GET'], url_path='content') + def content(self, request, pk): + try: + db_storage = CloudStorageModel.objects.get(pk=pk) + credentials = Credentials() + credentials.convert_from_db({ + 'type': db_storage.credentials_type, + 'value': db_storage.credentials, + }) + details = { + 'resource': db_storage.resource, + 'credentials': credentials, + 'specific_attributes': db_storage.get_specific_attributes() + } + storage = get_cloud_storage_instance(cloud_provider=db_storage.provider_type, **details) + storage.initialize_content() + storage_files = storage.content + + manifest_path = request.query_params.get('manifest_path', 'manifest.jsonl') + with TemporaryDirectory(suffix='manifest', prefix='cvat') as tmp_dir: + tmp_manifest_path = os.path.join(tmp_dir, 'manifest.jsonl') + storage.download_file(manifest_path, tmp_manifest_path) + manifest = ImageManifestManager(tmp_manifest_path) + manifest.init_index() + manifest_files = manifest.data + content = {f:[] for f in set(storage_files) | set(manifest_files)} + for key, _ in content.items(): + if key in storage_files: content[key].append('s') # storage + if key in manifest_files: content[key].append('m') # manifest + + data = json.dumps(content) + return Response(data=data, content_type="aplication/json") + + except CloudStorageModel.DoesNotExist: + message = f"Storage {pk} does not exist" + slogger.glob.error(message) + return HttpResponseNotFound(message) + except Exception as ex: + return HttpResponseBadRequest(str(ex)) + def rq_handler(job, exc_type, exc_value, tb): job.exc_info = "".join( traceback.format_exception_only(exc_type, exc_value)) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 079d5789..53bb2c14 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -111,6 +111,7 @@ class LambdaFunction: # display name for the function self.name = meta_anno.get('name', self.id) self.min_pos_points = int(meta_anno.get('min_pos_points', 1)) + self.min_neg_points = int(meta_anno.get('min_neg_points', -1)) self.startswith_box = bool(meta_anno.get('startswith_box', False)) self.gateway = gateway @@ -127,6 +128,7 @@ class LambdaFunction: if self.kind is LambdaType.INTERACTOR: response.update({ 'min_pos_points': self.min_pos_points, + 'min_neg_points': self.min_neg_points, 'startswith_box': self.startswith_box }) @@ -166,8 +168,9 @@ class LambdaFunction: elif self.kind == LambdaType.INTERACTOR: payload.update({ "image": self._get_image(db_task, data["frame"], quality), - "pos_points": data["pos_points"], - "neg_points": data["neg_points"] + "pos_points": data["pos_points"][2:] if self.startswith_box else data["pos_points"], + "neg_points": data["neg_points"], + "obj_bbox": data["pos_points"][0:2] if self.startswith_box else None }) elif self.kind == LambdaType.REID: payload.update({ diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index c9037583..2ec26515 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -1,12 +1,12 @@ click==7.1.2 -Django==3.1.10 +Django==3.1.13 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==8.2.0 +Pillow==8.3.0 numpy==1.19.5 python-ldap==3.3.1 pytz==2020.1 @@ -45,9 +45,11 @@ tensorflow==2.4.1 # Optional requirement of Datumaro patool==1.12 diskcache==5.0.2 open3d==0.11.2 +boto3==1.17.61 +azure-storage-blob==12.8.1 # --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.8 --no-binary=datumaro --no-binary=pycocotools +datumaro==0.1.10.1 --no-binary=datumaro --no-binary=pycocotools diff --git a/cvat/settings/base.py b/cvat/settings/base.py index bfcd8d65..3da6c068 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -56,8 +56,8 @@ def generate_ssh_keys(): IGNORE_FILES = ('README.md', 'ssh.pid') keys_to_add = [entry.name for entry in os.scandir(ssh_dir) if entry.name not in IGNORE_FILES] keys_to_add = ' '.join(os.path.join(ssh_dir, f) for f in keys_to_add) - subprocess.run(['ssh-add {}'.format(keys_to_add)], - shell = True, + subprocess.run(['ssh-add {}'.format(keys_to_add)], # nosec + shell=True, stderr = subprocess.PIPE, # lets set the timeout if ssh-add requires a input passphrase for key # otherwise the process will be freezed @@ -68,14 +68,14 @@ def generate_ssh_keys(): fcntl.flock(pid, fcntl.LOCK_EX) try: add_ssh_keys() - keys = subprocess.run(['ssh-add -l'], shell = True, + keys = subprocess.run(['ssh-add', '-l'], # nosec stdout = subprocess.PIPE).stdout.decode('utf-8').split('\n') if 'has no identities' in keys[0]: print('SSH keys were not found') volume_keys = os.listdir(keys_dir) if not ('id_rsa' in volume_keys and 'id_rsa.pub' in volume_keys): print('New pair of keys are being generated') - subprocess.run(['ssh-keygen -b 4096 -t rsa -f {}/id_rsa -q -N ""'.format(ssh_dir)], shell = True) + subprocess.run(['ssh-keygen -b 4096 -t rsa -f {}/id_rsa -q -N ""'.format(ssh_dir)], shell=True) # nosec shutil.copyfile('{}/id_rsa'.format(ssh_dir), '{}/id_rsa'.format(keys_dir)) shutil.copymode('{}/id_rsa'.format(ssh_dir), '{}/id_rsa'.format(keys_dir)) shutil.copyfile('{}/id_rsa.pub'.format(ssh_dir), '{}/id_rsa.pub'.format(keys_dir)) @@ -86,15 +86,15 @@ def generate_ssh_keys(): shutil.copymode('{}/id_rsa'.format(keys_dir), '{}/id_rsa'.format(ssh_dir)) shutil.copyfile('{}/id_rsa.pub'.format(keys_dir), '{}/id_rsa.pub'.format(ssh_dir)) shutil.copymode('{}/id_rsa.pub'.format(keys_dir), '{}/id_rsa.pub'.format(ssh_dir)) - subprocess.run(['ssh-add', '{}/id_rsa'.format(ssh_dir)], shell = True) + subprocess.run(['ssh-add', '{}/id_rsa'.format(ssh_dir)]) # nosec finally: fcntl.flock(pid, fcntl.LOCK_UN) try: if os.getenv("SSH_AUTH_SOCK", None): generate_ssh_keys() -except Exception: - pass +except Exception as ex: + print(str(ex)) INSTALLED_APPS = [ 'django.contrib.admin', @@ -104,7 +104,6 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'cvat.apps.authentication', - 'cvat.apps.documentation', 'cvat.apps.dataset_manager', 'cvat.apps.engine', 'cvat.apps.dataset_repo', @@ -344,7 +343,7 @@ DATA_ROOT = os.path.join(BASE_DIR, 'data') LOGSTASH_DB = os.path.join(DATA_ROOT,'logstash.db') os.makedirs(DATA_ROOT, exist_ok=True) if not os.path.exists(LOGSTASH_DB): - os.mknod(LOGSTASH_DB) + open(LOGSTASH_DB, 'w').close() MEDIA_DATA_ROOT = os.path.join(DATA_ROOT, 'data') os.makedirs(MEDIA_DATA_ROOT, exist_ok=True) @@ -370,6 +369,9 @@ os.makedirs(LOGS_ROOT, exist_ok=True) MIGRATIONS_LOGS_ROOT = os.path.join(LOGS_ROOT, 'migrations') os.makedirs(MIGRATIONS_LOGS_ROOT, exist_ok=True) +CLOUD_STORAGE_ROOT = os.path.join(DATA_ROOT, 'storages') +os.makedirs(CLOUD_STORAGE_ROOT, exist_ok=True) + LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -452,7 +454,7 @@ RESTRICTIONS = { # this setting limits the number of projects for the user 'project_limit': None, - # this setting reduse task visibility to owner and assignee only + # this setting reduces task visibility to owner and assignee only 'reduce_task_visibility': False, # allow access to analytics component to users with the following roles diff --git a/cvat/settings/testing.py b/cvat/settings/testing.py index e79b0f39..992ba866 100644 --- a/cvat/settings/testing.py +++ b/cvat/settings/testing.py @@ -5,12 +5,13 @@ from .development import * import tempfile -_temp_dir = tempfile.TemporaryDirectory(suffix="cvat") +_temp_dir = tempfile.TemporaryDirectory(dir=BASE_DIR, suffix="cvat") +BASE_DIR = _temp_dir.name -DATA_ROOT = os.path.join(_temp_dir.name, 'data') +DATA_ROOT = os.path.join(BASE_DIR, 'data') os.makedirs(DATA_ROOT, exist_ok=True) -SHARE_ROOT = os.path.join(_temp_dir.name, 'share') +SHARE_ROOT = os.path.join(BASE_DIR, 'share') os.makedirs(SHARE_ROOT, exist_ok=True) MEDIA_DATA_ROOT = os.path.join(DATA_ROOT, 'data') @@ -30,7 +31,7 @@ os.makedirs(CACHE_ROOT, exist_ok=True) # To avoid ERROR django.security.SuspiciousFileOperation: # The joined path (...) is located outside of the base path component -MEDIA_ROOT = _temp_dir.name +MEDIA_ROOT = BASE_DIR # Suppress all logs by default for logger in LOGGING["loggers"].values(): diff --git a/cvat/urls.py b/cvat/urls.py index 1fa4cb50..54e18d2c 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -26,7 +26,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('', include('cvat.apps.engine.urls')), path('django-rq/', include('django_rq.urls')), - path('documentation/', include('cvat.apps.documentation.urls')), ] if apps.is_installed('cvat.apps.dataset_repo'): diff --git a/cvat_proxy/conf.d/cvat.conf.template b/cvat_proxy/conf.d/cvat.conf.template deleted file mode 100644 index 0c7ab685..00000000 --- a/cvat_proxy/conf.d/cvat.conf.template +++ /dev/null @@ -1,22 +0,0 @@ -server { - listen 80; - server_name _ default; - return 404; -} - -server { - listen 80; - server_name ${CVAT_HOST}; - - proxy_pass_header X-CSRFToken; - proxy_set_header Host $http_host; - proxy_pass_header Set-Cookie; - - location ~* /api/.*|git/.*|opencv/.*|analytics/.*|static/.*|admin(?:/(.*))?.*|documentation/.*|django-rq(?:/(.*))? { - proxy_pass http://cvat:8080; - } - - location / { - proxy_pass http://cvat_ui; - } -} diff --git a/cvat_proxy/nginx.conf b/cvat_proxy/nginx.conf deleted file mode 100644 index 105f76b0..00000000 --- a/cvat_proxy/nginx.conf +++ /dev/null @@ -1,18 +0,0 @@ -worker_processes 2; - - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - # For long domain names (e.g. AWS hosts) - server_names_hash_bucket_size 128; - - include /etc/nginx/conf.d/*.conf; - client_max_body_size 0; -} diff --git a/docker-compose.https.yml b/docker-compose.https.yml new file mode 100644 index 00000000..efdcf692 --- /dev/null +++ b/docker-compose.https.yml @@ -0,0 +1,41 @@ +# Copyright (C) 2018-2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +version: '3.3' + +services: + cvat: + labels: + - traefik.http.routers.cvat.entrypoints=websecure + - traefik.http.routers.cvat.tls.certresolver=lets-encrypt + + cvat_ui: + labels: + - traefik.http.routers.cvat-ui.entrypoints=websecure + - traefik.http.routers.cvat-ui.tls.certresolver=lets-encrypt + + traefik: + image: traefik:v2.4 + container_name: traefik + command: + - "--providers.docker.exposedByDefault=false" + - "--providers.docker.network=cvat" + - "--entryPoints.web.address=:80" + - "--entryPoints.web.http.redirections.entryPoint.to=websecure" + - "--entryPoints.web.http.redirections.entryPoint.scheme=https" + - "--entryPoints.websecure.address=:443" + - "--certificatesResolvers.lets-encrypt.acme.email=${ACME_EMAIL:?Please set the ACME_EMAIL env variable}" + - "--certificatesResolvers.lets-encrypt.acme.tlsChallenge=true" + - "--certificatesResolvers.lets-encrypt.acme.storage=/letsencrypt/acme.json" + # Uncomment to get Traefik dashboard + # - "--entryPoints.dashboard.address=:8090" + # - "--api.dashboard=true" + ports: + - 80:80 + - 443:443 + volumes: + - cvat_letsencrypt:/letsencrypt + +volumes: + cvat_letsencrypt: diff --git a/docker-compose.yml b/docker-compose.yml index ce096044..2ae57d99 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,13 @@ -# # Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT -# + version: '3.3' services: cvat_db: container_name: cvat_db image: postgres:10-alpine - networks: - default: - aliases: - - db restart: always environment: POSTGRES_USER: root @@ -20,15 +15,15 @@ services: POSTGRES_HOST_AUTH_METHOD: trust volumes: - cvat_db:/var/lib/postgresql/data + networks: + - cvat cvat_redis: container_name: cvat_redis image: redis:4.0-alpine - networks: - default: - aliases: - - redis restart: always + networks: + - cvat cvat: container_name: cvat @@ -43,47 +38,61 @@ services: CVAT_REDIS_HOST: 'cvat_redis' CVAT_POSTGRES_HOST: 'cvat_db' ADAPTIVE_AUTO_ANNOTATION: 'false' + labels: + - traefik.enable=true + - traefik.http.services.cvat.loadbalancer.server.port=8080 + - traefik.http.routers.cvat.rule=Host(`${CVAT_HOST:-localhost}`) && + PathPrefix(`/api/`, `/git/`, `/opencv/`, `/analytics/`, `/static/`, `/admin`, `/documentation/`, `/django-rq`) + - traefik.http.routers.cvat.entrypoints=web volumes: - cvat_data:/home/django/data - cvat_keys:/home/django/keys - cvat_logs:/home/django/logs + networks: + - cvat cvat_ui: container_name: cvat_ui image: openvino/cvat_ui restart: always - networks: - default: - aliases: - - ui depends_on: - cvat - - cvat_proxy: - container_name: cvat_proxy - image: nginx:stable-alpine - restart: always - depends_on: + labels: + - traefik.enable=true + - traefik.http.services.cvat-ui.loadbalancer.server.port=80 + - traefik.http.routers.cvat-ui.rule=Host(`${CVAT_HOST:-localhost}`) + - traefik.http.routers.cvat-ui.entrypoints=web + networks: - cvat - - cvat_ui - environment: - CVAT_HOST: localhost + + traefik: + image: traefik:v2.4 + container_name: traefik + command: + - "--providers.docker.exposedByDefault=false" + - "--providers.docker.network=cvat" + - "--entryPoints.web.address=:8080" + # Uncomment to get Traefik dashboard + # - "--entryPoints.dashboard.address=:8090" + # - "--api.dashboard=true" + # labels: + # - traefik.enable=true + # - traefik.http.routers.dashboard.entrypoints=dashboard + # - traefik.http.routers.dashboard.service=api@internal + # - traefik.http.routers.dashboard.rule=Host(`${CVAT_HOST:-localhost}`) ports: - - '8080:80' + - 8080:8080 + - 8090:8090 volumes: - - ./cvat_proxy/nginx.conf:/etc/nginx/nginx.conf:ro - - ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro - command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" - -networks: - default: - ipam: - driver: default - config: - - subnet: 172.28.0.0/24 + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - cvat volumes: cvat_db: cvat_data: cvat_keys: cvat_logs: + +networks: + cvat: \ No newline at end of file diff --git a/helm-chart/README.md b/helm-chart/README.md index 4b74ac73..b135b058 100644 --- a/helm-chart/README.md +++ b/helm-chart/README.md @@ -8,8 +8,8 @@ helm repo update helm dependency update ``` -4. (Optional) Install ingress of your choice (for example: https://github.com/kubernetes/ingress-nginx) -5. (Optional) Create certificates for https (for example: https://github.com/jetstack/cert-manager/ ) +4. (Optional) Install ingress of your choice (for example: ) +5. (Optional) Create certificates for https (for example: ) 6. (Optional) Create values.override.yaml and override there parameters you want 7. Change postgresql password as described below 8. Add ingress to values.override.yaml(example also below) @@ -43,7 +43,7 @@ postgresql: ## How to describe ingress: Just set `ingress.enabled:` to `true`, then copy example, uncomment it and change values there ## How to understand what diff will be inflicted by 'helm upgrade'? -You can use https://github.com/databus23/helm-diff#install for that +You can use for that ## I want to use my own postgresql/redis with your chart. Just set `postgresql.enabled` or `redis.enabled` to `false`, as described below. Then - put your instance params to "external" field @@ -53,6 +53,6 @@ Then reference it in helm update/install command using `-f` flag ## Why you used external charts to provide redis and postgres? Because they definitely know what they do better then we are, so we are getting more quality and less support ## What is kubernetes and how it is working? -See https://kubernetes.io/ +See ## What is helm and how it is working? -See https://helm.sh/ +See diff --git a/package-lock.json b/package-lock.json index 5323abff..32939de3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -430,6 +430,15 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/mdast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", + "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -573,6 +582,16 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -990,6 +1009,12 @@ "tweetnacl": "^0.14.3" } }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1199,6 +1224,22 @@ } } }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1397,6 +1438,18 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "confusing-browser-globals": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", @@ -2108,6 +2161,30 @@ "reusify": "^1.0.4" } }, + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dev": true, + "requires": { + "format": "^0.2.0" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -2251,6 +2328,12 @@ "mime-types": "^2.1.12" } }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=", + "dev": true + }, "fromentries": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz", @@ -2263,6 +2346,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2730,6 +2820,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-buffer": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", @@ -2754,6 +2853,12 @@ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "dev": true }, + "is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha1-3pu1snhzigWgsJpX4ftNSjQan2s=", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3092,6 +3197,15 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3141,6 +3255,53 @@ "type-check": "~0.4.0" } }, + "libnpmconfig": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", + "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -3197,6 +3358,24 @@ } } }, + "load-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-3.0.0.tgz", + "integrity": "sha512-od7eKCCZ62ITvFf8nHHrIiYmgOHb4xVNDRDqxBWSaao5FZyyZVX8OmRCbwjDGPrSrgIulwPNyBsWCGnhiDC0oQ==", + "dev": true, + "requires": { + "libnpmconfig": "^1.0.0", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3396,6 +3575,12 @@ "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", "dev": true }, + "markdown-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==", + "dev": true + }, "markdown-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", @@ -3426,12 +3611,135 @@ "unist-util-visit": "^2.0.0" } }, + "mdast-util-find-and-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", + "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "dev": true, + "requires": { + "escape-string-regexp": "^4.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, + "mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "dependencies": { + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + } + } + }, + "mdast-util-frontmatter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-0.2.0.tgz", + "integrity": "sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==", + "dev": true, + "requires": { + "micromark-extension-frontmatter": "^0.2.0" + } + }, + "mdast-util-gfm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", + "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "dev": true, + "requires": { + "mdast-util-gfm-autolink-literal": "^0.1.0", + "mdast-util-gfm-strikethrough": "^0.2.0", + "mdast-util-gfm-table": "^0.1.0", + "mdast-util-gfm-task-list-item": "^0.1.0", + "mdast-util-to-markdown": "^0.6.1" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", + "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "dev": true, + "requires": { + "ccount": "^1.0.0", + "mdast-util-find-and-replace": "^1.1.0", + "micromark": "^2.11.3" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", + "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "dev": true, + "requires": { + "mdast-util-to-markdown": "^0.6.0" + } + }, + "mdast-util-gfm-table": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", + "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "dev": true, + "requires": { + "markdown-table": "^2.0.0", + "mdast-util-to-markdown": "~0.6.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "dev": true, + "requires": { + "mdast-util-to-markdown": "~0.6.0" + } + }, "mdast-util-heading-style": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/mdast-util-heading-style/-/mdast-util-heading-style-1.0.6.tgz", "integrity": "sha512-8ZuuegRqS0KESgjAGW8zTx4tJ3VNIiIaGFNEzFpRSAQBavVc7AvOo9I4g3crcZBfYisHs4seYh0rAVimO6HyOw==", "dev": true }, + "mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "dependencies": { + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + } + } + }, "mdast-util-to-string": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", @@ -3488,6 +3796,81 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "micromark-extension-frontmatter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-0.2.2.tgz", + "integrity": "sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==", + "dev": true, + "requires": { + "fault": "^1.0.0" + } + }, + "micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dev": true, + "requires": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dev": true, + "requires": { + "micromark": "~2.11.3" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, + "micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "dev": true + }, + "micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dev": true, + "requires": { + "micromark": "~2.11.0" + } + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", @@ -4065,6 +4448,12 @@ "semver-compare": "^1.0.0" } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, "postcss": { "version": "7.0.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", @@ -4321,6 +4710,26 @@ "type-fest": "^0.8.1" } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4357,10 +4766,72 @@ "unified": "^9.0.0" } }, + "remark-cli": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-cli/-/remark-cli-9.0.0.tgz", + "integrity": "sha512-y6kCXdwZoMoh0Wo4Och1tDW50PmMc86gW6GpF08v9d+xUCEJE2wwXdQ+TnTaUamRnfFdU+fE+eNf2PJ53cyq8g==", + "dev": true, + "requires": { + "markdown-extensions": "^1.1.0", + "remark": "^13.0.0", + "unified-args": "^8.0.0" + }, + "dependencies": { + "remark": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "dev": true, + "requires": { + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" + } + }, + "remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dev": true, + "requires": { + "mdast-util-from-markdown": "^0.8.0" + } + }, + "remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dev": true, + "requires": { + "mdast-util-to-markdown": "^0.6.0" + } + } + } + }, + "remark-frontmatter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-3.0.0.tgz", + "integrity": "sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==", + "dev": true, + "requires": { + "mdast-util-frontmatter": "^0.2.0", + "micromark-extension-frontmatter": "^0.2.0" + } + }, + "remark-gfm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", + "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "dev": true, + "requires": { + "mdast-util-gfm": "^0.1.0", + "micromark-extension-gfm": "^0.3.0" + } + }, "remark-lint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/remark-lint/-/remark-lint-7.0.1.tgz", - "integrity": "sha512-caZXo3qhuBxzvq9JSJFVQ/ERDq/6TJVgWn0KDwKOIJCGOuLXfQhby5XttUq+Rn7kLbNMtvwfWHJlte14LpaeXQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/remark-lint/-/remark-lint-8.0.0.tgz", + "integrity": "sha512-ESI8qJQ/TIRjABDnqoFsTiZntu+FRifZ5fJ77yX63eIDijl/arvmDvT+tAf75/Nm5BFL4R2JFUtkHRGVjzYUsg==", "dev": true, "requires": { "remark-message-control": "^6.0.0" @@ -4378,27 +4849,18 @@ "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-checkbox-character-style": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-checkbox-character-style/-/remark-lint-checkbox-character-style-2.0.1.tgz", - "integrity": "sha512-ANs1HaNOEYmc+O9Xyew7HRA48VXPnk7VLM76fLEf6bifXZU+VAJe+a6cmS+ohTSVSTjkMDl9dnbqiWQRE1U4zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-checkbox-character-style/-/remark-lint-checkbox-character-style-3.0.0.tgz", + "integrity": "sha512-691OJ5RdBRXVpvnOEiBhMB4uhHJSHVttw83O4qyAkNBiqxa1Axqhsz8FgmzYgRLQbOGd2ncVUcXG1LOJt6C0DQ==", "dev": true, "requires": { "unified-lint-rule": "^1.0.0", "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0", - "vfile-location": "^3.0.0" + "unist-util-visit": "^2.0.0" } }, "remark-lint-code-block-style": { @@ -4552,24 +5014,15 @@ } }, "remark-lint-list-item-bullet-indent": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-list-item-bullet-indent/-/remark-lint-list-item-bullet-indent-2.0.1.tgz", - "integrity": "sha512-tozDt9LChG1CvYJnBQH/oh45vNcHYBvg79ogvV0f8MtE/K0CXsM8EpfQ6pImFUdHpBV1op6aF6zPMrB0AkRhcQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-list-item-bullet-indent/-/remark-lint-list-item-bullet-indent-3.0.0.tgz", + "integrity": "sha512-X2rleWP8XReC4LXKF7Qi5vYiPJkA4Grx5zxsjHofFrVRz6j0PYOCuz7vsO+ZzMunFMfom6FODnscSWz4zouDVw==", "dev": true, "requires": { "pluralize": "^8.0.0", "unified-lint-rule": "^1.0.0", "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-list-item-content-indent": { @@ -4583,14 +5036,6 @@ "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-list-item-indent": { @@ -4604,14 +5049,6 @@ "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-list-item-spacing": { @@ -4664,22 +5101,22 @@ } }, "remark-lint-no-blockquote-without-marker": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-blockquote-without-marker/-/remark-lint-no-blockquote-without-marker-3.0.1.tgz", - "integrity": "sha512-sM953+u0zN90SGd2V5hWcFbacbpaROUslS5Q5F7/aa66/2rAwh6zVnrXc4pf7fFOpj7I9Xa8Aw+uB+3RJWwdrQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-blockquote-without-marker/-/remark-lint-no-blockquote-without-marker-4.0.0.tgz", + "integrity": "sha512-Y59fMqdygRVFLk1gpx2Qhhaw5IKOR9T38Wf7pjR07bEFBGUNfcoNVIFMd1TCJfCPQxUyJzzSqfZz/KT7KdUuiQ==", "dev": true, "requires": { "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", + "unist-util-generated": "^1.0.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0", "vfile-location": "^3.0.0" } }, "remark-lint-no-consecutive-blank-lines": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-2.0.1.tgz", - "integrity": "sha512-CP34b9AOaK1iD8FDplWvF9cJ318izoOaPXb2nb7smf/NdVHBI7joDzXcD4ojHOb3DTZuQcZ2bVv36vTPi/mv0Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-3.0.0.tgz", + "integrity": "sha512-kmzLlOLrapBKEngwYFTdCZDmeOaze6adFPB7G0EdymD9V1mpAlnneINuOshRLEDKK5fAhXKiZXxdGIaMPkiXrA==", "dev": true, "requires": { "pluralize": "^8.0.0", @@ -4687,14 +5124,6 @@ "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-no-dead-urls": { @@ -4793,9 +5222,9 @@ } }, "remark-lint-no-heading-content-indent": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-heading-content-indent/-/remark-lint-no-heading-content-indent-2.0.1.tgz", - "integrity": "sha512-Jp0zCykGwg13z7XU4VuoFK7DN8bVZ1u3Oqu3hqECsH6LMASb0tW4zcTIc985kcVo3OQTRyb6KLQXL2ltOvppKA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-heading-content-indent/-/remark-lint-no-heading-content-indent-3.0.0.tgz", + "integrity": "sha512-yULDoVSIqKylLDfW6mVUbrHlyEWUSFtVFiKc+/BA412xDIhm8HZLUnP+FsuBC0OzbIZ+bO9Txy52WtO3LGnK1A==", "dev": true, "requires": { "mdast-util-heading-style": "^1.0.2", @@ -4804,14 +5233,6 @@ "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", "unist-util-visit": "^2.0.0" - }, - "dependencies": { - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - } } }, "remark-lint-no-heading-punctuation": { @@ -4827,9 +5248,9 @@ } }, "remark-lint-no-inline-padding": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-inline-padding/-/remark-lint-no-inline-padding-2.0.1.tgz", - "integrity": "sha512-a36UlPvRrLCgxjjG3YZA9VCDvLBcoBtGNyM04VeCPz+d9hHe+5Fs1C/jL+DRLCH7nff90jJ5C/9b8/LTwhjaWA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-inline-padding/-/remark-lint-no-inline-padding-3.0.0.tgz", + "integrity": "sha512-3s9uW3Yux9RFC0xV81MQX3bsYs+UY7nPnRuMxeIxgcVwxQ4E/mTJd9QjXUwBhU9kdPtJ5AalngdmOW2Tgar8Cg==", "dev": true, "requires": { "mdast-util-to-string": "^1.0.2", @@ -4898,27 +5319,29 @@ } }, "remark-lint-no-table-indentation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-table-indentation/-/remark-lint-no-table-indentation-2.0.1.tgz", - "integrity": "sha512-PnqIyg5qf+QbaIfolxXpakk/MR1RxZ0EdhKgVqsaEwv8+fka1LZYu7QO+ZFmrT82gVzvjRqHJkmxTskC/VP30w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-table-indentation/-/remark-lint-no-table-indentation-3.0.0.tgz", + "integrity": "sha512-+l7GovI6T+3LhnTtz/SmSRyOb6Fxy6tmaObKHrwb/GAebI/4MhFS1LVo3vbiP/RpPYtyQoFbbuXI55hqBG4ibQ==", "dev": true, "requires": { "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" + "unist-util-visit": "^2.0.0", + "vfile-location": "^3.0.0" } }, "remark-lint-no-undefined-references": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-no-undefined-references/-/remark-lint-no-undefined-references-2.0.1.tgz", - "integrity": "sha512-tXM2ctFnduC3QcskrIePUajcjtNtBmo2dvlj4aoQJtQy09Soav/rYngb8u/SgERc6Irdmm5s55UAwR9CcSrzVg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-no-undefined-references/-/remark-lint-no-undefined-references-3.0.0.tgz", + "integrity": "sha512-0hzaJS9GuzSQVOeeNdJr/s66LRQOzp618xuOQPYWHcJdd+SCaRTyWbjMrTM/cCI5L1sYjgurp410NkIBQ32Vqg==", "dev": true, "requires": { "collapse-white-space": "^1.0.4", "unified-lint-rule": "^1.0.0", "unist-util-generated": "^1.1.0", - "unist-util-visit": "^2.0.0" + "unist-util-position": "^3.1.0", + "unist-util-visit": "^2.0.0", + "vfile-location": "^3.1.0" } }, "remark-lint-no-unused-definitions": { @@ -4981,9 +5404,9 @@ } }, "remark-lint-table-cell-padding": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-2.0.1.tgz", - "integrity": "sha512-vytUq4O1cg9UBXyeduANqpVqlbZpEtpXe/hYdvAObWgp1Jr7l74Zcvm+pn/ouaCuAsrxDVWeTa5Mg3V4OByw4g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-3.0.0.tgz", + "integrity": "sha512-sEKrbyFZPZpxI39R8/r+CwUrin9YtyRwVn0SQkNQEZWZcIpylK+bvoKIldvLIXQPob+ZxklL0GPVRzotQMwuWQ==", "dev": true, "requires": { "unified-lint-rule": "^1.0.0", @@ -5005,9 +5428,9 @@ } }, "remark-lint-table-pipes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/remark-lint-table-pipes/-/remark-lint-table-pipes-2.0.1.tgz", - "integrity": "sha512-ZdR9rj1BZYXHPXFk3Gnb4agwL+CtO/SojhHua4iRBx1WCQElCeZS3M9naRrE41+2QSNkKnytgGZJzyAlm2nFGQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-table-pipes/-/remark-lint-table-pipes-3.0.0.tgz", + "integrity": "sha512-QPokSazEdl0Y8ayUV9UB0Ggn3Jos/RAQwIo0z1KDGnJlGDiF80Jc6iU9RgDNUOjlpQffSLIfSVxH5VVYF/K3uQ==", "dev": true, "requires": { "unified-lint-rule": "^1.0.0", @@ -5063,14 +5486,14 @@ } }, "remark-preset-lint-consistent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-preset-lint-consistent/-/remark-preset-lint-consistent-3.0.1.tgz", - "integrity": "sha512-Q+i6iSHvEtklZHcrTcRzNy96gRUqnNXkVsZAiH1J06jrkarBoSakQOkWD5gpR7tGbfr9VchrntdWY+n0YSf9Ww==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-preset-lint-consistent/-/remark-preset-lint-consistent-4.0.0.tgz", + "integrity": "sha512-1euNZfRanM3wysHAR0vYX6uMbbKTlmTc+QvrymgRayKV8uhslQBISa+XduWk7mSz68ylS8CRR7JGvBfi6kDQjg==", "dev": true, "requires": { - "remark-lint": "^7.0.0", + "remark-lint": "^8.0.0", "remark-lint-blockquote-indentation": "^2.0.0", - "remark-lint-checkbox-character-style": "^2.0.0", + "remark-lint-checkbox-character-style": "^3.0.0", "remark-lint-code-block-style": "^2.0.0", "remark-lint-emphasis-marker": "^2.0.0", "remark-lint-fenced-code-marker": "^2.0.0", @@ -5080,16 +5503,16 @@ "remark-lint-ordered-list-marker-style": "^2.0.0", "remark-lint-rule-style": "^2.0.0", "remark-lint-strong-marker": "^2.0.0", - "remark-lint-table-cell-padding": "^2.0.0" + "remark-lint-table-cell-padding": "^3.0.0" } }, "remark-preset-lint-markdown-style-guide": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-preset-lint-markdown-style-guide/-/remark-preset-lint-markdown-style-guide-3.0.1.tgz", - "integrity": "sha512-1C4s6TtYCPueZIkxXK8aJ6qz84WqsxA7vA11i1PBIwJuL9a254X+QlbzhhEVKp0GwV4M/YTAVcfbGWVuiNEynw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-preset-lint-markdown-style-guide/-/remark-preset-lint-markdown-style-guide-4.0.0.tgz", + "integrity": "sha512-gczDlfZ28Fz0IN/oddy0AH4CiTu9S8d3pJWUsrnwFiafjhJjPGobGE1OD3bksi53md1Bp4K0fzo99YYfvB4Sjw==", "dev": true, "requires": { - "remark-lint": "^7.0.0", + "remark-lint": "^8.0.0", "remark-lint-blockquote-indentation": "^2.0.0", "remark-lint-code-block-style": "^2.0.0", "remark-lint-definition-case": "^2.0.0", @@ -5105,12 +5528,12 @@ "remark-lint-link-title-style": "^2.0.0", "remark-lint-list-item-content-indent": "^2.0.0", "remark-lint-list-item-indent": "^2.0.0", - "remark-lint-list-item-spacing": "^2.0.0", + "remark-lint-list-item-spacing": "^3.0.0", "remark-lint-maximum-heading-length": "^2.0.0", "remark-lint-maximum-line-length": "^2.0.0", "remark-lint-no-auto-link-without-protocol": "^2.0.0", - "remark-lint-no-blockquote-without-marker": "^3.0.0", - "remark-lint-no-consecutive-blank-lines": "^2.0.0", + "remark-lint-no-blockquote-without-marker": "^4.0.0", + "remark-lint-no-consecutive-blank-lines": "^3.0.0", "remark-lint-no-duplicate-headings": "^2.0.0", "remark-lint-no-emphasis-as-heading": "^2.0.0", "remark-lint-no-file-name-articles": "^1.0.0", @@ -5119,43 +5542,57 @@ "remark-lint-no-file-name-mixed-case": "^1.0.0", "remark-lint-no-file-name-outer-dashes": "^1.0.0", "remark-lint-no-heading-punctuation": "^2.0.0", - "remark-lint-no-inline-padding": "^2.0.0", + "remark-lint-no-inline-padding": "^3.0.0", "remark-lint-no-literal-urls": "^2.0.0", "remark-lint-no-multiple-toplevel-headings": "^2.0.0", "remark-lint-no-shell-dollars": "^2.0.0", "remark-lint-no-shortcut-reference-image": "^2.0.0", "remark-lint-no-shortcut-reference-link": "^2.0.0", - "remark-lint-no-table-indentation": "^2.0.0", + "remark-lint-no-table-indentation": "^3.0.0", "remark-lint-ordered-list-marker-style": "^2.0.0", "remark-lint-ordered-list-marker-value": "^2.0.0", "remark-lint-rule-style": "^2.0.0", "remark-lint-strong-marker": "^2.0.0", - "remark-lint-table-cell-padding": "^2.0.0", + "remark-lint-table-cell-padding": "^3.0.0", "remark-lint-table-pipe-alignment": "^2.0.0", - "remark-lint-table-pipes": "^2.0.0", + "remark-lint-table-pipes": "^3.0.0", "remark-lint-unordered-list-marker-style": "^2.0.0" + }, + "dependencies": { + "remark-lint-list-item-spacing": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-lint-list-item-spacing/-/remark-lint-list-item-spacing-3.0.0.tgz", + "integrity": "sha512-SRUVonwdN3GOSFb6oIYs4IfJxIVR+rD0nynkX66qEO49/qDDT1PPvkndis6Nyew5+t+2V/Db9vqllL6SWbnEtw==", + "dev": true, + "requires": { + "unified-lint-rule": "^1.0.0", + "unist-util-generated": "^1.1.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + } + } } }, "remark-preset-lint-recommended": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-preset-lint-recommended/-/remark-preset-lint-recommended-4.0.1.tgz", - "integrity": "sha512-zn+ImQbOVcAQVWLL0R0rFQ2Wy8JyWnuU3mJ8Zh0EVOckglcxByssvTbKqPih3Lh8ogpE38EfnC3a/vshj4Jx6A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-preset-lint-recommended/-/remark-preset-lint-recommended-5.0.0.tgz", + "integrity": "sha512-uu+Ab8JCwMMaKvvB0LOWTWtM3uAvJbKQM/oyWCEJqj7lUVNTKZS575Ro5rKM3Dx7kQjjR1iw0e99bpAYTc5xNA==", "dev": true, "requires": { - "remark-lint": "^7.0.0", + "remark-lint": "^8.0.0", "remark-lint-final-newline": "^1.0.0", "remark-lint-hard-break-spaces": "^2.0.0", - "remark-lint-list-item-bullet-indent": "^2.0.0", + "remark-lint-list-item-bullet-indent": "^3.0.0", "remark-lint-list-item-indent": "^2.0.0", "remark-lint-no-auto-link-without-protocol": "^2.0.0", - "remark-lint-no-blockquote-without-marker": "^3.0.0", + "remark-lint-no-blockquote-without-marker": "^4.0.0", "remark-lint-no-duplicate-definitions": "^2.0.0", - "remark-lint-no-heading-content-indent": "^2.0.0", - "remark-lint-no-inline-padding": "^2.0.0", + "remark-lint-no-heading-content-indent": "^3.0.0", + "remark-lint-no-inline-padding": "^3.0.0", "remark-lint-no-literal-urls": "^2.0.0", "remark-lint-no-shortcut-reference-image": "^2.0.0", "remark-lint-no-shortcut-reference-link": "^2.0.0", - "remark-lint-no-undefined-references": "^2.0.0", + "remark-lint-no-undefined-references": "^3.0.0", "remark-lint-no-unused-definitions": "^2.0.0", "remark-lint-ordered-list-marker-style": "^2.0.0" } @@ -5855,6 +6292,16 @@ "is-number": "^7.0.0" } }, + "to-vfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-6.1.0.tgz", + "integrity": "sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==", + "dev": true, + "requires": { + "is-buffer": "^2.0.0", + "vfile": "^4.0.0" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -5940,6 +6387,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -5973,6 +6426,106 @@ "vfile": "^4.0.0" } }, + "unified-args": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unified-args/-/unified-args-8.1.0.tgz", + "integrity": "sha512-t1HPS1cQPsVvt/6EtyWIbQGurza5684WGRigNghZRvzIdHm3LPgMdXPyGx0npORKzdiy5+urkF0rF5SXM8lBuQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "chalk": "^3.0.0", + "chokidar": "^3.0.0", + "fault": "^1.0.2", + "json5": "^2.0.0", + "minimist": "^1.2.0", + "text-table": "^0.2.0", + "unified-engine": "^8.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "unified-engine": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-8.1.0.tgz", + "integrity": "sha512-ptXTWUf9HZ2L9xto7tre+hSdSN7M9S0rypUpMAcFhiDYjrXLrND4If+8AZOtPFySKI/Zhfxf7GVAR34BqixDUA==", + "dev": true, + "requires": { + "concat-stream": "^2.0.0", + "debug": "^4.0.0", + "fault": "^1.0.0", + "figures": "^3.0.0", + "glob": "^7.0.3", + "ignore": "^5.0.0", + "is-buffer": "^2.0.0", + "is-empty": "^1.0.0", + "is-plain-obj": "^2.0.0", + "js-yaml": "^3.6.1", + "load-plugin": "^3.0.0", + "parse-json": "^5.0.0", + "to-vfile": "^6.0.0", + "trough": "^1.0.0", + "unist-util-inspect": "^5.0.0", + "vfile-reporter": "^6.0.0", + "vfile-statistics": "^1.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "unified-lint-rule": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/unified-lint-rule/-/unified-lint-rule-1.0.6.tgz", @@ -5983,9 +6536,9 @@ } }, "unified-message-control": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unified-message-control/-/unified-message-control-3.0.1.tgz", - "integrity": "sha512-K2Kvvp1DBzeuxYLLsumZh/gDWUTl4e2z/P3VReFirC78cfHKtQifbhnfRrSBtKtd1Uc6cvYTW0/SZIUaMAEcTg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unified-message-control/-/unified-message-control-3.0.3.tgz", + "integrity": "sha512-oY5z2n8ugjpNHXOmcgrw0pQeJzavHS0VjPBP21tOcm7rc2C+5Q+kW9j5+gqtf8vfW/8sabbsK5+P+9QPwwEHDA==", "dev": true, "requires": { "unist-util-visit": "^2.0.0", @@ -6013,6 +6566,15 @@ "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==", "dev": true }, + "unist-util-inspect": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-5.0.1.tgz", + "integrity": "sha512-fPNWewS593JSmg49HbnE86BJKuBi1/nMWhDSccBvbARfxezEuJV85EaARR9/VplveiwCoLm2kWq+DhP8TBaDpw==", + "dev": true, + "requires": { + "is-empty": "^1.0.0" + } + }, "unist-util-is": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", @@ -6150,6 +6712,72 @@ "unist-util-stringify-position": "^2.0.0" } }, + "vfile-reporter": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-6.0.2.tgz", + "integrity": "sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA==", + "dev": true, + "requires": { + "repeat-string": "^1.5.0", + "string-width": "^4.0.0", + "supports-color": "^6.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-sort": "^2.1.2", + "vfile-statistics": "^1.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "vfile-reporter-json": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-reporter-json/-/vfile-reporter-json-2.0.2.tgz", + "integrity": "sha512-L9s5WLxOFCygydfGAaItZtgd2eQi/HVgo0ChYVbm02EnFMzybr3PZYUCmSqcCWMKWCX5FLjvSwjkSVS47Eph6g==", + "dev": true + }, + "vfile-sort": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.2.tgz", + "integrity": "sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==", + "dev": true + }, + "vfile-statistics": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.4.tgz", + "integrity": "sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6396,6 +7024,12 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true } } } diff --git a/package.json b/package.json index cb209f6d..30096627 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,9 @@ "micromatch": "^4.0.2", "nyc": "^15.1.0", "prettier": "2.1.2", + "remark-cli": "^9.0.0", + "remark-frontmatter": "^3.0.0", + "remark-gfm": "^1.0.0", "remark-lint-emphasis-marker": "^2.0.0", "remark-lint-list-item-spacing": "^2.0.0", "remark-lint-maximum-heading-length": "^2.0.0", @@ -33,12 +36,13 @@ "remark-lint-ordered-list-marker-style": "^2.0.0", "remark-lint-strong-marker": "^2.0.0", "remark-lint-unordered-list-marker-style": "^2.0.0", - "remark-preset-lint-consistent": "^3.0.0", - "remark-preset-lint-markdown-style-guide": "^3.0.0", - "remark-preset-lint-recommended": "^4.0.0", + "remark-preset-lint-consistent": "^4.0.0", + "remark-preset-lint-markdown-style-guide": "^4.0.0", + "remark-preset-lint-recommended": "^5.0.0", "source-map-support": "^0.5.19", "stylelint": "^13.6.1", - "stylelint-config-standard": "^20.0.0" + "stylelint-config-standard": "^20.0.0", + "vfile-reporter-json": "^2.0.2" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/serverless/deploy_cpu.sh b/serverless/deploy_cpu.sh index 531a2126..4a88158f 100755 --- a/serverless/deploy_cpu.sh +++ b/serverless/deploy_cpu.sh @@ -6,7 +6,9 @@ FUNCTIONS_DIR=${1:-$SCRIPT_DIR} nuctl create project cvat -for func_config in $(find "$FUNCTIONS_DIR" -name "function.yaml") +shopt -s globstar + +for func_config in "$FUNCTIONS_DIR"/**/function.yaml do func_root=$(dirname "$func_config") echo Deploying $(dirname "$func_root") function... diff --git a/serverless/deploy_gpu.sh b/serverless/deploy_gpu.sh index 3845a113..ed323ae7 100755 --- a/serverless/deploy_gpu.sh +++ b/serverless/deploy_gpu.sh @@ -2,24 +2,19 @@ # Sample commands to deploy nuclio functions on GPU SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +FUNCTIONS_DIR=${1:-$SCRIPT_DIR} nuctl create project cvat -nuctl deploy --project-name cvat \ - --path "$SCRIPT_DIR/tensorflow/faster_rcnn_inception_v2_coco/nuclio" \ - --platform local --base-image tensorflow/tensorflow:2.1.1-gpu \ - --desc "GPU based Faster RCNN from Tensorflow Object Detection API" \ - --image cvat/tf.faster_rcnn_inception_v2_coco_gpu \ - --triggers '{"myHttpTrigger": {"maxWorkers": 1}}' \ - --resource-limit nvidia.com/gpu=1 --verbose - -nuctl deploy --project-name cvat \ - --path "$SCRIPT_DIR/tensorflow/matterport/mask_rcnn/nuclio" \ - --platform local --base-image tensorflow/tensorflow:1.15.5-gpu-py3 \ - --desc "GPU based implementation of Mask RCNN on Python 3, Keras, and TensorFlow." \ - --image cvat/tf.matterport.mask_rcnn_gpu\ - --triggers '{"myHttpTrigger": {"maxWorkers": 1}}' \ - --resource-limit nvidia.com/gpu=1 --verbose +shopt -s globstar +for func_config in "$FUNCTIONS_DIR"/**/function-gpu.yaml +do + func_root=$(dirname "$func_config") + echo "Deploying $(dirname "$func_root") function..." + nuctl deploy --project-name cvat --path "$func_root" \ + --volume "$SCRIPT_DIR/common:/opt/nuclio/common" \ + --file "$func_config" --platform local +done nuctl get function diff --git a/serverless/openvino/dextr/nuclio/main.py b/serverless/openvino/dextr/nuclio/main.py index 10f47026..5242b334 100644 --- a/serverless/openvino/dextr/nuclio/main.py +++ b/serverless/openvino/dextr/nuclio/main.py @@ -8,7 +8,7 @@ def init_context(context): context.logger.info("Init context... 0%") model = ModelHandler() - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") @@ -16,7 +16,7 @@ def handler(context, event): context.logger.info("call handler") data = event.body points = data["pos_points"] - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) image = Image.open(buf) polygon = context.user_data.model.handle(image, points) diff --git a/serverless/openvino/dextr/nuclio/model_handler.py b/serverless/openvino/dextr/nuclio/model_handler.py index e8429d52..75197d53 100644 --- a/serverless/openvino/dextr/nuclio/model_handler.py +++ b/serverless/openvino/dextr/nuclio/model_handler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Intel Corporation +# Copyright (C) 2018-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -21,7 +21,7 @@ class ModelHandler: # polygon: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] def handle(self, image, points): DEXTR_PADDING = 50 - DEXTR_TRESHOLD = 0.9 + DEXTR_TRESHOLD = 0.8 DEXTR_SIZE = 512 numpy_image = np.array(image) @@ -43,7 +43,7 @@ class ModelHandler: resized = resized[:, :, :3] # Make a heatmap - points = points - [min(points[:, 0]), min(points[:, 1])] + [DEXTR_PADDING, DEXTR_PADDING] + points = points - [bounding_box[0], bounding_box[1]] points = (points * [DEXTR_SIZE / numpy_cropped.shape[1], DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) for point in points: @@ -51,25 +51,24 @@ class ModelHandler: gaussian_y_axis = np.arange(0, DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) heatmap = np.maximum(heatmap, gaussian) - cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) + cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) # Concat an image and a heatmap input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) input_dextr = input_dextr.transpose((2,0,1)) pred = self.model.infer(input_dextr[np.newaxis, ...], False)[0, 0, :, :] - pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) - result = np.zeros(numpy_image.shape[:2]) - result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > DEXTR_TRESHOLD + pred = (pred > DEXTR_TRESHOLD).astype(np.uint8) + pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_NEAREST) + result = np.zeros(numpy_image.shape[:2]).astype(np.uint8) + result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred # Convert a mask to a polygon - result = np.array(result, dtype=np.uint8) - cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) contours = None if int(cv2.__version__.split('.')[0]) > 3: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] else: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] contours = max(contours, key=lambda arr: arr.size) if contours.shape.count(1): diff --git a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py index 9197632a..ca159e63 100644 --- a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py +++ b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py @@ -8,15 +8,15 @@ def init_context(context): context.logger.info("Init context... 0%") model = ModelHandler() - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run person-reidentification-retail-0300 model") data = event.body - buf0 = io.BytesIO(base64.b64decode(data["image0"].encode('utf-8'))) - buf1 = io.BytesIO(base64.b64decode(data["image1"].encode('utf-8'))) + buf0 = io.BytesIO(base64.b64decode(data["image0"])) + buf1 = io.BytesIO(base64.b64decode(data["image1"])) threshold = float(data.get("threshold", 0.5)) max_distance = float(data.get("max_distance", 50)) image0 = Image.open(buf0) diff --git a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py index 9942bc02..38664c5e 100644 --- a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py +++ b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py @@ -9,20 +9,22 @@ def init_context(context): context.logger.info("Init context... 0%") # Read labels - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) + labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} # Read the DL model model = ModelHandler(labels) - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run semantic-segmentation-adas-0001 model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) diff --git a/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py b/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py index ab54e76c..70936a96 100644 --- a/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py +++ b/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py @@ -9,20 +9,21 @@ def init_context(context): context.logger.info("Init context... 0%") # Read labels - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} # Read the DL model model = ModelHandler(labels) - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run text-detection-0004 model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) pixel_threshold = float(data.get("pixel_threshold", 0.8)) link_threshold = float(data.get("link_threshold", 0.8)) image = Image.open(buf) diff --git a/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py index 6ae5c801..26a9b306 100644 --- a/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py +++ b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -9,20 +9,22 @@ def init_context(context): context.logger.info("Init context... 0%") # Read labels - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) + labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} # Read the DL model model = ModelHandler(labels) - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run faster_rcnn_inception_v2_coco model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) diff --git a/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py index 8fc7d285..8b0fe8ae 100644 --- a/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py +++ b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -9,20 +9,22 @@ def init_context(context): context.logger.info("Init context... 0%") # Read labels - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) + labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} # Read the DL model model = ModelHandler(labels) - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run mask_rcnn_inception_resnet_v2_atrous_coco model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.2)) image = Image.open(buf) diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py index 806ab654..b0d5dc97 100644 --- a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py @@ -9,20 +9,22 @@ def init_context(context): context.logger.info("Init context... 0%") # Read labels - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) + labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} # Read the DL model model = ModelHandler(labels) - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run yolo-v3-tf model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py index c032bf8d..5b051677 100644 --- a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 Intel Corporation +# Copyright (C) 2020-2021 Intel Corporation # # SPDX-License-Identifier: MIT @@ -50,7 +50,7 @@ def scale_bbox(x, y, h, w, class_id, confidence, h_scale, w_scale): def parse_yolo_region(blob, resized_image_shape, original_im_shape, params, threshold): # ------------------------------------------ Validating output parameters ------------------------------------------ _, _, out_blob_h, out_blob_w = blob.shape - assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ + assert out_blob_w == out_blob_h, "Invalid size of output blob. It should be in NCHW layout and height should " \ "be equal to width. Current height = {}, current width = {}" \ "".format(out_blob_h, out_blob_w) @@ -131,7 +131,7 @@ class ModelHandler: objects += parse_yolo_region(out_blob, self.model.input_size(), origin_im_size, layer_params, threshold) - # Filtering overlapping boxes (non-maximum supression) + # Filtering overlapping boxes (non-maximum suppression) IOU_THRESHOLD = 0.4 objects = sorted(objects, key=lambda obj : obj['confidence'], reverse=True) for i, obj in enumerate(objects): diff --git a/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function-gpu.yaml b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function-gpu.yaml new file mode 100644 index 00000000..80594ffe --- /dev/null +++ b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function-gpu.yaml @@ -0,0 +1,136 @@ +metadata: + name: pth.facebookresearch.detectron2.retinanet_r101 + namespace: cvat + annotations: + name: RetinaNet R101 + type: detector + framework: pytorch + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: RetinaNet R101 from Detectron2 optimized for GPU + runtime: 'python:3.8' + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/pth.facebookresearch.detectron2.retinanet_r101 + baseImage: ubuntu:20.04 + + directives: + preCopy: + - kind: ENV + value: DEBIAN_FRONTEND=noninteractive + - kind: RUN + value: apt-get update && apt-get -y install curl git python3 python3-pip + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: pip3 install torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html + - kind: RUN + value: pip3 install 'git+https://github.com/facebookresearch/detectron2@v0.4' + - kind: RUN + value: curl -O https://dl.fbaipublicfiles.com/detectron2/COCO-Detection/retinanet_R_101_FPN_3x/190397697/model_final_971ab9.pkl + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/local/bin/pip + + triggers: + myHttpTrigger: + maxWorkers: 1 + kind: 'http' + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + resources: + limits: + nvidia.com/gpu: 1 + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 + mountMode: volume diff --git a/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function.yaml b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function.yaml new file mode 100644 index 00000000..5c7c2c64 --- /dev/null +++ b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/function.yaml @@ -0,0 +1,132 @@ +metadata: + name: pth.facebookresearch.detectron2.retinanet_r101 + namespace: cvat + annotations: + name: RetinaNet R101 + type: detector + framework: pytorch + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: RetinaNet R101 from Detectron2 + runtime: 'python:3.8' + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/pth.facebookresearch.detectron2.retinanet_r101 + baseImage: ubuntu:20.04 + + directives: + preCopy: + - kind: ENV + value: DEBIAN_FRONTEND=noninteractive + - kind: RUN + value: apt-get update && apt-get -y install curl git python3 python3-pip + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: pip3 install torch==1.8.1+cpu torchvision==0.9.1+cpu torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html + - kind: RUN + value: pip3 install 'git+https://github.com/facebookresearch/detectron2@v0.4' + - kind: RUN + value: curl -O https://dl.fbaipublicfiles.com/detectron2/COCO-Detection/retinanet_R_101_FPN_3x/190397697/model_final_971ab9.pkl + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/local/bin/pip + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: 'http' + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 + mountMode: volume diff --git a/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/main.py b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/main.py new file mode 100644 index 00000000..993629a0 --- /dev/null +++ b/serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio/main.py @@ -0,0 +1,60 @@ +import json +import base64 +import io +from PIL import Image + +import torch +from detectron2.model_zoo import get_config +from detectron2.data.detection_utils import convert_PIL_to_numpy +from detectron2.engine.defaults import DefaultPredictor +from detectron2.data.datasets.builtin_meta import COCO_CATEGORIES + +CONFIG_OPTS = ["MODEL.WEIGHTS", "model_final_971ab9.pkl"] +CONFIDENCE_THRESHOLD = 0.5 + +def init_context(context): + context.logger.info("Init context... 0%") + + cfg = get_config('COCO-Detection/retinanet_R_101_FPN_3x.yaml') + if torch.cuda.is_available(): + CONFIG_OPTS.extend(['MODEL.DEVICE', 'cuda']) + else: + CONFIG_OPTS.extend(['MODEL.DEVICE', 'cpu']) + + cfg.merge_from_list(CONFIG_OPTS) + cfg.MODEL.RETINANET.SCORE_THRESH_TEST = CONFIDENCE_THRESHOLD + cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = CONFIDENCE_THRESHOLD + cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = CONFIDENCE_THRESHOLD + cfg.freeze() + predictor = DefaultPredictor(cfg) + + context.user_data.model_handler = predictor + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run retinanet-R101 model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"])) + threshold = float(data.get("threshold", 0.5)) + image = convert_PIL_to_numpy(Image.open(buf), format="BGR") + + predictions = context.user_data.model_handler(image) + + instances = predictions['instances'] + pred_boxes = instances.pred_boxes + scores = instances.scores + pred_classes = instances.pred_classes + results = [] + for box, score, label in zip(pred_boxes, scores, pred_classes): + label = COCO_CATEGORIES[int(label)]["name"] + if score >= threshold: + results.append({ + "confidence": str(float(score)), + "label": label, + "points": box.tolist(), + "type": "rectangle", + }) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/pytorch/foolwood/siammask/nuclio/main.py b/serverless/pytorch/foolwood/siammask/nuclio/main.py index 51c3669a..ea3dc141 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/main.py +++ b/serverless/pytorch/foolwood/siammask/nuclio/main.py @@ -9,14 +9,14 @@ def init_context(context): # Read the DL model model = ModelHandler() - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run SiamMask model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) shape = data.get("shape") state = data.get("state") image = Image.open(buf) diff --git a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml index 50a25753..57b78b71 100644 --- a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml +++ b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml @@ -7,6 +7,7 @@ metadata: spec: framework: pytorch min_pos_points: 1 + min_neg_points: 0 spec: description: f-BRS interactive segmentation diff --git a/serverless/pytorch/saic-vul/fbrs/nuclio/main.py b/serverless/pytorch/saic-vul/fbrs/nuclio/main.py index ad1f8edd..15f5b5b1 100644 --- a/serverless/pytorch/saic-vul/fbrs/nuclio/main.py +++ b/serverless/pytorch/saic-vul/fbrs/nuclio/main.py @@ -12,7 +12,7 @@ def init_context(context): context.logger.info("Init context... 0%") model = ModelHandler() - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") @@ -22,7 +22,7 @@ def handler(context, event): pos_points = data["pos_points"] neg_points = data["neg_points"] threshold = data.get("threshold", 0.5) - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) image = Image.open(buf) polygon = context.user_data.model.handle(image, pos_points, diff --git a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml index f84f543c..a210195f 100644 --- a/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml +++ b/serverless/pytorch/shiyinzhang/iog/nuclio/function.yaml @@ -7,6 +7,7 @@ metadata: spec: framework: pytorch min_pos_points: 1 + min_neg_points: 0 startswith_box: true spec: diff --git a/serverless/pytorch/shiyinzhang/iog/nuclio/main.py b/serverless/pytorch/shiyinzhang/iog/nuclio/main.py index 16c4d732..96b9d0cd 100644 --- a/serverless/pytorch/shiyinzhang/iog/nuclio/main.py +++ b/serverless/pytorch/shiyinzhang/iog/nuclio/main.py @@ -13,7 +13,7 @@ def init_context(context): context.logger.info("Init context... 0%") model = ModelHandler() - setattr(context.user_data, 'model', model) + context.user_data.model = model context.logger.info("Init context...100%") @@ -24,7 +24,7 @@ def handler(context, event): neg_points = data["neg_points"] obj_bbox = data.get("obj_bbox", None) threshold = data.get("threshold", 0.8) - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) image = Image.open(buf) if obj_bbox is None: diff --git a/serverless/pytorch/shiyinzhang/iog/nuclio/model_handler.py b/serverless/pytorch/shiyinzhang/iog/nuclio/model_handler.py index 5d972915..c16cd84a 100644 --- a/serverless/pytorch/shiyinzhang/iog/nuclio/model_handler.py +++ b/serverless/pytorch/shiyinzhang/iog/nuclio/model_handler.py @@ -50,10 +50,10 @@ class ModelHandler: # extract a crop with padding from the image crop_padding = 30 crop_bbox = [ - max(bbox[0] - crop_padding, 0), - max(bbox[1] - crop_padding, 0), - min(bbox[2] + crop_padding, image.width - 1), - min(bbox[3] + crop_padding, image.height - 1) + max(bbox[0][0] - crop_padding, 0), + max(bbox[0][1] - crop_padding, 0), + min(bbox[1][0] + crop_padding, image.width - 1), + min(bbox[1][1] + crop_padding, image.height - 1) ] crop_shape = ( int(crop_bbox[2] - crop_bbox[0] + 1), # width diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function-gpu.yaml b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function-gpu.yaml new file mode 100644 index 00000000..576edf25 --- /dev/null +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function-gpu.yaml @@ -0,0 +1,136 @@ +metadata: + name: tf-faster-rcnn-inception-v2-coco + namespace: cvat + annotations: + name: Faster RCNN via Tensorflow + type: detector + framework: tensorflow + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: Faster RCNN from Tensorflow Object Detection API optimized for GPU + runtime: 'python:3.6' + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/tf.faster_rcnn_inception_v2_coco + baseImage: tensorflow/tensorflow:2.1.1-gpu + + directives: + preCopy: + - kind: RUN + value: apt install curl + - kind: WORKDIR + value: /opt/nuclio + + postCopy: + - kind: RUN + value: + curl -O http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && + tar -xzf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && rm faster_rcnn_inception_v2_coco_2018_01_28.tar.gz + - kind: RUN + value: ln -s faster_rcnn_inception_v2_coco_2018_01_28 faster_rcnn + - kind: RUN + value: pip install pillow pyyaml + + triggers: + myHttpTrigger: + maxWorkers: 1 + kind: 'http' + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + resources: + limits: + nvidia.com/gpu: 1 + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 + mountMode: volume diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 4e6da8eb..13a87239 100644 --- a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -108,9 +108,9 @@ spec: postCopy: - kind: RUN - value: curl -O http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz - - kind: RUN - value: tar -xzf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && rm faster_rcnn_inception_v2_coco_2018_01_28.tar.gz + value: + curl -O http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && + tar -xzf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && rm faster_rcnn_inception_v2_coco_2018_01_28.tar.gz - kind: RUN value: ln -s faster_rcnn_inception_v2_coco_2018_01_28 faster_rcnn - kind: RUN diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py index 8bcad27c..7271fa69 100644 --- a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -10,17 +10,20 @@ def init_context(context): context.logger.info("Init context... 0%") model_path = "/opt/nuclio/faster_rcnn/frozen_inference_graph.pb" model_handler = ModelLoader(model_path) - setattr(context.user_data, 'model_handler', model_handler) - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + context.user_data.model_handler = model_handler + + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} - setattr(context.user_data, "labels", labels) + context.user_data.labels = labels + context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run faster_rcnn_inception_v2_coco model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/function-gpu.yaml b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function-gpu.yaml new file mode 100644 index 00000000..f6e4497b --- /dev/null +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function-gpu.yaml @@ -0,0 +1,135 @@ +metadata: + name: tf-matterport-mask-rcnn + namespace: cvat + annotations: + name: Mask RCNN via Tensorflow + type: detector + framework: tensorflow + spec: | + [ + { "id": 0, "name": "BG" }, + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id": 10, "name": "traffic_light" }, + { "id": 11, "name": "fire_hydrant" }, + { "id": 12, "name": "stop_sign" }, + { "id": 13, "name": "parking_meter" }, + { "id": 14, "name": "bench" }, + { "id": 15, "name": "bird" }, + { "id": 16, "name": "cat" }, + { "id": 17, "name": "dog" }, + { "id": 18, "name": "horse" }, + { "id": 19, "name": "sheep" }, + { "id": 20, "name": "cow" }, + { "id": 21, "name": "elephant" }, + { "id": 22, "name": "bear" }, + { "id": 23, "name": "zebra" }, + { "id": 24, "name": "giraffe" }, + { "id": 25, "name": "backpack" }, + { "id": 26, "name": "umbrella" }, + { "id": 27, "name": "handbag" }, + { "id": 28, "name": "tie" }, + { "id": 29, "name": "suitcase" }, + { "id": 30, "name": "frisbee" }, + { "id": 31, "name": "skis" }, + { "id": 32, "name": "snowboard" }, + { "id": 33, "name": "sports_ball" }, + { "id": 34, "name": "kite" }, + { "id": 35, "name": "baseball_bat" }, + { "id": 36, "name": "baseball_glove" }, + { "id": 37, "name": "skateboard" }, + { "id": 38, "name": "surfboard" }, + { "id": 39, "name": "tennis_racket" }, + { "id": 40, "name": "bottle" }, + { "id": 41, "name": "wine_glass" }, + { "id": 42, "name": "cup" }, + { "id": 43, "name": "fork" }, + { "id": 44, "name": "knife" }, + { "id": 45, "name": "spoon" }, + { "id": 46, "name": "bowl" }, + { "id": 47, "name": "banana" }, + { "id": 48, "name": "apple" }, + { "id": 49, "name": "sandwich" }, + { "id": 50, "name": "orange" }, + { "id": 51, "name": "broccoli" }, + { "id": 52, "name": "carrot" }, + { "id": 53, "name": "hot_dog" }, + { "id": 54, "name": "pizza" }, + { "id": 55, "name": "donut" }, + { "id": 56, "name": "cake" }, + { "id": 57, "name": "chair" }, + { "id": 58, "name": "couch" }, + { "id": 59, "name": "potted_plant" }, + { "id": 60, "name": "bed" }, + { "id": 61, "name": "dining_table" }, + { "id": 62, "name": "toilet" }, + { "id": 63, "name": "tv" }, + { "id": 64, "name": "laptop" }, + { "id": 65, "name": "mouse" }, + { "id": 66, "name": "remote" }, + { "id": 67, "name": "keyboard" }, + { "id": 68, "name": "cell_phone" }, + { "id": 69, "name": "microwave" }, + { "id": 70, "name": "oven" }, + { "id": 71, "name": "toaster" }, + { "id": 72, "name": "sink" }, + { "id": 73, "name": "refrigerator" }, + { "id": 74, "name": "book" }, + { "id": 75, "name": "clock" }, + { "id": 76, "name": "vase" }, + { "id": 77, "name": "scissors" }, + { "id": 78, "name": "teddy_bear" }, + { "id": 79, "name": "hair_drier" }, + { "id": 80, "name": "toothbrush" } + ] + +spec: + description: Mask RCNN optimized for GPU + + runtime: 'python:3.6' + handler: main:handler + eventTimeout: 30s + env: + - name: MASK_RCNN_DIR + value: /opt/nuclio/Mask_RCNN + build: + image: cvat/tf.matterport.mask_rcnn + baseImage: tensorflow/tensorflow:1.15.5-gpu-py3 + directives: + postCopy: + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: apt update && apt install --no-install-recommends -y git curl + - kind: RUN + value: git clone --depth 1 https://github.com/matterport/Mask_RCNN.git + - kind: RUN + value: curl -L https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5 + - kind: RUN + value: pip3 install numpy cython pyyaml keras==2.1.0 scikit-image Pillow + + triggers: + myHttpTrigger: + maxWorkers: 1 + kind: 'http' + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + resources: + limits: + nvidia.com/gpu: 1 + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 + mountMode: volume diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py b/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py index 95816dd4..75829539 100644 --- a/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py @@ -10,19 +10,20 @@ import yaml def init_context(context): context.logger.info("Init context... 0%") - functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + with open("/opt/nuclio/function.yaml", 'rb') as function_file: + functionconfig = yaml.safe_load(function_file) labels_spec = functionconfig['metadata']['annotations']['spec'] labels = {item['id']: item['name'] for item in json.loads(labels_spec)} model_handler = ModelLoader(labels) - setattr(context.user_data, 'model_handler', model_handler) + context.user_data.model_handler = model_handler context.logger.info("Init context...100%") def handler(context, event): context.logger.info("Run tf.matterport.mask_rcnn model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + buf = io.BytesIO(base64.b64decode(data["image"])) threshold = float(data.get("threshold", 0.2)) image = Image.open(buf) diff --git a/site/README.md b/site/README.md new file mode 100644 index 00000000..c706e583 --- /dev/null +++ b/site/README.md @@ -0,0 +1,79 @@ +## Basic manual for website editing + +### Edit or add documentation pages + +To edit and/or add documentation, you need to have a [GitHub](https://github.com/login) account. +To change documentation files or add a documentation page, +simply click `Edit this page` on the page you would like to edit. +If you need to add a child page, click `Create child page`. + +If you need to edit the text that has the markup [markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet), +click on the `Fork this repository` button. + +Read how to edit files for github ([GitHub docs](https://docs.github.com/en/github/managing-files-in-a-repository/editing-files-in-another-users-repository)). + +Please note that files have a markup for correct display on the site: the title, the title of the link, +the weight (affects the order of files display on the sidebar) and description (optional): + + --- + title: "Title" + linkTitle: "Link Title" + weight: 1 + description: > + Description + --- + +### Start site localy + +To start the site locally, you need a recent [extended version hugo](https://github.com/gohugoio/hugo/releases) +(recommend version 0.75.0 or later). +Open the most recent release and scroll down until you find a list of Extended versions. [Read more](https://gohugo.io/getting-started/installing/#quick-install) + +Add a path to "hugo" in the "Path" environment variable. + +Clone a repository branch containing the site. For example, using a git command: + + git clone --branch + +If you want to build and/or serve your site locally, you also need to get local copies of the theme’s own submodules: + + git submodule update --init --recursive + +To build and preview your site locally, use: + + cd /cvat/site/ + hugo server + +By default, your site will be available at . + +Instead of a "hugo server" command, you can use the "hugo" command that generates the site into a "public" folder. + +To build or update your site’s CSS resources you will need [PostCSS](https://postcss.org/) to create final assets. +To install it you must have a recent version of [NodeJS](https://nodejs.org/en/) installed on your machine, +so you can use npm, the Node package manager. +By default npm installs tools under the directory where you run [npm install](https://docs.npmjs.com/cli/v6/commands/npm-install#description): + + cd /cvat/site/ + npm ci + +Then you can build a website in the "public" folder: + + hugo + +[Read more](https://www.docsy.dev/docs/getting-started/) + +### Update the submodule of the docsy theme + +To update the submodule of the docsy theme you need to have a repository clone. While in the repository folder, +use the git command: + + git submodule update --remote + +Add and then commit the change to project: + + git add themes/ + git commit -m "Updating theme submodule" + +Push the commit to project repo. For example, run: + + git push diff --git a/site/assets/icons/logo.svg b/site/assets/icons/logo.svg new file mode 100644 index 00000000..45e05ffb --- /dev/null +++ b/site/assets/icons/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/assets/scss/_custom.scss b/site/assets/scss/_custom.scss new file mode 100644 index 00000000..f8a370ef --- /dev/null +++ b/site/assets/scss/_custom.scss @@ -0,0 +1,135 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* Increased left padding on the sidebar of documentation */ + +.td-sidebar-nav__section .ul-1 ul { + padding-left: 0.6rem !important; + padding-right: 0.1rem !important; +} + +/* Main documentation page */ + +#docs section { + padding-top: 2rem; + padding-bottom: 7rem; +} + +#docs .row div { + margin-top: 1rem; +} + +/* Footer */ + +.footer-disclaimer { + font-size: 0.83rem; + line-height: 1.25; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.container-fluid footer { + min-height: inherit; + padding-bottom: 0.5rem !important; + padding-top: 2rem !important; +} + +/* Icon color for temporary page */ + +#temporary-page i { + color: lightgrey; +} + +/* About page */ + +.logo-2 { + opacity: 0.8; +} + +.history #year h2 { + text-shadow: 0 0 3px rgb(27, 27, 27); +} + +/* block location */ + +.location { + width: 70%; +} + +.marker-location i { + color: lightgray; +} + +/* World map block "the team" */ + +.team-container { + margin: auto; + max-width: 1200px; +} + +.world-map-container { + width: 100%; +} + +#world-map { + z-index: 1; + width: 100%; + height: 100%; +} + +#world-map-marker { + z-index: 2; + position: absolute; + border-radius: 50%; + border: 2px white solid; + box-shadow: 2px 2px 1px gray; + max-height: 27px; +} + +@media (max-width: 1680px) { + #world-map-marker { + margin-left: 25px; + } +} + +@media (max-width: 1540px) { + #world-map-marker { + margin-left: 35px; + max-height: 25px; + } +} + +@media (max-width: 1200px) { + #world-map-marker { + margin-top: 5px; + margin-left: 40px; + max-height: 20px; + } +} + +@media (max-width: 992px) { + #world-map-marker { + margin-top: 10px; + margin-left: 30px; + max-height: 15px; + } +} + +@media (max-width: 768px) { + #world-map-marker { + margin-top: 15px; + margin-left: 15px; + max-height: 10px; + } +} + +#world-map-marker:hover { + border: 4px white solid; +} + +/* cover block on about page */ + +#td-cover-block-0 { + background-image: url("../images/background-about-page.jpg"); +} diff --git a/site/assets/scss/_variables_project.scss b/site/assets/scss/_variables_project.scss new file mode 100644 index 00000000..3ef8dd4b --- /dev/null +++ b/site/assets/scss/_variables_project.scss @@ -0,0 +1,17 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +/* + +Add styles or override variables from the theme here. + +*/ + +@import 'custom'; + +$enable-gradients: false; +$enable-rounded: true; +$enable-shadows: true; + +$info: #f1f1f1; diff --git a/site/build_docs.py b/site/build_docs.py new file mode 100644 index 00000000..d649af46 --- /dev/null +++ b/site/build_docs.py @@ -0,0 +1,68 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +from packaging import version +import subprocess + +import git + +MINIMUM_VERSION='1.5.0' + +def prepare_tags(repo): + tags = {} + for tag in repo.tags: + tag_version = version.parse(tag.name) + if tag_version >= version.Version(MINIMUM_VERSION) and not tag_version.is_prerelease: + release_version = (tag_version.major, tag_version.minor) + if not release_version in tags or tag_version > version.parse(tags[release_version].name): + tags[release_version] = tag + + return tags.values() + +def generate_versioning_config(filename, versions, url_prefix=''): + def write_version_item(file_object, version, url): + file_object.write('[[params.versions]]\n') + file_object.write('version = "{}"\n'.format(version)) + file_object.write('url = "{}"\n\n'.format(url)) + + with open(filename, 'w') as f: + write_version_item(f, 'develop', '{}/'.format(url_prefix)) + for v in versions: + write_version_item(f, v, '{}/{}'.format(url_prefix, v)) + +def generate_docs(repo, output_dir, tags): + def run_hugo(content_loc, destination_dir): + subprocess.run([ # nosec + 'hugo', + '--destination', + destination_dir, + '--config', + 'config.toml,versioning.toml', + ], + cwd=content_loc, + ) + + cwd = repo.working_tree_dir + content_loc = os.path.join(cwd, 'site') + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + generate_versioning_config(os.path.join(cwd, 'site', 'versioning.toml'), (t.name for t in tags)) + run_hugo(content_loc, output_dir) + + generate_versioning_config(os.path.join(cwd, 'site', 'versioning.toml'), (t.name for t in tags), '/..') + for tag in tags: + repo.git.checkout(tag.name) + destination_dir = os.path.join(output_dir, tag.name) + os.makedirs(destination_dir) + run_hugo(content_loc, destination_dir) + +if __name__ == "__main__": + repo_root = os.getcwd() + repo = git.Repo(repo_root) + output_dir = os.path.join(repo_root, 'public') + + tags = prepare_tags(repo) + generate_docs(repo, output_dir, tags) diff --git a/site/config.toml b/site/config.toml new file mode 100644 index 00000000..390309ec --- /dev/null +++ b/site/config.toml @@ -0,0 +1,196 @@ +baseURL = "/" +title = "CVAT" +relativeURLs = true + +enableRobotsTXT = true + +# Hugo allows theme composition (and inheritance). The precedence is from left to right. +theme = ["docsy"] + +# Will give values to .Lastmod etc. +enableGitInfo = true + +# Language settings +contentDir = "content/en" +defaultContentLanguage = "en" +defaultContentLanguageInSubdir = false +# Useful when translating. +enableMissingTranslationPlaceholders = true + +disableKinds = ["taxonomy", "taxonomyTerm"] + +# Highlighting config +pygmentsCodeFences = true +pygmentsUseClasses = false +# Use the new Chroma Go highlighter in Hugo. +pygmentsUseClassic = false +#pygmentsOptions = "linenos=table" +# See https://help.farbox.com/pygments.html +pygmentsStyle = "tango" + +# Configure how URLs look like per section. +[permalinks] +blog = "/:section/:year/:month/:day/:slug/" + +## Configuration for BlackFriday markdown parser: https://github.com/russross/blackfriday +[blackfriday] +plainIDAnchors = true +hrefTargetBlank = true +angledQuotes = false +latexDashes = true + +# Image processing configuration. +[imaging] +resampleFilter = "CatmullRom" +quality = 75 +anchor = "smart" + +[[menu.main]] + name = "Try it now" + weight = 50 + url = "https://cvat.org" + +[services] +[services.googleAnalytics] +# Comment out the next line to disable GA tracking. Also disables the feature described in [params.ui.feedback]. +id = "UA-00000000-0" + +# Language configuration + +[languages] +[languages.en] +title = "CVAT" +description = "" +languageName ="English" +# Weight used for sorting. +weight = 1 + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true + [markup.highlight] + # See a complete list of available styles at https://xyproto.github.io/splash/docs/all.html + style = "tango" + # Uncomment if you want your chosen highlight style used for code blocks without a specified language + # guessSyntax = "true" + +# Everything below this are Site Params + +# Comment out if you don't want the "print entire section" link enabled. +[outputs] +section = ["HTML", "print"] + +[params] +intel_terms_of_use = "https://www.intel.com/content/www/us/en/legal/terms-of-use.html" +intel_privacy_notice = "https://www.intel.com/content/www/us/en/privacy/intel-privacy-notice.html" +cvat_terms_of_use = "https://cvat.org/api/v1/restrictions/terms-of-use" + +# First one is picked as the Twitter card image if not set on page. +# images = ["images/project-illustration.png"] + +# Menu title if your navbar has a versions selector to access old versions of your site. +# This menu appears only if you have at least one [params.versions] set. +version_menu = "Releases" + +# Flag used in the "version-banner" partial to decide whether to display a +# banner on every page indicating that this is an archived version of the docs. +# Set this flag to "true" if you want to display the banner. +archived_version = false + +# The version number for the version of the docs represented in this doc set. +# Used in the "version-banner" partial to display a version number for the +# current doc set. +version = "0.0" + +# A link to latest version of the docs. Used in the "version-banner" partial to +# point people to the main doc site. +url_latest_version = "https://example.com" + +# Repository configuration (URLs for in-page links to opening issues and suggesting changes) +github_repo = "https://github.com/openvinotoolkit/cvat" +# An optional link to a related project repo. For example, the sibling repository where your product code lives. +github_project_repo = "https://github.com/openvinotoolkit/cvat" + +# Specify a value here if your content directory is not in your repo's root directory +github_subdir = "site" + +# Uncomment this if you have a newer GitHub repo with "main" as the default branch, +# or specify a new value if you want to reference another branch in your GitHub links +github_branch = "develop" + +# Google Custom Search Engine ID. Remove or comment out to disable search. +# gcs_engine_id = "011737558837375720776:fsdu1nryfng" + +# Enable Algolia DocSearch +algolia_docsearch = false + +# Enable Lunr.js offline search +offlineSearch = true + +# Enable syntax highlighting and copy buttons on code blocks with Prism +prism_syntax_highlighting = false + +# User interface configuration +[params.ui] +# Enable to show the side bar menu in its compact state. +sidebar_menu_compact = true +ul_show = 2 +# Set to true to disable breadcrumb navigation. +breadcrumb_disable = false +# Set to true to hide the sidebar search box (the top nav search box will still be displayed if search is enabled) +sidebar_search_disable = true +# Set to false if you don't want to display a logo (/assets/icons/logo.svg) in the top nav bar +navbar_logo = true +# Set to true to disable the About link in the site footer +footer_about_disable = false + +# Adds a H2 section titled "Feedback" to the bottom of each doc. The responses are sent to Google Analytics as events. +# This feature depends on [services.googleAnalytics] and will be disabled if "services.googleAnalytics.id" is not set. +# If you want this feature, but occasionally need to remove the "Feedback" section from a single page, +# add "hide_feedback: true" to the page's front matter. +[params.ui.feedback] +enable = false +# The responses that the user sees after clicking "yes" (the page was helpful) or "no" (the page was not helpful). +yes = 'Glad to hear it! Please
    tell us how we can improve.' +no = 'Sorry to hear that. Please tell us how we can improve.' + +# Adds a reading time to the top of each doc. +# If you want this feature, but occasionally need to remove the Reading time from a single page, +# add "hide_readingtime: true" to the page's front matter +[params.ui.readingtime] +enable = false + +[params.links] +# End user relevant links. These will show up on left side of footer and in the community page if you have one. +[[params.links.user]] + name ="Gitter public chat" + url = "https://gitter.im/opencv-cvat/public" + icon = "fab fa-gitter" + desc = "Join our Gitter channel for community support." +[[params.links.user]] + name = "Stack Overflow" + url = "https://stackoverflow.com/search?q=%23cvat" + icon = "fab fa-stack-overflow" + desc = "Practical questions and curated answers" +[[params.links.user]] + name = "YouTube" + url = "https://www.youtube.com/user/nmanovic" + icon = "fab fa-youtube" + desc = "Practical questions and curated answers" +# Developer relevant links. These will show up on right side of footer and in the community page if you have one. +[[params.links.developer]] + name = "GitHub" + url = "https://github.com/openvinotoolkit/cvat" + icon = "fab fa-github" + desc = "Development takes place here!" +[[params.links.developer]] + name = "Forum on Intel Developer Zone" + url = "https://community.intel.com/t5/Intel-Distribution-of-OpenVINO/bd-p/distribution-openvino-toolkit" + icon = "fas fa-envelope" + desc = "Development takes place here!" +[[params.links.developer]] + name ="Gitter developers chat" + url = "https://gitter.im/opencv-cvat/dev" + icon = "fab fa-gitter" + desc = "Join our Gitter channel for community support." diff --git a/site/content/en/_index.html b/site/content/en/_index.html new file mode 100644 index 00000000..8c5fd7a3 --- /dev/null +++ b/site/content/en/_index.html @@ -0,0 +1,22 @@ ++++ +title = "Home" +linkTitle = "Home" ++++ + +{{< blocks/section height="full" color="docs" >}} + +
    +
    + +
    +
    +

    This page is in development.

    +
    +
    +

    + Visit our GitHub repository. +

    +
    +
    + +{{< /blocks/section >}} diff --git a/site/content/en/about/_index.html b/site/content/en/about/_index.html new file mode 100644 index 00000000..065ab742 --- /dev/null +++ b/site/content/en/about/_index.html @@ -0,0 +1,153 @@ +--- +title: "About" +linkTitle: "About" +menu: + main: + weight: 50 +--- + + +{{< blocks/cover image_anchor="center" height="min" >}} + +
    + +

    About Us

    +

    CVAT was designed to provide users with a set of convenient instruments for annotating digital images and videos.
    CVAT supports supervised machine learning tasks pertaining to object detection, image classification, image segmentation and 3D data annotation. It allows users to annotate images with four types of shapes: boxes, polygons (both generally and for segmentation tasks), polylines (e.g., for annotation of markings on roads),
    and points (e.g., for annotation of face landmarks or pose estimation).

    +
    + +{{< /blocks/cover >}} + + +{{< blocks/section height="auto" color="info" >}} + +
    +

    Data scientists need annotated data (and lots of it) to train the deep neural networks (DNNs) at the core of AI workflows. Obtaining annotated data or annotating data yourself is a challenging and time-consuming process.
    For example, it took about 3,100 total hours for members of Intel’s own data annotation team to annotate more than 769,000 objects for just one of our algorithms. To help solve this challenge, Intel is conducting research to find better methods of data annotation and deliver tools that help developers do the same.

    +
    +
    +
    +

    2016 + +

    + Vatic as a web-based annotation solution. +
    +
    +

    2017 + +

    + Internal version with support for images and attributes. +
    +
    +

    2018 + +

    + First public release on GitHub. +
    +
    +

    2020 + +

    + UI based on React and AntD.
    cvat.org as demo platform.
    +
    +
    +

    2021 + +

    + Dataset as the first-class citizen. +
    +
    +

    202X + +

    + Data platform. +
    +
    +
    +
    + +{{< /blocks/section >}} + + +{{< blocks/section height="auto" color="info" >}} + +

    Core Team

    +
    + + +{{< /blocks/section >}} + + +{{< blocks/section height="auto" color="docs" >}} + +

    Contact Us:

    +
    + +
    +
    + +

    Russia, Nizhny Novgorod, Turgeneva street 30 (campus TGV)

    +
    +

    + Feedback from users helps Intel determine future direction for CVAT’s development. We hope to improve the tool’s user experience, feature set, stability, automation features and ability to be integrated with other services and encourage members of the community to take an active part in CVAT’s development. +

    + +
    + + +
    +
    + +{{< /blocks/section >}} diff --git a/site/content/en/docs/_index.md b/site/content/en/docs/_index.md new file mode 100644 index 00000000..12b8b6f4 --- /dev/null +++ b/site/content/en/docs/_index.md @@ -0,0 +1,65 @@ +--- +title: 'Documentation' +linkTitle: 'Documentation' +description: 'Welcome to the documentation of Computer Vision Annotation Tool.' +no_list: true +menu: + main: + weight: 20 +--- + +CVAT is a free, online, interactive video and image annotation tool for computer vision. +It is being developed and used by Intel to annotate millions of objects with different properties. +Many UI and UX decisions are based on feedbacks from professional data annotation team. +Try it online [cvat.org](https://cvat.org). + +Our documentation provides information for AI researchers, system administrators, developers, simple and advanced users. +The documentation is divided into three sections, and each section is divided into subsections `basic` and `advanced`. + +
    + +{{< blocks/section color="docs" >}} + +{{% blocks/feature icon="fa-sign-in-alt" title="[Getting started](/docs/getting_started/)" %}} + +Basic information and sections needed for a quick start. + +{{% /blocks/feature %}} + +{{% blocks/feature icon="fa-question" title="[FAQ](/docs/faq/)" %}} + +Answers to frequently asked questions. + +{{% /blocks/feature %}} + +{{% blocks/feature icon="fab fa-github" title="[GitHub Repository](https://github.com/openvinotoolkit/cvat)" %}} + +Computer Vision Annotation Tool GitHub repository. + +{{% /blocks/feature %}} + + + + +{{% blocks/feature icon="fa-book" title="[Manual](/docs/manual/)" %}} + +This section contains documents for CVAT simple and advanced users. + +{{% /blocks/feature %}} + +{{% blocks/feature icon="fa-server" title="[Administration](/docs/administration/)" %}} + +This section contains documents for system administrators. + +{{% /blocks/feature %}} + +{{% blocks/feature icon="fa-terminal" title="[Contributing](/docs/contributing/)" %}} + +This section contains documents for developers. + +{{% /blocks/feature %}} + + +{{< /blocks/section >}} + +
    diff --git a/site/content/en/docs/administration/_index.md b/site/content/en/docs/administration/_index.md new file mode 100644 index 00000000..cb85ffa3 --- /dev/null +++ b/site/content/en/docs/administration/_index.md @@ -0,0 +1,9 @@ +--- + +title: 'Administration' +linkTitle: 'Administration' +weight: 3 +description: 'This section contains documents for system administrators' +hide_feedback: true + +--- diff --git a/site/content/en/docs/administration/advanced/_index.md b/site/content/en/docs/administration/advanced/_index.md new file mode 100644 index 00000000..a58d2bb5 --- /dev/null +++ b/site/content/en/docs/administration/advanced/_index.md @@ -0,0 +1,9 @@ +--- + +title: 'Advanced' +linkTitle: 'Advanced' +weight: 2 +description: 'This section contains basic documents for system administrators' +hide_feedback: true + +--- diff --git a/components/analytics/README.md b/site/content/en/docs/administration/advanced/analytics.md similarity index 85% rename from components/analytics/README.md rename to site/content/en/docs/administration/advanced/analytics.md index 3c9c9b8b..6ef814ef 100644 --- a/components/analytics/README.md +++ b/site/content/en/docs/administration/advanced/analytics.md @@ -1,6 +1,17 @@ -## Analytics for Computer Vision Annotation Tool (CVAT) + -![](/cvat/apps/documentation/static/documentation/images/image097.jpg) +--- + +title: 'Analytics for Computer Vision Annotation Tool (CVAT)' +linkTitle: 'Analytics' +weight: 2 +description: This section on [GitHub](https://github.com/openvinotoolkit/cvat/tree/develop/components/analytics) + +--- + + + +![](/images/image097.jpg) It is possible to proxy annotation logs from client to ELK. To do that run the following command below: diff --git a/cvat/apps/documentation/backup_guide.md b/site/content/en/docs/administration/advanced/backup_guide.md similarity index 89% rename from cvat/apps/documentation/backup_guide.md rename to site/content/en/docs/administration/advanced/backup_guide.md index e2664c61..316f929f 100644 --- a/cvat/apps/documentation/backup_guide.md +++ b/site/content/en/docs/administration/advanced/backup_guide.md @@ -1,3 +1,11 @@ +--- +title: 'Backup guide' +linkTitle: 'Backup guide' +weight: 11 +--- + + + ## About CVAT data volumes Docker volumes are used to store all CVAT data: @@ -8,13 +16,13 @@ Docker volumes are used to store all CVAT data: - `cvat_data`: used to store uploaded and prepared media data. Mounted into `cvat` container by `/home/django/data` path. -- `cvat_keys`: used to store user ssh keys needed for [synchronization with a remote Git repository](user_guide.md#task-synchronization-with-a-repository). +- `cvat_keys`: used to store user ssh keys needed for [synchronization with a remote Git repository](/docs/manual/advanced/task-synchronization/). Mounted into `cvat` container by `/home/django/keys` path. - `cvat_logs`: used to store logs of CVAT backend processes managed by supevisord. Mounted into `cvat` container by `/home/django/logs` path. -- `cvat_events`: this is an optional volume that is used only when [Analytics component](../../../components/analytics) +- `cvat_events`: this is an optional volume that is used only when [Analytics component](/docs/administration/advanced/analytics/) is enabled and is used to store Elasticsearch database files. Mounted into `cvat_elasticsearch` container by `/usr/share/elasticsearch/data` path. @@ -48,7 +56,7 @@ cvat_data.tar.bz2 cvat_db.tar.bz2 cvat_events.tar.bz2 ## How to restore CVAT from backup -Note: CVAT containers must exist (if no, please follow the [installation guide](installation.md#quick-installation-guide)). +Note: CVAT containers must exist (if no, please follow the [installation guide](/docs/administration/basics/installation/#quick-installation-guide)). Stop all CVAT containers: ```console diff --git a/cvat/apps/documentation/installation_automatic_annotation.md b/site/content/en/docs/administration/advanced/installation_automatic_annotation.md similarity index 81% rename from cvat/apps/documentation/installation_automatic_annotation.md rename to site/content/en/docs/administration/advanced/installation_automatic_annotation.md index 3203f96d..d75e941a 100644 --- a/cvat/apps/documentation/installation_automatic_annotation.md +++ b/site/content/en/docs/administration/advanced/installation_automatic_annotation.md @@ -1,4 +1,16 @@ -### Semi-automatic and Automatic Annotation + + +--- + +title: 'Semi-automatic and Automatic Annotation' +linkTitle: 'Installation Auto Annotation' +weight: 5 +description: 'This page provides information about the installation of components needed for +semi-automatic and automatic annotation' + +--- + + > **⚠ WARNING: Do not use `docker-compose up`** > If you did, make sure all containers are stopped by `docker-compose down`. @@ -20,7 +32,7 @@ - You have to install `nuctl` command line tool to build and deploy serverless functions. Download [version 1.5.16](https://github.com/nuclio/nuclio/releases/tag/1.5.16). It is important that the version you download matches the version in - [docker-compose.serverless.yml](/components/serverless/docker-compose.serverless.yml) + [docker-compose.serverless.yml](https://github.com/openvinotoolkit/cvat/blob/develop/components/serverless/docker-compose.serverless.yml) After downloading the nuclio, give it a proper permission and do a softlink ``` @@ -28,7 +40,9 @@ sudo ln -sf $(pwd)/nuctl--linux-amd64 /usr/local/bin/nuctl ``` -- Create `cvat` project inside nuclio dashboard where you will deploy new serverless functions and deploy a couple of DL models. Commands below should be run only after CVAT has been installed using `docker-compose` because it runs nuclio dashboard which manages all serverless functions. +- Create `cvat` project inside nuclio dashboard where you will deploy new serverless functions + and deploy a couple of DL models. Commands below should be run only after CVAT has been installed + using `docker-compose` because it runs nuclio dashboard which manages all serverless functions. ```bash nuctl create project cvat @@ -50,9 +64,11 @@ **Note:** - - See [deploy_cpu.sh](/serverless/deploy_cpu.sh) for more examples. + - See [deploy_cpu.sh](https://github.com/openvinotoolkit/cvat/blob/develop/serverless/deploy_cpu.sh) + for more examples. #### GPU Support + You will need to install [Nvidia Container Toolkit](https://www.tensorflow.org/install/docker#gpu_support). Also you will need to add `--resource-limit nvidia.com/gpu=1 --triggers '{"myHttpTrigger": {"maxWorkers": 1}}'` to the nuclio deployment command. You can increase the maxWorker if you have enough GPU memory. @@ -69,12 +85,15 @@ ``` **Note:** + - The number of GPU deployed functions will be limited to your GPU memory. - - See [deploy_gpu.sh](/serverless/deploy_gpu.sh) script for more examples. + - See [deploy_gpu.sh](https://github.com/openvinotoolkit/cvat/blob/develop/serverless/deploy_gpu.sh) + script for more examples. **Troubleshooting Nuclio Functions:** -- You can open nuclio dashboard at [localhost:8070](http://localhost:8070). Make sure status of your functions are up and running without any error. +- You can open nuclio dashboard at [localhost:8070](http://localhost:8070). + Make sure status of your functions are up and running without any error. - Test your deployed DL model as a serverless function. The command below should work on Linux and Mac OS. ```bash @@ -115,12 +134,14 @@ } ] ``` + - To check for internal server errors, run `docker ps -a` to see the list of containers. Find the container that you are interested, e.g., `nuclio-nuclio-tf-faster-rcnn-inception-v2-coco-gpu`. Then check its logs by `docker logs ` e.g., + ```bash docker logs nuclio-nuclio-tf-faster-rcnn-inception-v2-coco-gpu ``` diff --git a/site/content/en/docs/administration/advanced/mounting_cloud_storages.md b/site/content/en/docs/administration/advanced/mounting_cloud_storages.md new file mode 100644 index 00000000..c1db66aa --- /dev/null +++ b/site/content/en/docs/administration/advanced/mounting_cloud_storages.md @@ -0,0 +1,395 @@ +--- +title: 'Mounting cloud storage' +linkTitle: 'Mounting cloud storage' +weight: 10 +--- + + + +## AWS S3 bucket as filesystem + +### Ubuntu 20.04 + +#### Mount + +1. Install s3fs: + + ```bash + sudo apt install s3fs + ``` + +1. Enter your credentials in a file `${HOME}/.passwd-s3fs` and set owner-only permissions: + + ```bash + echo ACCESS_KEY_ID:SECRET_ACCESS_KEY > ${HOME}/.passwd-s3fs + chmod 600 ${HOME}/.passwd-s3fs + ``` + +1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` +1. Run s3fs, replace `bucket_name`, `mount_point`: + + ```bash + s3fs -o allow_other + ``` + +For more details see [here](https://github.com/s3fs-fuse/s3fs-fuse). + +#### Automatically mount + +Follow the first 3 mounting steps above. + +##### Using fstab + +1. Create a bash script named aws_s3_fuse(e.g in /usr/bin, as root) with this content + (replace `user_name` on whose behalf the disk will be mounted, `backet_name`, `mount_point`, `/path/to/.passwd-s3fs`): + + ```bash + #!/bin/bash + sudo -u s3fs -o passwd_file=/path/to/.passwd-s3fs -o allow_other + exit 0 + ``` + +1. Give it the execution permission: + + ```bash + sudo chmod +x /usr/bin/aws_s3_fuse + ``` + +1. Edit `/etc/fstab` adding a line like this, replace `mount_point`): + + ```bash + /absolute/path/to/aws_s3_fuse fuse allow_other,user,_netdev 0 0 + ``` + +##### Using systemd + +1. Create unit file `sudo nano /etc/systemd/system/s3fs.service` + (replace `user_name`, `bucket_name`, `mount_point`, `/path/to/.passwd-s3fs`): + + ```bash + [Unit] + Description=FUSE filesystem over AWS S3 bucket + After=network.target + + [Service] + Environment="MOUNT_POINT=" + User= + Group= + ExecStart=s3fs ${MOUNT_POINT} -o passwd_file=/path/to/.passwd-s3fs -o allow_other + ExecStop=fusermount -u ${MOUNT_POINT} + Restart=always + Type=forking + + [Install] + WantedBy=multi-user.target + ``` + +1. Update the system configurations, enable unit autorun when the system boots, mount the bucket: + + ```bash + sudo systemctl daemon-reload + sudo systemctl enable s3fs.service + sudo systemctl start s3fs.service + ``` + +#### Check + +A file `/etc/mtab` contains records of currently mounted filesystems. + +```bash +cat /etc/mtab | grep 's3fs' +``` + +#### Unmount filesystem + +```bash +fusermount -u +``` + +If you used [systemd](#aws_s3_using_systemd) to mount a bucket: + +```bash +sudo systemctl stop s3fs.service +sudo systemctl disable s3fs.service +``` + +## Microsoft Azure container as filesystem + +### Ubuntu 20.04 + +#### Mount + +1. Set up the Microsoft package repository.(More [here](https://docs.microsoft.com/en-us/windows-server/administration/Linux-Package-Repository-for-Microsoft-Software#configuring-the-repositories)) + + ```bash + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + sudo apt-get update + ``` + +1. Install `blobfuse` and `fuse`: + + ```bash + sudo apt-get install blobfuse fuse + ``` + + For more details see [here](https://github.com/Azure/azure-storage-fuse/wiki/1.-Installation) + +1. Create environments (replace `account_name`, `account_key`, `mount_point`): + + ```bash + export AZURE_STORAGE_ACCOUNT= + export AZURE_STORAGE_ACCESS_KEY= + MOUNT_POINT= + ``` + +1. Create a folder for cache: + + ```bash + sudo mkdir -p /mnt/blobfusetmp + ``` + +1. Make sure the file must be owned by the user who mounts the container: + + ```bash + sudo chown /mnt/blobfusetmp + ``` + +1. Create the mount point, if it doesn't exists: + + ```bash + mkdir -p ${MOUNT_POINT} + ``` + +1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` +1. Mount container(replace `your_container`): + + ```bash + blobfuse ${MOUNT_POINT} --container-name= --tmp-path=/mnt/blobfusetmp -o allow_other + ``` + +#### Automatically mount + +Follow the first 7 mounting steps above. + +##### Using fstab + +1. Create configuration file `connection.cfg` with same content, change accountName, + select one from accountKey or sasToken and replace with your value: + + ```bash + accountName + # Please provide either an account key or a SAS token, and delete the other line. + accountKey + #change authType to specify only 1 + sasToken + authType + containerName + ``` + +1. Create a bash script named `azure_fuse`(e.g in /usr/bin, as root) with content below + (replace `user_name` on whose behalf the disk will be mounted, `mount_point`, `/path/to/blobfusetmp`,`/path/to/connection.cfg`): + + ```bash + #!/bin/bash + sudo -u blobfuse --tmp-path=/path/to/blobfusetmp --config-file=/path/to/connection.cfg -o allow_other + exit 0 + ``` + +1. Give it the execution permission: + + ```bash + sudo chmod +x /usr/bin/azure_fuse + ``` + +1. Edit `/etc/fstab` with the blobfuse script. Add the following line(replace paths): + +```bash +/absolute/path/to/azure_fuse fuse allow_other,user,_netdev +``` + +##### Using systemd + +1. Create unit file `sudo nano /etc/systemd/system/blobfuse.service`. + (replace `user_name`, `mount_point`, `container_name`,`/path/to/connection.cfg`): + + ```bash + [Unit] + Description=FUSE filesystem over Azure container + After=network.target + + [Service] + Environment="MOUNT_POINT=" + User= + Group= + ExecStart=blobfuse ${MOUNT_POINT} --container-name= --tmp-path=/mnt/blobfusetmp --config-file=/path/to/connection.cfg -o allow_other + ExecStop=fusermount -u ${MOUNT_POINT} + Restart=always + Type=forking + + [Install] + WantedBy=multi-user.target + ``` + +1. Update the system configurations, enable unit autorun when the system boots, mount the container: + + ```bash + sudo systemctl daemon-reload + sudo systemctl enable blobfuse.service + sudo systemctl start blobfuse.service + ``` + + Or for more detail [see here](https://github.com/Azure/azure-storage-fuse/tree/master/systemd) + +#### Check + +A file `/etc/mtab` contains records of currently mounted filesystems. + +```bash +cat /etc/mtab | grep 'blobfuse' +``` + +#### Unmount filesystem + +```bash +fusermount -u +``` + +If you used [systemd](#azure_using_systemd) to mount a container: + +```bash +sudo systemctl stop blobfuse.service +sudo systemctl disable blobfuse.service +``` + +If you have any mounting problems, check out the [answers](https://github.com/Azure/azure-storage-fuse/wiki/3.-Troubleshoot-FAQ) +to common problems + +## Google Drive as filesystem + +### Ubuntu 20.04 + +#### Mount + +To mount a google drive as a filesystem in user space(FUSE) +you can use [google-drive-ocamlfuse](https://github.com/astrada/google-drive-ocamlfuse) +To do this follow the instructions below: + +1. Install google-drive-ocamlfuse: + + ```bash + sudo add-apt-repository ppa:alessandro-strada/ppa + sudo apt-get update + sudo apt-get install google-drive-ocamlfuse + ``` + +1. Run `google-drive-ocamlfuse` without parameters: + + ```bash + google-drive-ocamlfuse + ``` + + This command will create the default application directory (~/.gdfuse/default), + containing the configuration file config (see the [wiki](https://github.com/astrada/google-drive-ocamlfuse/wiki) + page for more details about configuration). + And it will start a web browser to obtain authorization to access your Google Drive. + This will let you modify default configuration before mounting the filesystem. + + Then you can choose a local directory to mount your Google Drive (e.g.: ~/GoogleDrive). + +1. Create the mount point, if it doesn't exist(replace mount_point): + + ```bash + mountpoint="" + mkdir -p $mountpoint + ``` + +1. Uncomment `user_allow_other` in the `/etc/fuse.conf` file: `sudo nano /etc/fuse.conf` +1. Mount the filesystem: + + ```bash + google-drive-ocamlfuse -o allow_other $mountpoint + ``` + +#### Automatically mount + +Follow the first 4 mounting steps above. + +##### Using fstab + +1. Create a bash script named gdfuse(e.g in /usr/bin, as root) with this content + (replace `user_name` on whose behalf the disk will be mounted, `label`, `mount_point`): + + ```bash + #!/bin/bash + sudo -u google-drive-ocamlfuse -o allow_other -label