From 4b179be1ee98caf4cc701a864ca6ec7bcecc7805 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov Date: Wed, 5 Jul 2023 19:42:17 +0300 Subject: [PATCH 01/48] Updated CHANGELOG.md, bump version (#6426) --- CHANGELOG.md | 20 +++++++++++++++----- cvat-cli/requirements/base.txt | 2 +- cvat-cli/src/cvat_cli/version.py | 2 +- cvat-sdk/cvat_sdk/core/client.py | 2 +- cvat-sdk/gen/generate.sh | 2 +- cvat/__init__.py | 2 +- cvat/schema.yml | 2 +- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655121c979f8..de738f233687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[Unreleased] ### Added -- Now CVAT supports project/task markdown description with additional assets -(png, jpeg, gif, webp images and pdf files) () -- Ground Truth jobs and quality analytics for tasks () - +- TDB ### Changed - TDB @@ -21,6 +18,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - TDB +### Fixed +- TDB + +### Security +- TDB + +## \[2.5.0] - 2023-07-05 +### Added +- Now CVAT supports project/task markdown description with additional assets + (png, jpeg, gif, webp images and pdf files) () +- Ground Truth jobs and quality analytics for tasks () + ### Fixed - The problem with manifest file in tasks restored from backup () - The problem with task mode in a task restored from backup () @@ -30,7 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[API\] Invalid schema for the owner field in several endpoints () - \[SDK\] Loading tasks that have been cached with the PyTorch adapter () -- The problem with importing annotations if dataset has extra dots in filenames () +- The problem with importing annotations if dataset has extra dots in filenames + () ### Security - More comprehensive SSRF mitigations were implemented. diff --git a/cvat-cli/requirements/base.txt b/cvat-cli/requirements/base.txt index ecef7b5601f1..612076fc5c72 100644 --- a/cvat-cli/requirements/base.txt +++ b/cvat-cli/requirements/base.txt @@ -1,3 +1,3 @@ -cvat-sdk~=2.4.0 +cvat-sdk~=2.5.0 Pillow>=6.2.0 setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/cvat-cli/src/cvat_cli/version.py b/cvat-cli/src/cvat_cli/version.py index fe68e791718f..84e6495dd8d9 100644 --- a/cvat-cli/src/cvat_cli/version.py +++ b/cvat-cli/src/cvat_cli/version.py @@ -1 +1 @@ -VERSION = "2.4.4" +VERSION = "2.5.0" diff --git a/cvat-sdk/cvat_sdk/core/client.py b/cvat-sdk/cvat_sdk/core/client.py index 1db2de999bd6..9bb61ffc3ed7 100644 --- a/cvat-sdk/cvat_sdk/core/client.py +++ b/cvat-sdk/cvat_sdk/core/client.py @@ -62,8 +62,8 @@ class Client: """ SUPPORTED_SERVER_VERSIONS = ( - pv.Version("2.4"), pv.Version("2.5"), + pv.Version("2.6"), ) def __init__( diff --git a/cvat-sdk/gen/generate.sh b/cvat-sdk/gen/generate.sh index 11ebb3533de6..1b3e8bcb7789 100755 --- a/cvat-sdk/gen/generate.sh +++ b/cvat-sdk/gen/generate.sh @@ -8,7 +8,7 @@ set -e GENERATOR_VERSION="v6.0.1" -VERSION="2.4.4" +VERSION="2.5.0" LIB_NAME="cvat_sdk" LAYER1_LIB_NAME="${LIB_NAME}/api_client" DST_DIR="$(cd "$(dirname -- "$0")/.." && pwd)" diff --git a/cvat/__init__.py b/cvat/__init__.py index 4f9cdb50d31d..d227ef70902a 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (2, 5, 0, 'alpha', 0) +VERSION = (2, 6, 0, 'alpha', 0) __version__ = get_version(VERSION) diff --git a/cvat/schema.yml b/cvat/schema.yml index e9bae7e1b84f..47a68af2e933 100644 --- a/cvat/schema.yml +++ b/cvat/schema.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: CVAT REST API - version: '2.5' + version: '2.6' description: REST API for Computer Vision Annotation Tool (CVAT) termsOfService: https://www.google.com/policies/terms/ contact: From c31fbeb82c4e2bd1a0fa5abfea05121f25b57592 Mon Sep 17 00:00:00 2001 From: Anastasia Yasakova Date: Mon, 10 Jul 2023 12:43:13 +0300 Subject: [PATCH 02/48] Code coverage fixes (#6420) ### Motivation and context - Removed code coverage from full and schedule workflows - Updated codecov.yml - Fixed coverage saving in rqworker.py - Now one code coverage report is sent to codecov after passing all tests ### How has this been tested? ### Checklist - [ ] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [ ] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --------- Co-authored-by: yasakova-anastasia --- .github/codecov.yml | 8 ++++- .github/workflows/full.yml | 32 +++---------------- .github/workflows/main.yml | 46 +++++++++++++++++++++------- .github/workflows/schedule.yml | 27 +++------------- cvat/rqworker.py | 10 +++--- tests/python/requirements.txt | 2 +- tests/python/rest_api/test_tasks.py | 1 + tests/python/shared/fixtures/init.py | 6 ++-- 8 files changed, 63 insertions(+), 69 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index d259debd2cb1..aa7fd1c08451 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -23,5 +23,11 @@ codecov: require_ci_to_pass: yes notify: wait_for_ci: yes - after_n_builds: 16 +coverage: + status: + patch: false + project: + default: + target: auto + threshold: 5% \ No newline at end of file diff --git a/.github/workflows/full.yml b/.github/workflows/full.yml index 598245b9450d..d2f0a23a3c32 100644 --- a/.github/workflows/full.yml +++ b/.github/workflows/full.yml @@ -52,19 +52,12 @@ jobs: - name: CVAT server. Build and push uses: docker/build-push-action@v3 with: - build-args: | - "COVERAGE_PROCESS_START=.coveragerc" cache-from: type=local,src=/tmp/cvat_cache_server context: . file: Dockerfile tags: cvat/server outputs: type=docker,dest=/tmp/cvat_server/image.tar - - name: Instrumentation of the code then rebuilding the CVAT UI - run: | - yarn --frozen-lockfile - yarn run coverage - - name: CVAT UI. Build and push uses: docker/build-push-action@v3 with: @@ -161,8 +154,6 @@ jobs: - name: Running REST API and SDK tests id: run_tests - env: - COVERAGE_PROCESS_START: ".coveragerc" run: | pip3 install -r cvat-sdk/gen/requirements.txt ./cvat-sdk/gen/generate.sh @@ -171,12 +162,7 @@ jobs: pip3 install -e ./cvat-sdk pip3 install -e ./cvat-cli - pytest tests/python/ --cov --cov-report xml - - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} + pytest tests/python/ - name: Creating a log file from cvat containers if: failure() && steps.run_tests.conclusion == 'failure' @@ -236,15 +222,10 @@ jobs: while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done 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 && coverage json && mv coverage.json ${CONTAINER_COVERAGE_DATA_DIR}' + -c 'python manage.py test cvat/apps -v 2' docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ - -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test && mv cvat-core/reports/coverage/coverage-final.json ${CONTAINER_COVERAGE_DATA_DIR}' - - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} + -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test' - name: Creating a log file from cvat containers if: failure() @@ -340,19 +321,16 @@ jobs: npx cypress run \ --headed \ --browser chrome \ + --env coverage=false \ --config-file cypress_canvas3d.config.js \ --spec 'cypress/e2e/${{ matrix.specs }}/**/*.js,cypress/e2e/remove_users_tasks_projects_organizations.js' else npx cypress run \ --browser chrome \ + --env coverage=false \ --spec 'cypress/e2e/${{ matrix.specs }}/**/*.js,cypress/e2e/remove_users_tasks_projects_organizations.js' fi - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - name: Creating a log file from "cvat" container logs if: failure() run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 656e382bcb30..023645d52380 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,12 +167,14 @@ jobs: env: COVERAGE_PROCESS_START: ".coveragerc" run: | - pytest tests/python/ --cov --cov-report xml + pytest tests/python/ --cov --cov-report=json - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 + - name: Uploading code coverage results as an artifact + uses: actions/upload-artifact@v3.1.1 with: - token: ${{ secrets.CODECOV_TOKEN }} + name: coverage_results + path: | + coverage*.json - name: Creating a log file from cvat containers if: failure() && steps.run_tests.conclusion == 'failure' @@ -230,15 +232,18 @@ jobs: while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done 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 && coverage json && mv coverage.json ${CONTAINER_COVERAGE_DATA_DIR}' + -c 'coverage run -a manage.py test cvat/apps && coverage json && mv coverage.json ${CONTAINER_COVERAGE_DATA_DIR}/unit_tests_coverage.json' docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test && mv cvat-core/reports/coverage/coverage-final.json ${CONTAINER_COVERAGE_DATA_DIR}' - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 + - name: Uploading code coverage results as an artifact + uses: actions/upload-artifact@v3.1.1 with: - token: ${{ secrets.CODECOV_TOKEN }} + name: coverage_results + path: | + ${{ github.workspace }}/coverage-final.json + ${{ github.workspace }}/unit_tests_coverage.json - name: Creating a log file from cvat containers if: failure() @@ -339,11 +344,14 @@ jobs: --browser chrome \ --spec 'cypress/e2e/${{ matrix.specs }}/**/*.js,cypress/e2e/remove_users_tasks_projects_organizations.js' fi + mv coverage/coverage-final.json coverage/${{ matrix.specs }}_coverage.json - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 + - name: Uploading code coverage results as an artifact + uses: actions/upload-artifact@v3.1.1 with: - token: ${{ secrets.CODECOV_TOKEN }} + name: coverage_results + path: | + tests/coverage/${{ matrix.specs }}_coverage.json - name: Creating a log file from "cvat" container logs if: failure() @@ -463,3 +471,19 @@ jobs: docker tag cvat/ui:latest "${UI_IMAGE_REPO}:dev" docker push "${UI_IMAGE_REPO}:dev" + + codecov: + runs-on: ubuntu-latest + needs: [unit_testing, e2e_testing, rest_api_testing] + steps: + - uses: actions/checkout@v3 + + - name: Downloading coverage results + uses: actions/download-artifact@v3 + with: + name: coverage_results + + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index 80808b29159e..af521ccae138 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -88,8 +88,6 @@ jobs: - name: CVAT server. Build and push uses: docker/build-push-action@v3 with: - build-args: | - "COVERAGE_PROCESS_START=.coveragerc" cache-from: type=local,src=/tmp/cvat_cache_server context: . file: Dockerfile @@ -97,11 +95,6 @@ jobs: tags: ${{ steps.meta-server.outputs.tags }} labels: ${{ steps.meta-server.outputs.labels }} - - name: Instrumentation of the code then rebuilding the CVAT UI - run: | - yarn --frozen-lockfile - yarn run coverage - - name: CVAT UI. Build and push uses: docker/build-push-action@v3 with: @@ -165,8 +158,6 @@ jobs: ./opa test cvat/apps/iam/rules - name: REST API and SDK tests - env: - COVERAGE_PROCESS_START: ".coveragerc" run: | pip3 install -r cvat-sdk/gen/requirements.txt ./cvat-sdk/gen/generate.sh @@ -175,7 +166,7 @@ jobs: pip3 install -e ./cvat-sdk pip3 install -e ./cvat-cli - pytest tests/python/ --cov --cov-report xml + pytest tests/python/ pytest tests/python/ --stop-services - name: Unit tests @@ -188,18 +179,13 @@ jobs: while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done 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 && coverage json && mv coverage.json ${CONTAINER_COVERAGE_DATA_DIR}' + -c 'python manage.py test cvat/apps -v 2' docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ - -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test && mv cvat-core/reports/coverage/coverage-final.json ${CONTAINER_COVERAGE_DATA_DIR}' + -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test' docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml down -v - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - e2e_testing: needs: build runs-on: ubuntu-latest @@ -304,19 +290,16 @@ jobs: npx cypress run \ --headed \ --browser chrome \ + --env coverage=false \ --config-file cypress_canvas3d.config.js \ --spec 'cypress/e2e/${{ matrix.specs }}/**/*.js,cypress/e2e/remove_users_tasks_projects_organizations.js' else npx cypress run \ --browser chrome \ + --env coverage=false \ --spec 'cypress/e2e/${{ matrix.specs }}/**/*.js,cypress/e2e/remove_users_tasks_projects_organizations.js' fi - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - name: Creating a log file from "cvat" container logs if: failure() run: | diff --git a/cvat/rqworker.py b/cvat/rqworker.py index 78319ff2e841..d368a1ef2629 100644 --- a/cvat/rqworker.py +++ b/cvat/rqworker.py @@ -68,11 +68,13 @@ def execute_job(self, *args, **kwargs): if os.environ.get("COVERAGE_PROCESS_START"): import coverage + default_exit = os._exit - def coverage_exit(): + def coverage_exit(*args, **kwargs): cov = coverage.Coverage.current() - cov.stop() - cov.save() - os._exit(0) + if cov: + cov.stop() + cov.save() + default_exit(*args, **kwargs) os._exit = coverage_exit diff --git a/tests/python/requirements.txt b/tests/python/requirements.txt index f1d7333f1926..79cdfc6a992e 100644 --- a/tests/python/requirements.txt +++ b/tests/python/requirements.txt @@ -1,7 +1,7 @@ pytest==6.2.5 pytest-cases==3.6.13 pytest-timeout==2.1.0 -pytest-cov==4.0.0 +pytest-cov==4.1.0 requests==2.31.0 deepdiff==5.6.0 boto3==1.17.61 diff --git a/tests/python/rest_api/test_tasks.py b/tests/python/rest_api/test_tasks.py index 14ce2f5fbf6f..7c26b3b12291 100644 --- a/tests/python/rest_api/test_tasks.py +++ b/tests/python/rest_api/test_tasks.py @@ -2279,6 +2279,7 @@ def _init_tasks(cls): cls.data[key] = (task, dataset_file) + @pytest.mark.skip("Fails sometimes, needs to be fixed") @pytest.mark.parametrize( "task_kind, annotation_kind, expect_success", [ diff --git a/tests/python/shared/fixtures/init.py b/tests/python/shared/fixtures/init.py index 6b4f720f5b7d..ae849db92031 100644 --- a/tests/python/shared/fixtures/init.py +++ b/tests/python/shared/fixtures/init.py @@ -471,10 +471,10 @@ def collect_code_coverage_from_containers(): # get code coverage report docker_exec(container, "coverage combine", capture_output=False) - docker_exec(container, "coverage xml", capture_output=False) + docker_exec(container, "coverage json", capture_output=False) docker_cp( - f"{PREFIX}_{container}_1:home/django/coverage.xml", - f"coverage_{container}.xml", + f"{PREFIX}_{container}_1:home/django/coverage.json", + f"coverage_{container}.json", ) From 44382be02e98a29c28e9adf42fd7c56bd3bde298 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 10 Jul 2023 15:55:00 +0300 Subject: [PATCH 03/48] Fix overly broad exception conversion in `LambdaFunction.invoke` (#6394) The intent of the `try`/`except` statement is to catch accesses to missing members of the `data` dictionary, but due to the large amount of code in the `try` block, it may end up catching entirely unrelated `KeyError`s. Those unrelated `KeyError`s should not be converted to `ValidationError`s, since they might not have anything to do with input validation, and the conversion will make it harder to debug these exceptions. An example of these misapplied conversions is a recent bug where a `KeyError` was coming from inside `_get_image` (fixed by f6420eb7). To fix this, make sure to only catch `KeyError`s emitted by accesses to `data`. --- CHANGELOG.md | 3 + cvat/apps/lambda_manager/views.py | 206 +++++++++++++++--------------- 2 files changed, 108 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de738f233687..8091b53ef437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added missed auto_add argument to Issue model () - \[API\] Performance of several API endpoints () - \[API\] Invalid schema for the owner field in several endpoints () +- Some internal errors occurring during lambda function invocations + could be mistakenly reported as invalid requests + () - \[SDK\] Loading tasks that have been cached with the PyTorch adapter () - The problem with importing annotations if dataset has extra dots in filenames diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 5f154366bff9..370521964dbb 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -210,119 +210,123 @@ def invoke( is_interactive: Optional[bool] = False, request: Optional[Request] = None ): - try: - if db_job is not None and db_job.get_task_id() != db_task.id: - raise ValidationError("Job task id does not match task id", - code=status.HTTP_400_BAD_REQUEST - ) - - payload = {} - data = {k: v for k,v in data.items() if v is not None} - threshold = data.get("threshold") - if threshold: - payload.update({ "threshold": threshold }) - quality = data.get("quality") - mapping = data.get("mapping", {}) - - task_attributes = {} - mapping_by_default = {} - for db_label in (db_task.project.label_set if db_task.project_id else db_task.label_set).prefetch_related("attributespec_set").all(): - mapping_by_default[db_label.name] = { - 'name': db_label.name, - 'attributes': {} + if db_job is not None and db_job.get_task_id() != db_task.id: + raise ValidationError("Job task id does not match task id", + code=status.HTTP_400_BAD_REQUEST + ) + + payload = {} + data = {k: v for k,v in data.items() if v is not None} + + def mandatory_arg(name: str) -> Any: + try: + return data[name] + except KeyError: + raise ValidationError( + "`{}` lambda function was called without mandatory argument: {}" + .format(self.id, name), + code=status.HTTP_400_BAD_REQUEST) + + threshold = data.get("threshold") + if threshold: + payload.update({ "threshold": threshold }) + quality = data.get("quality") + mapping = data.get("mapping", {}) + + task_attributes = {} + mapping_by_default = {} + for db_label in (db_task.project.label_set if db_task.project_id else db_task.label_set).prefetch_related("attributespec_set").all(): + mapping_by_default[db_label.name] = { + 'name': db_label.name, + 'attributes': {} + } + task_attributes[db_label.name] = {} + for attribute in db_label.attributespec_set.all(): + task_attributes[db_label.name][attribute.name] = { + 'input_type': attribute.input_type, + 'values': attribute.values.split('\n') } - task_attributes[db_label.name] = {} - for attribute in db_label.attributespec_set.all(): - task_attributes[db_label.name][attribute.name] = { - 'input_type': attribute.input_type, - 'values': attribute.values.split('\n') - } - if not mapping: - # use mapping by default to avoid labels in mapping which - # don't exist in the task - mapping = mapping_by_default - else: - # filter labels in mapping which don't exist in the task - mapping = {k:v for k,v in mapping.items() if v['name'] in mapping_by_default} + if not mapping: + # use mapping by default to avoid labels in mapping which + # don't exist in the task + mapping = mapping_by_default + else: + # filter labels in mapping which don't exist in the task + mapping = {k:v for k,v in mapping.items() if v['name'] in mapping_by_default} - attr_mapping = { label: mapping[label]['attributes'] if 'attributes' in mapping[label] else {} for label in mapping } - mapping = { modelLabel: mapping[modelLabel]['name'] for modelLabel in mapping } + attr_mapping = { label: mapping[label]['attributes'] if 'attributes' in mapping[label] else {} for label in mapping } + mapping = { modelLabel: mapping[modelLabel]['name'] for modelLabel in mapping } - supported_attrs = {} - for func_label, func_attrs in self.func_attributes.items(): - if func_label not in mapping: - continue + supported_attrs = {} + for func_label, func_attrs in self.func_attributes.items(): + if func_label not in mapping: + continue - mapped_label = mapping[func_label] - mapped_attributes = attr_mapping.get(func_label, {}) - supported_attrs[func_label] = {} + mapped_label = mapping[func_label] + mapped_attributes = attr_mapping.get(func_label, {}) + supported_attrs[func_label] = {} - if mapped_attributes: - task_attr_names = [task_attr for task_attr in task_attributes[mapped_label]] - for attr in func_attrs: - mapped_attr = mapped_attributes.get(attr["name"]) - if mapped_attr in task_attr_names: - supported_attrs[func_label].update({ attr["name"]: task_attributes[mapped_label][mapped_attr] }) + if mapped_attributes: + task_attr_names = [task_attr for task_attr in task_attributes[mapped_label]] + for attr in func_attrs: + mapped_attr = mapped_attributes.get(attr["name"]) + if mapped_attr in task_attr_names: + supported_attrs[func_label].update({ attr["name"]: task_attributes[mapped_label][mapped_attr] }) - # Check job frame boundaries - if db_job: - task_data = db_task.data - data_start_frame = task_data.start_frame - step = task_data.get_frame_step() - - for key, desc in ( - ('frame', 'frame'), - ('frame0', 'start frame'), - ('frame1', 'end frame'), - ): - if key not in data: - continue + # Check job frame boundaries + if db_job: + task_data = db_task.data + data_start_frame = task_data.start_frame + step = task_data.get_frame_step() + + for key, desc in ( + ('frame', 'frame'), + ('frame0', 'start frame'), + ('frame1', 'end frame'), + ): + if key not in data: + continue - abs_frame_id = data_start_frame + data[key] * step - if not db_job.segment.contains_frame(abs_frame_id): - raise ValidationError(f"The {desc} is outside the job range", - code=status.HTTP_400_BAD_REQUEST) + abs_frame_id = data_start_frame + data[key] * step + if not db_job.segment.contains_frame(abs_frame_id): + raise ValidationError(f"The {desc} is outside the job range", + code=status.HTTP_400_BAD_REQUEST) - if self.kind == LambdaType.DETECTOR: - payload.update({ - "image": self._get_image(db_task, data["frame"], quality) - }) - elif self.kind == LambdaType.INTERACTOR: - payload.update({ - "image": self._get_image(db_task, data["frame"], quality), - "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({ - "image0": self._get_image(db_task, data["frame0"], quality), - "image1": self._get_image(db_task, data["frame1"], quality), - "boxes0": data["boxes0"], - "boxes1": data["boxes1"] - }) - max_distance = data.get("max_distance") - if max_distance: - payload.update({ - "max_distance": max_distance - }) - elif self.kind == LambdaType.TRACKER: + if self.kind == LambdaType.DETECTOR: + payload.update({ + "image": self._get_image(db_task, mandatory_arg("frame"), quality) + }) + elif self.kind == LambdaType.INTERACTOR: + payload.update({ + "image": self._get_image(db_task, mandatory_arg("frame"), quality), + "pos_points": mandatory_arg("pos_points")[2:] if self.startswith_box else mandatory_arg("pos_points"), + "neg_points": mandatory_arg("neg_points"), + "obj_bbox": mandatory_arg("pos_points")[0:2] if self.startswith_box else None + }) + elif self.kind == LambdaType.REID: + payload.update({ + "image0": self._get_image(db_task, mandatory_arg("frame0"), quality), + "image1": self._get_image(db_task, mandatory_arg("frame1"), quality), + "boxes0": mandatory_arg("boxes0"), + "boxes1": mandatory_arg("boxes1") + }) + max_distance = data.get("max_distance") + if max_distance: payload.update({ - "image": self._get_image(db_task, data["frame"], quality), - "shapes": data.get("shapes", []), - "states": data.get("states", []) + "max_distance": max_distance }) - else: - raise ValidationError( - '`{}` lambda function has incorrect type: {}' - .format(self.id, self.kind), - code=status.HTTP_500_INTERNAL_SERVER_ERROR) - except KeyError as err: + elif self.kind == LambdaType.TRACKER: + payload.update({ + "image": self._get_image(db_task, mandatory_arg("frame"), quality), + "shapes": data.get("shapes", []), + "states": data.get("states", []) + }) + else: raise ValidationError( - "`{}` lambda function was called without mandatory argument: {}" - .format(self.id, str(err)), - code=status.HTTP_400_BAD_REQUEST) + '`{}` lambda function has incorrect type: {}' + .format(self.id, self.kind), + code=status.HTTP_500_INTERNAL_SERVER_ERROR) if is_interactive and request: interactive_function_call_signal.send(sender=self, request=request) From c15a855339139f33db9c71b721d5f6451ea81c32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:47:01 +0300 Subject: [PATCH 04/48] Bump tough-cookie from 4.1.2 to 4.1.3 (#6445) Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
Release notes

Sourced from tough-cookie's releases.

4.1.3

Security fix for Prototype Pollution discovery in #282. This is a minor release, although output from the inspect utility is affected by this change, we felt this change was important enough to be pushed into the next patch.

Commits
  • 4ff4d29 4.1.3 release preparation, update the package and lib/version to 4.1.3. (#284)
  • 12d4747 Prevent prototype pollution in cookie memstore (#283)
  • f06b72d Fix documentation for store.findCookies, missing allowSpecialUseDomain proper...
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tough-cookie&package-manager=npm_and_yarn&previous-version=4.1.2&new-version=4.1.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opencv/cvat/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index cbfff391e155..b3ce26598505 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12414,9 +12414,9 @@ touch@^3.1.0: nopt "~1.0.10" tough-cookie@^4.0.0, tough-cookie@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== dependencies: psl "^1.1.33" punycode "^2.1.1" From a63f0f10aaab813565eeb24a19852c23fcc7e002 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:47:11 +0300 Subject: [PATCH 05/48] Bump stylelint from 13.13.1 to 15.10.1 (#6444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [stylelint](https://github.com/stylelint/stylelint) from 13.13.1 to 15.10.1.
Release notes

Sourced from stylelint's releases.

15.10.1

15.10.0

15.9.0

  • Added: insideFunctions: {"function": int} to number-max-precision (#6932) (@​romainmenke).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for border-radius shorthand (#6958) (@​mattxwang).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for border-width shorthand (#6956) (@​mattxwang).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for grid-column and grid-row (#6957) (@​mattxwang).

15.8.0

15.7.0

  • Added: splitList: boolean to selector-nested-pattern (#6896) (@​is2ei).
  • Fixed: unit-no-unknown false positives for unicode-range descriptors (#6892) (@​romainmenke).

... (truncated)

Changelog

Sourced from stylelint's changelog.

15.10.1

15.10.0

15.9.0

  • Added: insideFunctions: {"function": int} to number-max-precision (#6932) (@​romainmenke).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for border-radius shorthand (#6958) (@​mattxwang).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for border-width shorthand (#6956) (@​mattxwang).
  • Fixed: declaration-block-no-redundant-longhand-properties autofix for grid-column and grid-row (#6957) (@​mattxwang).

15.8.0

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by mattxwang, a new releaser for stylelint since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=stylelint&package-manager=npm_and_yarn&previous-version=13.13.1&new-version=15.10.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opencv/cvat/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 609 +++++++++++++++++++++++---------------------------- 2 files changed, 276 insertions(+), 335 deletions(-) diff --git a/package.json b/package.json index 14efb012c4ae..93b410dfadac 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "sass-loader": "^10.0.0", "source-map-support": "^0.5.19", "style-loader": "^1.0.0", - "stylelint": "^13.6.1", + "stylelint": "^15.10.1", "stylelint-config-standard": "^20.0.0", "typescript": "^5.0.2", "vfile-reporter-json": "^2.0.2", diff --git a/yarn.lock b/yarn.lock index b3ce26598505..2257a5e986dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,7 +79,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.17.9", "@babel/core@^7.4.5", "@babel/core@^7.6.0", "@babel/core@^7.7.5": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.4.5", "@babel/core@^7.6.0", "@babel/core@^7.7.5": version "7.20.12" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== @@ -1069,6 +1069,26 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== +"@csstools/css-parser-algorithms@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz#0cc3a656dc2d638370ecf6f98358973bfbd00141" + integrity sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA== + +"@csstools/css-tokenizer@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz#07ae11a0a06365d7ec686549db7b729bc036528e" + integrity sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA== + +"@csstools/media-query-list-parser@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz#6ef642b728d30c1009bfbba3211c7e4c11302728" + integrity sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ== + +"@csstools/selector-specificity@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247" + integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g== + "@ctrl/tinycolor@^3.4.0": version "3.5.0" resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.5.0.tgz#6e52b3d1c38d13130101771821e09cdd414a16bc" @@ -1518,21 +1538,6 @@ dependencies: "@sinonjs/commons" "^2.0.0" -"@stylelint/postcss-css-in-js@^0.37.2": - version "0.37.3" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.3.tgz#d149a385e07ae365b0107314c084cb6c11adbf49" - integrity sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg== - dependencies: - "@babel/core" "^7.17.9" - -"@stylelint/postcss-markdown@^0.36.2": - version "0.36.2" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391" - integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ== - dependencies: - remark "^13.0.0" - unist-util-find-all-after "^3.0.2" - "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -1789,7 +1794,7 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== -"@types/minimist@^1.2.0": +"@types/minimist@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== @@ -1819,11 +1824,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - "@types/parse5@^6.0.0": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" @@ -2642,6 +2642,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + aria-query@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" @@ -2754,7 +2759,7 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -autoprefixer@^9.6.1, autoprefixer@^9.8.6: +autoprefixer@^9.6.1: version "9.8.8" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== @@ -3242,21 +3247,22 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== +camelcase-keys@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" + integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" + camelcase "^6.3.0" + map-obj "^4.1.0" + quick-lru "^5.1.1" + type-fest "^1.2.1" camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: +camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -3318,7 +3324,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3478,13 +3484,6 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-regexp@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" - integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q== - dependencies: - is-regexp "^2.0.0" - clone-response@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" @@ -3560,6 +3559,11 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" @@ -3753,16 +3757,15 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== +cosmiconfig@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" + integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== dependencies: - "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" + js-yaml "^4.1.0" parse-json "^5.0.0" path-type "^4.0.0" - yaml "^1.10.0" cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -3780,6 +3783,11 @@ css-blank-pseudo@^0.1.4: dependencies: postcss "^7.0.5" +css-functions-list@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b" + integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w== + css-has-pseudo@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" @@ -3861,6 +3869,14 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" @@ -4023,6 +4039,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decamelize@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" + integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== + decimal.js@^10.3.1, decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -4265,7 +4286,7 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -domelementtype@1, domelementtype@^1.3.1: +domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -4282,13 +4303,6 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -4296,7 +4310,7 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" -domutils@^1.5.1, domutils@^1.7.0: +domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== @@ -4415,11 +4429,6 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -4894,13 +4903,6 @@ execa@^6.1.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" -execall@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" - integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow== - dependencies: - clone-regexp "^2.1.0" - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4972,10 +4974,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.11, fast-glob@^3.2.5, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" + integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -4993,7 +4995,7 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fastest-levenshtein@^1.0.12: +fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== @@ -5089,6 +5091,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -5252,11 +5262,6 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stdin@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== - get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -5388,13 +5393,6 @@ globjoin@^0.1.4: resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== -gonzales-pe@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" - integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== - dependencies: - minimist "^1.2.5" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -5667,11 +5665,6 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - hosted-git-info@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" @@ -5719,10 +5712,10 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-tags@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" - integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== html-void-elements@^2.0.0: version "2.0.1" @@ -5740,18 +5733,6 @@ html-webpack-plugin@^5.5.0: pretty-error "^4.0.0" tapable "^2.0.0" -htmlparser2@^3.10.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -5881,7 +5862,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.0, ignore@^5.1.8, ignore@^5.2.0: +ignore@^5.0.0, ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -5954,6 +5935,11 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +indent-string@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" + integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -6255,6 +6241,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -6268,11 +6259,6 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-regexp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" - integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== - is-relative-url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-relative-url/-/is-relative-url-2.0.0.tgz#72902d7fe04b3d4792e7db15f9db84b7204c9cef" @@ -6332,11 +6318,6 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -6904,6 +6885,13 @@ js-yaml@^3.13.1, js-yaml@^3.6.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsdom@^19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" @@ -7095,10 +7083,10 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== -known-css-properties@^0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d" - integrity sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw== +known-css-properties@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.27.0.tgz#82a9358dda5fe7f7bd12b5e7142c0a205393c0c5" + integrity sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg== language-subtag-registry@~0.3.2: version "0.3.22" @@ -7238,6 +7226,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash-es@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" @@ -7338,14 +7333,6 @@ lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - log-update@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" @@ -7453,7 +7440,7 @@ map-obj@^1.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== -map-obj@^4.0.0: +map-obj@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== @@ -7735,6 +7722,11 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -7766,23 +7758,23 @@ memoize-one@^6.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" - integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== +meow@^10.1.5: + version "10.1.5" + resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f" + integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw== dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" + "@types/minimist" "^1.2.2" + camelcase-keys "^7.0.0" + decamelize "^5.0.0" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" + normalize-package-data "^3.0.2" + read-pkg-up "^8.0.0" + redent "^4.0.0" + trim-newlines "^4.0.2" + type-fest "^1.2.2" + yargs-parser "^20.2.9" merge-descriptors@1.0.1: version "1.0.1" @@ -8188,7 +8180,7 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== -min-indent@^1.0.0: +min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== @@ -8214,7 +8206,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== @@ -8296,6 +8288,11 @@ nan@^2.17.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -8383,17 +8380,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: +normalize-package-data@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== @@ -8413,11 +8400,6 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -normalize-selector@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" - integrity sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw== - normalize-url@^4.1.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" @@ -8733,7 +8715,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -8754,6 +8736,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -9139,13 +9128,6 @@ postcss-gap-properties@^2.0.0: dependencies: postcss "^7.0.2" -postcss-html@^0.36.0: - version "0.36.0" - resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" - integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== - dependencies: - htmlparser2 "^3.10.0" - postcss-image-set-function@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" @@ -9170,13 +9152,6 @@ postcss-lab-function@^2.0.1: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-less@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" - integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA== - dependencies: - postcss "^7.0.14" - postcss-load-config@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" @@ -9209,11 +9184,6 @@ postcss-media-minmax@^4.0.0: dependencies: postcss "^7.0.2" -postcss-media-query-parser@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" - integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== - postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" @@ -9339,27 +9309,10 @@ postcss-resolve-nested-selector@^0.1.1: resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" integrity sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw== -postcss-safe-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" - integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== - dependencies: - postcss "^7.0.26" - -postcss-sass@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" - integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== - dependencies: - gonzales-pe "^4.3.0" - postcss "^7.0.21" - -postcss-scss@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" - integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== - dependencies: - postcss "^7.0.6" +postcss-safe-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1" + integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ== postcss-selector-matches@^4.0.0: version "4.0.0" @@ -9386,20 +9339,15 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.5: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-syntax@^0.36.2: - version "0.36.2" - resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" - integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== - -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -9413,7 +9361,7 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.39" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== @@ -9421,6 +9369,15 @@ postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.2 picocolors "^0.2.1" source-map "^0.6.1" +postcss@^8.4.24: + version "8.4.25" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f" + integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9602,10 +9559,10 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== quickhull@^1.0.3: version "1.0.3" @@ -10250,24 +10207,24 @@ reactcss@^1.2.0: dependencies: lodash "^4.0.1" -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== +read-pkg-up@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670" + integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ== dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" + find-up "^5.0.0" + read-pkg "^6.0.0" + type-fest "^1.0.1" -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== +read-pkg@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c" + integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q== dependencies: "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" + normalize-package-data "^3.0.2" + parse-json "^5.2.0" + type-fest "^1.0.1" readable-stream@^2.0.1, readable-stream@~2.3.6: version "2.3.7" @@ -10282,7 +10239,7 @@ readable-stream@^2.0.1, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -10305,13 +10262,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== +redent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9" + integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag== dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" + indent-string "^5.0.0" + strip-indent "^4.0.0" redux-devtools-extension@^2.13.9: version "2.13.9" @@ -11317,7 +11274,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.1.tgz#cee884cd4e3f355660e501fa3276b27d7ffe5a20" integrity sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw== -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.9.0: +resolve@^1.12.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.9.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -11532,11 +11489,6 @@ selfsigned@^2.0.1: dependencies: node-forge "^1" -"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -11544,6 +11496,11 @@ semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^ dependencies: lru-cache "^6.0.0" +semver@^5.6.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -11661,6 +11618,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" + integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -11747,7 +11709,7 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -"source-map-js@>=0.6.2 <2.0.0": +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -11851,11 +11813,6 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -specificity@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" - integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -11923,7 +11880,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -12043,12 +12000,12 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== +strip-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853" + integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA== dependencies: - min-indent "^1.0.0" + min-indent "^1.0.1" strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" @@ -12094,66 +12051,51 @@ stylelint-config-standard@^20.0.0: dependencies: stylelint-config-recommended "^3.0.0" -stylelint@^13.6.1: - version "13.13.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.13.1.tgz#fca9c9f5de7990ab26a00f167b8978f083a18f3c" - integrity sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ== +stylelint@^15.10.1: + version "15.10.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.10.1.tgz#93f189958687e330c106b010cbec0c41dcae506d" + integrity sha512-CYkzYrCFfA/gnOR+u9kJ1PpzwG10WLVnoxHDuBA/JiwGqdM9+yx9+ou6SE/y9YHtfv1mcLo06fdadHTOx4gBZQ== dependencies: - "@stylelint/postcss-css-in-js" "^0.37.2" - "@stylelint/postcss-markdown" "^0.36.2" - autoprefixer "^9.8.6" + "@csstools/css-parser-algorithms" "^2.3.0" + "@csstools/css-tokenizer" "^2.1.1" + "@csstools/media-query-list-parser" "^2.1.2" + "@csstools/selector-specificity" "^3.0.0" balanced-match "^2.0.0" - chalk "^4.1.1" - cosmiconfig "^7.0.0" - debug "^4.3.1" - execall "^2.0.0" - fast-glob "^3.2.5" - fastest-levenshtein "^1.0.12" + colord "^2.9.3" + cosmiconfig "^8.2.0" + css-functions-list "^3.1.0" + css-tree "^2.3.1" + debug "^4.3.4" + fast-glob "^3.3.0" + fastest-levenshtein "^1.0.16" file-entry-cache "^6.0.1" - get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.3" + globby "^11.1.0" globjoin "^0.1.4" - html-tags "^3.1.0" - ignore "^5.1.8" + html-tags "^3.3.1" + ignore "^5.2.4" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.21.0" - lodash "^4.17.21" - log-symbols "^4.1.0" + is-plain-object "^5.0.0" + known-css-properties "^0.27.0" mathml-tag-names "^2.1.3" - meow "^9.0.0" - micromatch "^4.0.4" - normalize-selector "^0.2.0" - postcss "^7.0.35" - postcss-html "^0.36.0" - postcss-less "^3.1.4" - postcss-media-query-parser "^0.2.3" + meow "^10.1.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.24" postcss-resolve-nested-selector "^0.1.1" - postcss-safe-parser "^4.0.2" - postcss-sass "^0.4.4" - postcss-scss "^2.1.1" - postcss-selector-parser "^6.0.5" - postcss-syntax "^0.36.2" - postcss-value-parser "^4.1.0" + postcss-safe-parser "^6.0.0" + postcss-selector-parser "^6.0.13" + postcss-value-parser "^4.2.0" resolve-from "^5.0.0" - slash "^3.0.0" - specificity "^0.4.1" - string-width "^4.2.2" - strip-ansi "^6.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" style-search "^0.1.0" - sugarss "^2.0.0" + supports-hyperlinks "^3.0.0" svg-tags "^1.0.0" - table "^6.6.0" - v8-compile-cache "^2.3.0" - write-file-atomic "^3.0.3" - -sugarss@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" - integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== - dependencies: - postcss "^7.0.2" + table "^6.8.1" + write-file-atomic "^5.0.1" supports-color@^2.0.0: version "2.0.0" @@ -12174,7 +12116,7 @@ supports-color@^6.0.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -12188,6 +12130,14 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz#c711352a5c89070779b4dad54c05a2f14b15c94b" + integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -12263,7 +12213,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.9, table@^6.6.0: +table@^6.0.9, table@^6.8.1: version "6.8.1" resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== @@ -12440,10 +12390,10 @@ trim-lines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +trim-newlines@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125" + integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ== trim-right@^1.0.1: version "1.0.1" @@ -12533,11 +12483,6 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -12553,16 +12498,16 @@ type-fest@^0.3.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.0, type-fest@^0.8.1: +type-fest@^0.8.0: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -12738,13 +12683,6 @@ unist-util-filter@^4.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.0.0" -unist-util-find-all-after@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz#fdfecd14c5b7aea5e9ef38d5e0d5f774eeb561f6" - integrity sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ== - dependencies: - unist-util-is "^4.0.0" - unist-util-generated@^1.0.0, unist-util-generated@^1.1.0: version "1.1.6" resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" @@ -12947,7 +12885,7 @@ uvu@^0.5.0: kleur "^4.0.3" sade "^1.7.3" -v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: +v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== @@ -13378,7 +13316,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: +write-file-atomic@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -13396,6 +13334,14 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + ws@^8.11.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" @@ -13441,11 +13387,6 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - yaml@^2.1.3: version "2.2.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4" @@ -13459,7 +13400,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: +yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From 1e18713aa7508a75d217ff1397128ff99c38359d Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 12 Jul 2023 12:26:27 +0300 Subject: [PATCH 06/48] Allow attribute default/permitted values to be blank (#6454) This is de facto already allowed when CVAT is accessed via the UI/API, even though the model/serializer configuration seem to prohibit it. That's because validation for attribute properties is effectively disabled on the server due to a bug. However, the SDK checks the schema, and thus doesn't allow such values. I think blank default values make sense (particularly for "text" type attributes), so I updated the code to allow them. Blank permitted values don't make quite as much sense, but I had to allow them too, because the UI always submits the default value as the first permitted value (even for freeform text attributes). This will let us fix the broken validation on the server side (which I'm planning to do soon) without removing the ability to set blank default attribute values via the UI. (FWIW, I don't think that the UI should add a `values` property when serializing freeform text attributes at all, but it would take a more substantial change to fix that, which I don't have time for right now.) --- CHANGELOG.md | 3 ++- ...er_attributespec_default_value_and_more.py | 22 +++++++++++++++++ cvat/apps/engine/models.py | 4 ++-- cvat/apps/engine/serializers.py | 2 +- cvat/schema.yml | 4 ---- tests/python/sdk/test_projects.py | 24 +++++++++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 cvat/apps/engine/migrations/0073_alter_attributespec_default_value_and_more.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8091b53ef437..7800f0fc6bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - TDB ### Fixed -- TDB +- \[SDK\] Ability to create attributes with blank default values + () ### Security - TDB diff --git a/cvat/apps/engine/migrations/0073_alter_attributespec_default_value_and_more.py b/cvat/apps/engine/migrations/0073_alter_attributespec_default_value_and_more.py new file mode 100644 index 000000000000..7bfe361c875b --- /dev/null +++ b/cvat/apps/engine/migrations/0073_alter_attributespec_default_value_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.1 on 2023-07-10 15:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("engine", "0072_alter_issue_updated_date"), + ] + + operations = [ + migrations.AlterField( + model_name="attributespec", + name="default_value", + field=models.CharField(blank=True, max_length=128), + ), + migrations.AlterField( + model_name="attributespec", + name="values", + field=models.CharField(blank=True, max_length=4096), + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index 5e9f6781eb2c..8b1ee700271f 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -881,8 +881,8 @@ class AttributeSpec(models.Model): mutable = models.BooleanField() input_type = models.CharField(max_length=16, choices=AttributeType.choices()) - default_value = models.CharField(max_length=128) - values = models.CharField(max_length=4096) + default_value = models.CharField(blank=True, max_length=128) + values = models.CharField(blank=True, max_length=4096) class Meta: default_permissions = () diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index a92df38a0a22..f3303ea0ab03 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -214,7 +214,7 @@ class Meta: class AttributeSerializer(serializers.ModelSerializer): values = serializers.ListField(allow_empty=True, - child=serializers.CharField(max_length=200), + child=serializers.CharField(allow_blank=True, max_length=200), ) class Meta: diff --git a/cvat/schema.yml b/cvat/schema.yml index 47a68af2e933..cbb4310c538e 100644 --- a/cvat/schema.yml +++ b/cvat/schema.yml @@ -5993,7 +5993,6 @@ components: type: string maxLength: 200 required: - - default_value - input_type - mutable - name @@ -6011,16 +6010,13 @@ components: $ref: '#/components/schemas/InputTypeEnum' default_value: type: string - minLength: 1 maxLength: 128 values: type: array items: type: string - minLength: 1 maxLength: 200 required: - - default_value - input_type - mutable - name diff --git a/tests/python/sdk/test_projects.py b/tests/python/sdk/test_projects.py index 74ecf5e69084..852a286fc288 100644 --- a/tests/python/sdk/test_projects.py +++ b/tests/python/sdk/test_projects.py @@ -115,6 +115,30 @@ def test_can_create_empty_project(self): assert project.id != 0 assert project.name == "test project" + def test_can_create_project_with_attribute_with_blank_default(self): + project = self.client.projects.create( + spec=models.ProjectWriteRequest( + name="test project", + labels=[ + models.PatchedLabelRequest( + name="text", + attributes=[ + models.AttributeRequest( + name="text", + mutable=True, + input_type=models.InputTypeEnum("text"), + values=[], + default_value="", + ) + ], + ) + ], + ) + ) + + labels = project.get_labels() + assert labels[0].attributes[0].default_value == "" + def test_can_create_project_from_dataset(self, fxt_coco_dataset: Path): pbar_out = io.StringIO() pbar = make_pbar(file=pbar_out) From 03975ea4050347fd4d608bbbedf81f74331210c0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 12 Jul 2023 14:42:05 +0300 Subject: [PATCH 07/48] Supported multi-line input, fixed issue in aam with losing selection (#6458) ### Motivation and context ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [x] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [x] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- CHANGELOG.md | 2 +- cvat-canvas/package.json | 2 +- cvat-canvas/src/typescript/canvasView.ts | 34 +++++++--- cvat-ui/package.json | 2 +- .../attribute-editor.tsx | 67 ++++++++++++++----- .../object-item-attribute.tsx | 51 ++++++-------- .../components/labels-editor/label-form.tsx | 8 +++ .../e2e/actions_tasks/mutable_attributes.js | 2 +- .../issues_prs/issue_1919_check_text_attr.js | 27 ++++++++ ...d_attribute_correspond_chosen_attribute.js | 4 +- tests/cypress/support/commands.js | 2 +- 11 files changed, 137 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7800f0fc6bb7..3ae14be49c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[Unreleased] ### Added -- TDB +- Multi-line text attributes supported () ### Changed - TDB diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 68b60997cca0..64ac4c6e81f7 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.17.0", + "version": "2.17.1", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index fa886c872831..220a3de19028 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -2667,9 +2667,20 @@ export class CanvasViewImpl implements CanvasView, Listener { }); } - for (const tspan of (text.lines() as any).members) { - tspan.attr('x', text.attr('x')); + function applyParentX(parentText: SVGTSpanElement | SVGTextElement): void { + for (let i = 0; i < parentText.children.length; i++) { + if (i === 0) { + // do not align the first child + continue; + } + + const tspan = parentText.children[i]; + tspan.setAttribute('x', parentText.getAttribute('x')); + applyParentX(tspan as SVGTSpanElement); + } } + + applyParentX(text.node as any as SVGTextElement); } private deleteText(clientID: number): void { @@ -2733,15 +2744,16 @@ export class CanvasViewImpl implements CanvasView, Listener { } if (withAttr) { for (const attrID of Object.keys(attributes)) { - const value = attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID]; - block - .tspan(`${attrNames[attrID]}: ${value}`) - .attr({ - attrID, - dy: '1em', - x: 0, - }) - .addClass('cvat_canvas_text_attribute'); + const values = `${attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID]}`.split('\n'); + const parent = block.tspan(`${attrNames[attrID]}: `) + .attr({ attrID, dy: '1em', x: 0 }).addClass('cvat_canvas_text_attribute'); + values.forEach((attrLine: string, index: number) => { + parent + .tspan(attrLine) + .attr({ + dy: index === 0 ? 0 : '1em', + }); + }); } } }) diff --git a/cvat-ui/package.json b/cvat-ui/package.json index f96a51c6dc7a..f506b0e81a1b 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.53.0", + "version": "1.53.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index e472692e1b01..707a7a6c556c 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -2,15 +2,16 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import Text from 'antd/lib/typography/Text'; import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; import Select, { SelectValue } from 'antd/lib/select'; import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Input from 'antd/lib/input'; +import { TextAreaRef } from 'antd/lib/input/TextArea'; +import InputNumber from 'antd/lib/input-number'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; - import config from 'config'; interface InputElementParameters { @@ -27,6 +28,17 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { inputType, attrID, clientID, values, currentValue, onChange, } = parameters; + const ref = useRef(null); + const [selectionStart, setSelectionStart] = useState(currentValue.length); + + useEffect(() => { + const textArea = ref?.current?.resizableTextArea?.textArea; + if (textArea instanceof HTMLTextAreaElement) { + textArea.selectionStart = selectionStart; + textArea.selectionEnd = selectionStart; + } + }, [currentValue]); + const renderCheckbox = (): JSX.Element => ( <> Checkbox: @@ -77,34 +89,55 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { ); - const handleKeydown = (event: React.KeyboardEvent): void => { + const handleKeydown = (event: React.KeyboardEvent): void => { if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Tab', 'Shift', 'Control'].includes(event.key)) { event.preventDefault(); + event.stopPropagation(); const copyEvent = new KeyboardEvent('keydown', event); window.document.dispatchEvent(copyEvent); } }; + const renderNumber = (): JSX.Element => { + const [min, max, step] = values; + return ( + <> + Number: +
+ { + if (typeof value === 'number') { + onChange(`${value}`); + } + }} + onKeyDown={handleKeydown} + /> +
+ + ); + }; + const renderText = (): JSX.Element => ( <> - {inputType === 'number' ? Number: : Text: } + Text:
- ) => { + value={currentValue} + onChange={(event: React.ChangeEvent) => { const { value } = event.target; - if (inputType === 'number') { - if (value !== '') { - const numberValue = +value; - if (!Number.isNaN(numberValue)) { - onChange(`${numberValue}`); - } - } - } else { - onChange(value); + if (ref.current?.resizableTextArea?.textArea) { + setSelectionStart(ref.current.resizableTextArea.textArea.selectionStart); } + onChange(value); }} onKeyDown={handleKeydown} /> @@ -119,6 +152,8 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { element = renderSelect(); } else if (inputType === 'radio') { element = renderRadio(); + } else if (inputType === 'number') { + element = renderNumber(); } else { element = renderText(); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx index ca930a0fc818..53f28619fb4e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-attribute.tsx @@ -7,12 +7,12 @@ import { Col } from 'antd/lib/grid'; import Select from 'antd/lib/select'; import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; -import Input from 'antd/lib/input'; import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; import config from 'config'; import { clamp } from 'utils/math'; +import TextArea, { TextAreaRef } from 'antd/lib/input/TextArea'; interface Props { readonly: boolean; @@ -39,10 +39,21 @@ function attrIsTheSame(prevProps: Props, nextProps: Props): boolean { function ItemAttributeComponent(props: Props): JSX.Element { const { - attrInputType, attrValues, attrValue, attrName, attrID, readonly, changeAttribute, + attrInputType, attrValues, attrValue, + attrName, attrID, readonly, changeAttribute, } = props; const attrNameStyle: React.CSSProperties = { wordBreak: 'break-word', lineHeight: '1em', fontSize: 12 }; + const ref = useRef(null); + const [selectionStart, setSelectionStart] = useState(attrValue.length); + + useEffect(() => { + const textArea = ref?.current?.resizableTextArea?.textArea; + if (textArea instanceof HTMLTextAreaElement) { + textArea.selectionStart = selectionStart; + textArea.selectionEnd = selectionStart; + } + }, [attrValue]); if (attrInputType === 'checkbox') { return ( @@ -150,44 +161,24 @@ function ItemAttributeComponent(props: Props): JSX.Element { ); } - const ref = useRef(null); - const [selection, setSelection] = useState<{ - start: number | null; - end: number | null; - direction: 'forward' | 'backward' | 'none' | null; - }>({ - start: null, - end: null, - direction: null, - }); - - useEffect(() => { - if (ref.current && ref.current.input) { - ref.current.input.selectionStart = selection.start; - ref.current.input.selectionEnd = selection.end; - ref.current.input.selectionDirection = selection.direction; - } - }, [attrValue]); - return ( <> {attrName} - ): void => { - if (ref.current && ref.current.input) { - setSelection({ - start: ref.current.input.selectionStart, - end: ref.current.input.selectionEnd, - direction: ref.current.input.selectionDirection, - }); + style={{ + height: Math.min(120, attrValue.split('\n').length * 24), + minHeight: Math.min(120, attrValue.split('\n').length * 24), + }} + onChange={(event: React.ChangeEvent): void => { + if (ref.current?.resizableTextArea?.textArea) { + setSelectionStart(ref.current.resizableTextArea.textArea.selectionStart); } - changeAttribute(attrID, event.target.value); }} value={attrValue} diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 7bf295af5645..72fdbc90b0a5 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -317,6 +317,14 @@ export default class LabelForm extends React.Component { const { key } = fieldInstance; const value = attr ? attr.values[0] : ''; + if (attr?.input_type.toUpperCase() === 'TEXT') { + return ( + + + + ); + } + return ( diff --git a/tests/cypress/e2e/actions_tasks/mutable_attributes.js b/tests/cypress/e2e/actions_tasks/mutable_attributes.js index 27b40aa92b7d..04a91b99191d 100644 --- a/tests/cypress/e2e/actions_tasks/mutable_attributes.js +++ b/tests/cypress/e2e/actions_tasks/mutable_attributes.js @@ -31,7 +31,7 @@ context('Mutable attribute.', () => { function testChangingAttributeValue(expectedValue, value) { cy.get('.cvat-player-next-button').click(); cy.get('.attribute-annotation-sidebar-attr-elem-wrapper') - .find('[type="text"]') + .find('textarea') .should('have.value', expectedValue) .clear() .type(value); diff --git a/tests/cypress/e2e/issues_prs/issue_1919_check_text_attr.js b/tests/cypress/e2e/issues_prs/issue_1919_check_text_attr.js index 44462446c1c1..abb6ea94f44a 100644 --- a/tests/cypress/e2e/issues_prs/issue_1919_check_text_attr.js +++ b/tests/cypress/e2e/issues_prs/issue_1919_check_text_attr.js @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 Intel Corporation // // SPDX-License-Identifier: MIT @@ -30,9 +31,11 @@ context('Check label attribute changes', () => { cy.createRectangle(createRectangleShape2Points); cy.get('#cvat_canvas_shape_1').trigger('mousemove').rightclick(); }); + it('Open object menu details', () => { cy.get('.cvat-canvas-context-menu').contains('DETAILS').click(); }); + it('Clear field of text attribute and write new value', () => { cy.get('.cvat-canvas-context-menu') .contains(attrName) @@ -44,6 +47,7 @@ context('Check label attribute changes', () => { .type(newLabelAttrValue); }); }); + it('Check what value of right panel is changed too', () => { cy.get('#cvat-objects-sidebar-state-item-1') .contains(attrName) @@ -52,5 +56,28 @@ context('Check label attribute changes', () => { cy.get('.cvat-object-item-text-attribute').should('have.value', newLabelAttrValue); }); }); + + it('Specify many lines for a text attribute, update the page and check values', () => { + const multilineValue = 'This text attributes has many lines.\n - Line 1\n - Line 2'; + cy.get('.cvat-canvas-context-menu') + .contains(attrName) + .parents('.cvat-object-item-attribute-wrapper') + .within(() => { + cy.get('.cvat-object-item-text-attribute') + .clear() + .type(multilineValue); + }); + cy.saveJob(); + cy.reload(); + cy.get('.cvat-canvas-container').should('exist').and('be.visible'); + cy.get('#cvat-objects-sidebar-state-item-1') + .contains('DETAILS').click(); + cy.get('#cvat-objects-sidebar-state-item-1') + .contains(attrName) + .parents('.cvat-object-item-attribute-wrapper') + .within(() => { + cy.get('.cvat-object-item-text-attribute').should('have.value', multilineValue); + }); + }); }); }); diff --git a/tests/cypress/e2e/issues_prs2/issue_1425_highlighted_attribute_correspond_chosen_attribute.js b/tests/cypress/e2e/issues_prs2/issue_1425_highlighted_attribute_correspond_chosen_attribute.js index 9727f8e6442b..6b67969f8193 100644 --- a/tests/cypress/e2e/issues_prs2/issue_1425_highlighted_attribute_correspond_chosen_attribute.js +++ b/tests/cypress/e2e/issues_prs2/issue_1425_highlighted_attribute_correspond_chosen_attribute.js @@ -38,7 +38,7 @@ context('The highlighted attribute in AAM should correspond to the chosen attrib }); }); cy.get('.cvat-attribute-annotation-sidebar-attr-editor').within(() => { - cy.get('[type="text"]').should('have.value', textValue); + cy.get('textarea').should('have.value', textValue); }); }); it('Go to next attribute and check again', () => { @@ -49,7 +49,7 @@ context('The highlighted attribute in AAM should correspond to the chosen attrib }); }); cy.get('.cvat-attribute-annotation-sidebar-attr-editor').within(() => { - cy.get('[type="text"]').should('have.value', textValue); + cy.get('textarea').should('have.value', textValue); }); }); }); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 88fbf076ff84..2aa532b492ab 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -526,7 +526,7 @@ Cypress.Commands.add('createPolygon', (createPolygonParams) => { Cypress.Commands.add('openSettings', () => { cy.get('.cvat-right-header').find('.cvat-header-menu-user-dropdown').trigger('mouseover', { which: 1 }); - cy.get('.anticon-setting').click(); + cy.get('.anticon-setting').should('exist').and('be.visible').click(); cy.get('.cvat-settings-modal').should('be.visible'); }); From 31b5e25ca5f963b66f43adf14c03486cf7b374c9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 12 Jul 2023 16:27:26 +0300 Subject: [PATCH 08/48] Keep model input data unchanged in SDK (#6455) Nested containers may be modified during the model input data parsing in class constructors. This can lead to subtle memory errors, which are very hard to find. In CVAT, this helps to avoid unexpected problems in tests, such as one test affecting another one by subtly changing test assets. --- CHANGELOG.md | 1 + .../openapi-generator/model_utils.mustache | 62 +++++----- tests/python/sdk/test_api_wrappers.py | 114 ++++++++++++++++++ 3 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 tests/python/sdk/test_api_wrappers.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae14be49c2a..a08bcdf77f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - \[SDK\] Ability to create attributes with blank default values () +- \[SDK\] SDK should not change input data in models () ### Security - TDB diff --git a/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache b/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache index 09887e414b2e..c9e2b70d77bb 100644 --- a/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache +++ b/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache @@ -1,7 +1,7 @@ {{>partial_header}} from datetime import date, datetime # noqa: F401 -from copy import deepcopy +from copy import copy, deepcopy import inspect import io import os @@ -1310,14 +1310,18 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, if inner_required_types is None: # for this type, there are not more inner variables left to look at return input_value + if isinstance(input_value, list): - if input_value == []: + # avoid storing and changing the input value when the type is mutable collection + output_value = copy(input_value) + + if output_value == []: # allow an empty list - return input_value - for index, inner_value in enumerate(input_value): + return output_value + for index, inner_value in enumerate(output_value): inner_path = list(path_to_item) inner_path.append(index) - input_value[index] = validate_and_convert_types( + output_value[index] = validate_and_convert_types( inner_value, inner_required_types, inner_path, @@ -1326,16 +1330,19 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, configuration=configuration ) elif isinstance(input_value, dict): - if input_value == {}: + # avoid storing and changing the input value when the type is mutable collection + output_value = copy(input_value) + + if output_value == {}: # allow an empty dict - return input_value - for inner_key, inner_val in input_value.items(): + return output_value + for inner_key, inner_val in output_value.items(): inner_path = list(path_to_item) inner_path.append(inner_key) if get_simple_class(inner_key) != str: raise get_type_error(inner_key, inner_path, valid_classes, key_type=True) - input_value[inner_key] = validate_and_convert_types( + output_value[inner_key] = validate_and_convert_types( inner_val, inner_required_types, inner_path, @@ -1343,7 +1350,10 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, _check_type, configuration=configuration ) - return input_value + else: + output_value = input_value + + return output_value def model_to_dict(model_instance, serialize=True): @@ -1382,24 +1392,20 @@ def model_to_dict(model_instance, serialize=True): except KeyError: used_fallback_python_attribute_names.add(attr) if isinstance(value, list): - if not value: - # empty list or None - result[attr] = value - else: - res = [] - for v in value: - if isinstance(v, PRIMITIVE_TYPES) or v is None: - res.append(v) - elif isinstance(v, ModelSimple): - res.append(v.value) - elif isinstance(v, dict): - res.append(dict(map( - extract_item, - v.items() - ))) - else: - res.append(model_to_dict(v, serialize=serialize)) - result[attr] = res + res = [] + for v in value: + if isinstance(v, PRIMITIVE_TYPES) or v is None: + res.append(v) + elif isinstance(v, ModelSimple): + res.append(v.value) + elif isinstance(v, dict): + res.append(dict(map( + extract_item, + v.items() + ))) + else: + res.append(model_to_dict(v, serialize=serialize)) + result[attr] = res elif isinstance(value, dict): result[attr] = dict(map( extract_item, diff --git a/tests/python/sdk/test_api_wrappers.py b/tests/python/sdk/test_api_wrappers.py new file mode 100644 index 000000000000..84ec919c9ba2 --- /dev/null +++ b/tests/python/sdk/test_api_wrappers.py @@ -0,0 +1,114 @@ +# Copyright (C) 2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from copy import deepcopy + +from cvat_sdk import models +from deepdiff import DeepDiff + + +def test_models_do_not_change_input_values(): + # Nested containers may be modified during the model input data parsing. + # This can lead to subtle memory errors, which are very hard to find. + original_input_data = { + "name": "test", + "labels": [ + { + "name": "cat", + "attributes": [ + { + "default_value": "yy", + "input_type": "text", + "mutable": False, + "name": "x", + "values": ["yy"], + }, + { + "default_value": "1", + "input_type": "radio", + "mutable": False, + "name": "y", + "values": ["1", "2"], + }, + ], + } + ], + } + + input_data = deepcopy(original_input_data) + + models.TaskWriteRequest(**input_data) + + assert DeepDiff(original_input_data, input_data) == {} + + +def test_models_do_not_store_input_collections(): + # Avoid depending on input data for collection fields after the model is initialized. + # This can lead to subtle memory errors and unexpected behavior + # if the original input data is modified. + input_data = { + "name": "test", + "labels": [ + { + "name": "cat1", + "attributes": [ + { + "default_value": "yy", + "input_type": "text", + "mutable": False, + "name": "x", + "values": ["yy"], + }, + { + "default_value": "1", + "input_type": "radio", + "mutable": False, + "name": "y", + "values": ["1", "2"], + }, + ], + }, + {"name": "cat2", "attributes": []}, + ], + } + + model = models.TaskWriteRequest(**input_data) + model_data1 = model.to_dict() + + # Modify input value containers + input_data["labels"][0]["attributes"].clear() + input_data["labels"][1]["attributes"].append( + { + "default_value": "", + "input_type": "text", + "mutable": True, + "name": "z", + } + ) + input_data["labels"].append({"name": "dog"}) + + model_data2 = model.to_dict() + + assert DeepDiff(model_data1, model_data2) == {} + + +def test_models_do_not_return_internal_collections(): + # Avoid returning internal data for mutable collection fields. + # This can lead to subtle memory errors and unexpected behavior + # if the returned data is modified. + input_data = { + "name": "test", + "labels": [], + } + + model = models.TaskWriteRequest(**input_data) + model_data1 = model.to_dict() + model_data1_original = deepcopy(model_data1) + + # Modify an output value container + model_data1["labels"].append({"name": "dog"}) + + model_data2 = model.to_dict() + + assert DeepDiff(model_data1_original, model_data2) == {} From 0c8a46e3d01ab5d034b002be47b833fe37cd988a Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 12 Jul 2023 18:17:46 +0300 Subject: [PATCH 09/48] Honey Pot fixes (#6456) Fixed: UI: - opening skeleton point RMB menu leads to UI fail - annotation conflicts are accumulated when changing frames, if the annotation has multiple conflicts - improve OKS sigma description Server: - average mean iou can be negative --- cvat-canvas/src/typescript/canvasView.ts | 2 +- .../views/canvas2d/canvas-context-menu.tsx | 2 +- .../quality/quality-settings-modal.tsx | 18 ++++++++++++++--- cvat-ui/src/reducers/review-reducer.ts | 20 +++++++++++++++++-- cvat/apps/quality_control/quality_reports.py | 4 ++++ tests/python/shared/assets/cvat_db/data.json | 4 ++-- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 220a3de19028..1a6d442d614e 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -2183,7 +2183,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } private deactivateShape(): void { - if (this.activeElement.clientID !== null) { + if (this.activeElement.clientID) { const { displayAllText } = this.configuration; const { clientID } = this.activeElement; const drawnState = this.drawnStates[clientID]; diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx index 88b4fa67f5d9..89b57cc54fd7 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx @@ -128,7 +128,7 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null { (annotationConflict: AnnotationConflict) => annotationConflict.clientID === state.clientID, )); - const copyObject = state.isGroundTruth ? state : null; + const copyObject = state?.isGroundTruth ? state : null; if (workspace === Workspace.REVIEW_WORKSPACE) { return ReactDOM.createPortal( diff --git a/cvat-ui/src/components/task-analytics-page/quality/quality-settings-modal.tsx b/cvat-ui/src/components/task-analytics-page/quality/quality-settings-modal.tsx index 26e2b47f623b..4363934dee21 100644 --- a/cvat-ui/src/components/task-analytics-page/quality/quality-settings-modal.tsx +++ b/cvat-ui/src/components/task-analytics-page/quality/quality-settings-modal.tsx @@ -73,10 +73,22 @@ export default function QualitySettingsModal(): JSX.Element | null { const keypointTooltip = (
- OKS Sigma Like IoU threshold, but for points. - The percent of the bbox area, used as the radius of the circle around the GT point, + Object Keypoint Similarity (OKS) is like IoU, but for skeleton points. + + + The Sigma value is the percent of the skeleton bbox area ^ 0.5. + Used as the radius of the circle around a GT point, where the checked point is expected to be. + + The value is also used to match single point annotations, in which case + the bbox is the whole image. For point groups the bbox is taken + for the whole group. + + + If there is a rectangle annotation in the points group or skeleton, + it is used as the group bbox (supposing the whole group describes a single object). +
); @@ -217,7 +229,7 @@ export default function QualitySettingsModal(): JSX.Element | null { diff --git a/cvat-ui/src/reducers/review-reducer.ts b/cvat-ui/src/reducers/review-reducer.ts index d6032cd5af0e..f261956fba0d 100644 --- a/cvat-ui/src/reducers/review-reducer.ts +++ b/cvat-ui/src/reducers/review-reducer.ts @@ -131,8 +131,24 @@ export default function (state: ReviewState = defaultState, action: any): Review } }); }); - mainConflict.description = descriptionList.join(', '); - mergedFrameConflicts.push(mainConflict); + + // decorate the original conflict to avoid changing it + const description = descriptionList.join(', '); + const visibleConflict = new Proxy(mainConflict, { + get(target, prop) { + if (prop === 'description') { + return description; + } + + // By default, it looks like Reflect.get(target, prop, receiver) + // which has a different value of `this`. It doesn't allow to + // work with methods / properties that use private members. + const val = Reflect.get(target, prop); + return typeof val === 'function' ? (...args: any[]) => val.apply(target, args) : val; + }, + }); + + mergedFrameConflicts.push(visibleConflict); } } } diff --git a/cvat/apps/quality_control/quality_reports.py b/cvat/apps/quality_control/quality_reports.py index 32c9f72205e6..1f3ff5682569 100644 --- a/cvat/apps/quality_control/quality_reports.py +++ b/cvat/apps/quality_control/quality_reports.py @@ -1755,6 +1755,10 @@ def _find_closest_unmatched_shape(shape: dm.Annotation): _matched_shapes.add(matched_ann_id) resulting_distances.append(similarity) + resulting_distances = [ + sim if sim is not None and (sim >= 0) else 0 for sim in resulting_distances + ] + mean_iou = np.mean(resulting_distances) if resulting_distances else 0 if ( diff --git a/tests/python/shared/assets/cvat_db/data.json b/tests/python/shared/assets/cvat_db/data.json index b33aad592e9c..e9653d178cc8 100644 --- a/tests/python/shared/assets/cvat_db/data.json +++ b/tests/python/shared/assets/cvat_db/data.json @@ -10255,7 +10255,7 @@ "created_date": "2023-05-26T16:25:36.613Z", "target_last_updated": "2023-05-26T16:17:02.635Z", "gt_last_updated": "2023-05-26T16:16:50.630Z", - "data": "{\"parameters\":{\"included_annotation_types\":[\"bbox\",\"points\",\"mask\",\"polygon\",\"polyline\",\"skeleton\"],\"compare_attributes\":true,\"ignored_attributes\":[],\"iou_threshold\":0.4,\"low_overlap_threshold\":0.8,\"oks_sigma\":0.09,\"line_thickness\":0.01,\"compare_line_orientation\":true,\"line_orientation_threshold\":0.1,\"compare_groups\":true,\"group_match_threshold\":0.5,\"check_covered_annotations\":true,\"object_visibility_threshold\":0.05,\"panoptic_comparison\":true},\"comparison_summary\":{\"frame_share\":0.2727272727272727,\"frames\":[0,1,2],\"conflict_count\":37,\"warning_count\":15,\"error_count\":22,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":9,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6},\"annotations\":{\"valid_count\":21,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,8,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.6774193548387096,0.0],\"accuracy\":[0.0,0.5384615384615384,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.4883720930232558,\"precision\":0.6176470588235294,\"recall\":0.6363636363636364},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"mean_iou\":0.1773115335759765,\"accuracy\":0.5581395348837209},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"frame_count\":3,\"mean_conflict_count\":12.333333333333334},\"frame_results\":{\"0\":{\"conflicts\":[{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":91,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":118,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":88,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":102,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":68,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"},{\"obj_id\":98,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":107,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":121,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":122,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":70,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":76,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":93,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":74,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":95,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":73,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":96,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":64,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":66,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":94,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":89,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":123,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"},{\"obj_id\":97,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_direction\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"covered_annotation\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_attributes\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":82,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":103,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":81,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":104,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":83,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":105,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":87,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":106,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":80,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":111,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":77,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":114,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"}],\"annotations\":{\"valid_count\":21,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,2,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.84,0.0],\"accuracy\":[0.0,0.6363636363636364,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.5675675675675675,\"precision\":0.6176470588235294,\"recall\":0.7777777777777778},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"mean_iou\":0.5319346007279295,\"accuracy\":0.6486486486486487},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"conflict_count\":31,\"warning_count\":15,\"error_count\":16,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":3,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6}},\"1\":{\"conflicts\":[{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":130,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":128,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":124,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":125,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":127,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":129,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"}],\"annotations\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,6,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"mean_iou\":0.0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":6,\"warning_count\":0,\"error_count\":6,\"conflicts_by_type\":{\"missing_annotation\":6}},\"2\":{\"conflicts\":[],\"annotations\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,0,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"mean_iou\":0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":0,\"warning_count\":0,\"error_count\":0,\"conflicts_by_type\":{}}}}" + "data": "{\"parameters\":{\"included_annotation_types\":[\"bbox\",\"points\",\"mask\",\"polygon\",\"polyline\",\"skeleton\"],\"compare_attributes\":true,\"ignored_attributes\":[],\"iou_threshold\":0.4,\"low_overlap_threshold\":0.8,\"oks_sigma\":0.09,\"line_thickness\":0.01,\"compare_line_orientation\":true,\"line_orientation_threshold\":0.1,\"compare_groups\":true,\"group_match_threshold\":0.5,\"check_covered_annotations\":true,\"object_visibility_threshold\":0.05,\"panoptic_comparison\":true},\"comparison_summary\":{\"frame_share\":0.2727272727272727,\"frames\":[0,1,2],\"conflict_count\":37,\"warning_count\":15,\"error_count\":22,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":9,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6},\"annotations\":{\"valid_count\":21,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,8,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.6774193548387096,0.0],\"accuracy\":[0.0,0.5384615384615384,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.4883720930232558,\"precision\":0.6176470588235294,\"recall\":0.6363636363636364},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"mean_iou\":0.19532955159399454,\"accuracy\":0.5581395348837209},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"frame_count\":3,\"mean_conflict_count\":12.333333333333334},\"frame_results\":{\"0\":{\"conflicts\":[{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":91,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":118,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":88,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":102,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":68,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"},{\"obj_id\":98,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":107,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":121,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":122,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":70,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":76,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":93,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":74,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":95,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":73,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":96,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":64,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":66,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":94,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":89,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":123,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"},{\"obj_id\":97,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_direction\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"covered_annotation\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_attributes\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":82,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":103,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":81,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":104,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":83,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":105,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":87,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":106,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":80,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":111,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":77,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":114,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"}],\"annotations\":{\"valid_count\":21,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,2,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.84,0.0],\"accuracy\":[0.0,0.6363636363636364,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.5675675675675675,\"precision\":0.6176470588235294,\"recall\":0.7777777777777778},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"mean_iou\":0.5859886547819836,\"accuracy\":0.6486486486486487},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"conflict_count\":31,\"warning_count\":15,\"error_count\":16,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":3,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6}},\"1\":{\"conflicts\":[{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":130,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":128,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":124,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":125,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":127,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":129,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"}],\"annotations\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,6,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"mean_iou\":0.0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":6,\"warning_count\":0,\"error_count\":6,\"conflicts_by_type\":{\"missing_annotation\":6}},\"2\":{\"conflicts\":[],\"annotations\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,0,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"mean_iou\":0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":0,\"warning_count\":0,\"error_count\":0,\"conflicts_by_type\":{}}}}" } }, { @@ -10268,7 +10268,7 @@ "created_date": "2023-05-26T16:25:36.616Z", "target_last_updated": "2023-05-26T16:11:24.294Z", "gt_last_updated": "2023-05-26T16:16:50.630Z", - "data": "{\"parameters\":{\"included_annotation_types\":[\"bbox\",\"points\",\"mask\",\"polygon\",\"polyline\",\"skeleton\"],\"compare_attributes\":true,\"ignored_attributes\":[],\"iou_threshold\":0.4,\"low_overlap_threshold\":0.8,\"oks_sigma\":0.09,\"line_thickness\":0.01,\"compare_line_orientation\":true,\"line_orientation_threshold\":0.1,\"compare_groups\":true,\"group_match_threshold\":0.5,\"check_covered_annotations\":true,\"object_visibility_threshold\":0.05,\"panoptic_comparison\":true},\"comparison_summary\":{\"frame_share\":0.2727272727272727,\"frames\":[0,1,2],\"conflict_count\":37,\"warning_count\":15,\"error_count\":22,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":9,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6},\"annotations\":{\"valid_count\":21,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,8,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.6774193548387096,0.0],\"accuracy\":[0.0,0.5384615384615384,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.4883720930232558,\"precision\":0.6176470588235294,\"recall\":0.6363636363636364},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"mean_iou\":0.1773115335759765,\"accuracy\":0.5581395348837209},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"frame_count\":3,\"mean_conflict_count\":12.333333333333334},\"frame_results\":{\"0\":{\"conflicts\":[{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":91,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":118,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":88,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":102,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":68,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"},{\"obj_id\":98,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":107,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":121,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":122,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":70,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":76,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":93,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":74,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":95,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":73,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":96,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":64,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":66,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":94,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":89,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":123,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"},{\"obj_id\":97,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_direction\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"covered_annotation\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_attributes\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":82,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":103,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":81,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":104,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":83,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":105,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":87,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":106,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":80,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":111,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":77,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":114,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"}],\"annotations\":{\"valid_count\":21,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,2,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.84,0.0],\"accuracy\":[0.0,0.6363636363636364,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.5675675675675675,\"precision\":0.6176470588235294,\"recall\":0.7777777777777778},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"mean_iou\":0.5319346007279295,\"accuracy\":0.6486486486486487},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"conflict_count\":31,\"warning_count\":15,\"error_count\":16,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":3,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6}},\"1\":{\"conflicts\":[{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":130,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":128,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":124,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":125,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":127,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":129,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"}],\"annotations\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,6,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"mean_iou\":0.0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":6,\"warning_count\":0,\"error_count\":6,\"conflicts_by_type\":{\"missing_annotation\":6}},\"2\":{\"conflicts\":[],\"annotations\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,0,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"mean_iou\":0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":0,\"warning_count\":0,\"error_count\":0,\"conflicts_by_type\":{}}}}" + "data": "{\"parameters\":{\"included_annotation_types\":[\"bbox\",\"points\",\"mask\",\"polygon\",\"polyline\",\"skeleton\"],\"compare_attributes\":true,\"ignored_attributes\":[],\"iou_threshold\":0.4,\"low_overlap_threshold\":0.8,\"oks_sigma\":0.09,\"line_thickness\":0.01,\"compare_line_orientation\":true,\"line_orientation_threshold\":0.1,\"compare_groups\":true,\"group_match_threshold\":0.5,\"check_covered_annotations\":true,\"object_visibility_threshold\":0.05,\"panoptic_comparison\":true},\"comparison_summary\":{\"frame_share\":0.2727272727272727,\"frames\":[0,1,2],\"conflict_count\":37,\"warning_count\":15,\"error_count\":22,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":9,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6},\"annotations\":{\"valid_count\":21,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,8,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.6774193548387096,0.0],\"accuracy\":[0.0,0.5384615384615384,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.4883720930232558,\"precision\":0.6176470588235294,\"recall\":0.6363636363636364},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":9,\"extra_count\":10,\"total_count\":43,\"ds_count\":34,\"gt_count\":33,\"mean_iou\":0.19532955159399454,\"accuracy\":0.5581395348837209},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"frame_count\":3,\"mean_conflict_count\":12.333333333333334},\"frame_results\":{\"0\":{\"conflicts\":[{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":91,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":118,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":88,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":102,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":68,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"},{\"obj_id\":98,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"low_overlap\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":107,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":121,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":122,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":70,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":76,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":93,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":74,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":95,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":73,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":96,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":64,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":66,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"extra_annotation\",\"annotation_ids\":[{\"obj_id\":94,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":89,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":123,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":92,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"points\"},{\"obj_id\":119,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_label\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"},{\"obj_id\":97,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":0,\"type\":\"mismatching_direction\",\"annotation_ids\":[{\"obj_id\":67,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"polyline\"},{\"obj_id\":99,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"covered_annotation\",\"annotation_ids\":[{\"obj_id\":69,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_attributes\",\"annotation_ids\":[{\"obj_id\":65,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":101,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":82,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":103,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":81,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":104,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":83,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":105,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":87,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":106,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":80,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":111,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"},{\"frame_id\":0,\"type\":\"mismatching_groups\",\"annotation_ids\":[{\"obj_id\":77,\"job_id\":27,\"type\":\"shape\",\"shape_type\":\"rectangle\"},{\"obj_id\":114,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"warning\"}],\"annotations\":{\"valid_count\":21,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,2,3],[1,21,7],[1,2,0]],\"precision\":[0.0,0.7241379310344828,0.0],\"recall\":[0.0,0.84,0.0],\"accuracy\":[0.0,0.6363636363636364,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.5675675675675675,\"precision\":0.6176470588235294,\"recall\":0.7777777777777778},\"annotation_components\":{\"shape\":{\"valid_count\":24,\"missing_count\":3,\"extra_count\":10,\"total_count\":37,\"ds_count\":34,\"gt_count\":27,\"mean_iou\":0.5859886547819836,\"accuracy\":0.6486486486486487},\"label\":{\"valid_count\":21,\"invalid_count\":3,\"total_count\":24,\"accuracy\":0.875}},\"conflict_count\":31,\"warning_count\":15,\"error_count\":16,\"conflicts_by_type\":{\"low_overlap\":6,\"missing_annotation\":3,\"extra_annotation\":10,\"mismatching_label\":3,\"mismatching_direction\":1,\"covered_annotation\":1,\"mismatching_attributes\":1,\"mismatching_groups\":6}},\"1\":{\"conflicts\":[{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":130,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"rectangle\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":128,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"points\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":124,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polygon\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":125,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"ellipse\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":127,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"mask\"}],\"severity\":\"error\"},{\"frame_id\":1,\"type\":\"missing_annotation\",\"annotation_ids\":[{\"obj_id\":129,\"job_id\":28,\"type\":\"shape\",\"shape_type\":\"polyline\"}],\"severity\":\"error\"}],\"annotations\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,6,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":6,\"extra_count\":0,\"total_count\":6,\"ds_count\":0,\"gt_count\":6,\"mean_iou\":0.0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":6,\"warning_count\":0,\"error_count\":6,\"conflicts_by_type\":{\"missing_annotation\":6}},\"2\":{\"conflicts\":[],\"annotations\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"confusion_matrix\":{\"labels\":[\"dog\",\"cat\",\"unmatched\"],\"rows\":[[0,0,0],[0,0,0],[0,0,0]],\"precision\":[0.0,0.0,0.0],\"recall\":[0.0,0.0,0.0],\"accuracy\":[0.0,0.0,0.0],\"axes\":{\"cols\":\"gt\",\"rows\":\"ds\"}},\"accuracy\":0.0,\"precision\":0.0,\"recall\":0.0},\"annotation_components\":{\"shape\":{\"valid_count\":0,\"missing_count\":0,\"extra_count\":0,\"total_count\":0,\"ds_count\":0,\"gt_count\":0,\"mean_iou\":0,\"accuracy\":0.0},\"label\":{\"valid_count\":0,\"invalid_count\":0,\"total_count\":0,\"accuracy\":0.0}},\"conflict_count\":0,\"warning_count\":0,\"error_count\":0,\"conflicts_by_type\":{}}}}" } }, { From 573d57c88bd4642f125d160f7ada8c635f70a846 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 08:34:12 +0300 Subject: [PATCH 10/48] Bump semver from 6.3.0 to 6.3.1 in /tests (#6452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
Release notes

Sourced from semver's releases.

v6.3.1

6.3.1 (2023-07-10)

Bug Fixes

Changelog

Sourced from semver's changelog.

6.3.1 (2023-07-10)

Bug Fixes

6.2.0

  • Coerce numbers to strings when passed to semver.coerce()
  • Add rtl option to coerce from right to left

6.1.3

  • Handle X-ranges properly in includePrerelease mode

6.1.2

  • Do not throw when testing invalid version strings

6.1.1

  • Add options support for semver.coerce()
  • Handle undefined version passed to Range.test

6.1.0

  • Add semver.compareBuild function
  • Support * in semver.intersects

6.0

  • Fix intersects logic.

    This is technically a bug fix, but since it is also a change to behavior that may require users updating their code, it is marked as a major version increment.

5.7

  • Add minVersion method

5.6

  • Move boolean loose param to an options object, with backwards-compatibility protection.
  • Add ability to opt out of special prerelease version handling with the includePrerelease option flag.

5.5

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by lukekarrys, a new releaser for semver since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=semver&package-manager=npm_and_yarn&previous-version=6.3.0&new-version=6.3.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opencv/cvat/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/yarn.lock b/tests/yarn.lock index fca8679089c7..1c2cba350def 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -2449,14 +2449,14 @@ sax@>=0.6.0: integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.2: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" From 0715ec0483fa4a69e33c878119a84a7b4b88a8f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 08:34:53 +0300 Subject: [PATCH 11/48] Bump semver from 5.7.1 to 5.7.2 (#6453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
Release notes

Sourced from semver's releases.

v5.7.2

5.7.2 (2023-07-10)

Bug Fixes

Changelog

Sourced from semver's changelog.

5.7.2 (2023-07-10)

Bug Fixes

5.7

  • Add minVersion method

5.6

  • Move boolean loose param to an options object, with backwards-compatibility protection.
  • Add ability to opt out of special prerelease version handling with the includePrerelease option flag.

5.5

  • Add version coercion capabilities

5.4

  • Add intersection checking

5.3

  • Add minSatisfying method

5.2

  • Add prerelease(v) that returns prerelease components

5.1

  • Add Backus-Naur for ranges
  • Remove excessively cute inspection methods

5.0

  • Remove AMD/Browserified build artifacts
  • Fix ltr and gtr when using the * range
  • Fix for range * with a prerelease identifier
Commits
Maintainer changes

This version was pushed to npm by lukekarrys, a new releaser for semver since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=semver&package-manager=npm_and_yarn&previous-version=5.7.1&new-version=5.7.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opencv/cvat/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2257a5e986dc..6cf265c0a21a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11490,21 +11490,21 @@ selfsigned@^2.0.1: node-forge "^1" semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@~7.0.0: version "7.0.0" From 99a6cf9e5e1d1763cfb70fdd43f79c4c53c1d1c8 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 14 Jul 2023 12:16:51 +0300 Subject: [PATCH 12/48] Bump Hadolint version (#6478) --- .github/workflows/hadolint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index aa81751ab536..9529f69becba 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -14,7 +14,7 @@ jobs: - name: Run checks env: HADOLINT: "${{ github.workspace }}/hadolint" - HADOLINT_VER: "2.1.0" + HADOLINT_VER: "2.12.0" VERIFICATION_LEVEL: "error" run: | CHANGED_FILES="${{steps.files.outputs.all_changed_files}}" From 1531f862c480f3349a87b8f328518a1d2802c4cc Mon Sep 17 00:00:00 2001 From: Kirill Sizov Date: Mon, 17 Jul 2023 14:29:46 +0300 Subject: [PATCH 13/48] Update codeowners (#6489) ### Motivation and context ### How has this been tested? ### Checklist - [ ] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [ ] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7d95099ce985..d5a9291e707b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,10 +38,10 @@ /components/ @azhavoro # Component: Tests -/tests/ @yasakova-anastasia +/tests/ @kirill-sizov # Component: Serverless functions -/serverless/ @yasakova-anastasia +/serverless/ @kirill-sizov # Infrastructure Dockerfile* @azhavoro From bf3e31d743bfacc74e1a480a697f85146292eaa2 Mon Sep 17 00:00:00 2001 From: Kirill Sizov Date: Tue, 18 Jul 2023 11:19:49 +0300 Subject: [PATCH 14/48] Add error message (#6500) ### Motivation and context When users try to backup a task with cloud storage data they get such error: ![Screenshot from 2023-07-18 09-21-18](https://github.com/opencv/cvat/assets/43179655/a6e4ed09-8fb5-4ebb-a5e3-72b95d9aa195) And after that, they have no idea what's wrong with this task and why they cannot backup it. ### How has this been tested? ### Checklist - [ ] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [ ] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- cvat/apps/engine/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/apps/engine/backup.py b/cvat/apps/engine/backup.py index ee994bc81907..799b217c2d95 100644 --- a/cvat/apps/engine/backup.py +++ b/cvat/apps/engine/backup.py @@ -364,7 +364,7 @@ def _write_data(self, zip_object, target_dir=None): target_dir=target_data_dir, ) else: - raise NotImplementedError() + raise NotImplementedError("We don't currently support backing up tasks with data from cloud storage") def _write_task(self, zip_object, target_dir=None): task_dir = self._db_task.get_dirname() From 60b925a7d2e2088aa61a05672003cc056f1dbe4b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 18 Jul 2023 15:28:36 +0300 Subject: [PATCH 15/48] Fixed: 3d job can not be opened in validation mode (#6507) ### Motivation and context Resolved #6383 ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [x] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [x] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- CHANGELOG.md | 1 + cvat-ui/src/actions/annotation-actions.ts | 7 +------ cvat-ui/src/reducers/annotation-reducer.ts | 3 ++- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a08bcdf77f02..64ca6d2021dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[SDK\] Ability to create attributes with blank default values () - \[SDK\] SDK should not change input data in models () +- 3D job can not be opened in validation mode () ### Security - TDB diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index ebfe3a123a29..33ad6d48b9df 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -13,7 +13,7 @@ import { RectDrawingMethod, CuboidDrawingMethod, Canvas, CanvasMode as Canvas2DMode, } from 'cvat-canvas-wrapper'; import { - getCore, MLModel, DimensionType, JobType, Job, QualityConflict, + getCore, MLModel, JobType, Job, QualityConflict, } from 'cvat-core-wrapper'; import logger, { LogType } from 'cvat-logger'; import { getCVATStore } from 'cvat-store'; @@ -993,11 +993,6 @@ export function getJobAsync( }, }); - if (job.dimension === DimensionType.DIMENSION_3D) { - const workspace = Workspace.STANDARD3D; - dispatch(changeWorkspace(workspace)); - } - dispatch(changeFrameAsync(frameNumber, false)); } catch (error) { dispatch({ diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index a31c6740160e..71524879f8bd 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -221,7 +221,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { instance: job.dimension === DimensionType.DIMENSION_2D ? new Canvas() : new Canvas3d(), }, colors, - workspace: isReview ? Workspace.REVIEW_WORKSPACE : workspaceSelected, + workspace: isReview && job.dimension === DimensionType.DIMENSION_2D ? + Workspace.REVIEW_WORKSPACE : workspaceSelected, }; } case AnnotationActionTypes.GET_JOB_FAILED: { From 9fc6b00e27b1a75dad657d5fce17f7e931b157ca Mon Sep 17 00:00:00 2001 From: Kirill Sizov Date: Tue, 18 Jul 2023 16:02:13 +0300 Subject: [PATCH 16/48] No helm tests on edit (#6495) Duplicate https://github.com/opencv/cvat/pull/6373 for Helm tests --- .github/workflows/helm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index d90c33fb6087..0fae48884c28 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -5,7 +5,7 @@ on: - 'master' - 'develop' pull_request: - types: [edited, ready_for_review, opened, synchronize, reopened] + types: [ready_for_review, opened, synchronize, reopened] paths-ignore: - 'site/**' - '**/*.md' From bc5036fd2744dddd34be51f66b0368acfaf3a684 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 18 Jul 2023 17:58:00 +0300 Subject: [PATCH 17/48] Create cvat_sdk.datasets, a framework-agnostic version of cvat_sdk.pytorch (#6428) The new `TaskDataset` class provides conveniences like per-frame annotations, bulk data downloading, and caching without forcing a dependency on PyTorch (and somewhat awkwardly conforming to the PyTorch dataset interface). It also provides a few extra niceties, like easy access to labels and original frame numbers. Note that it's called `TaskDataset` rather than `TaskVisionDataset`, as my plan is to keep it domain-agnostic. The `MediaElement` class is extensible, and we can add, for example, support for point clouds, by adding another `load_*` method. There is currently no `ProjectDataset` equivalent, although one could (and probably should) be added later. If we add one, we should probably also add a `task_id` field to `Sample`. --- CHANGELOG.md | 2 + cvat-sdk/cvat_sdk/datasets/__init__.py | 7 + .../cvat_sdk/{pytorch => datasets}/caching.py | 0 cvat-sdk/cvat_sdk/datasets/common.py | 57 +++++ cvat-sdk/cvat_sdk/datasets/task_dataset.py | 164 ++++++++++++++ cvat-sdk/cvat_sdk/pytorch/__init__.py | 8 +- cvat-sdk/cvat_sdk/pytorch/common.py | 21 +- cvat-sdk/cvat_sdk/pytorch/project_dataset.py | 2 +- cvat-sdk/cvat_sdk/pytorch/task_dataset.py | 98 +-------- cvat-sdk/cvat_sdk/pytorch/transforms.py | 3 +- tests/python/sdk/test_datasets.py | 207 ++++++++++++++++++ tests/python/sdk/test_pytorch.py | 26 +-- tests/python/sdk/util.py | 19 +- 13 files changed, 484 insertions(+), 130 deletions(-) create mode 100644 cvat-sdk/cvat_sdk/datasets/__init__.py rename cvat-sdk/cvat_sdk/{pytorch => datasets}/caching.py (100%) create mode 100644 cvat-sdk/cvat_sdk/datasets/common.py create mode 100644 cvat-sdk/cvat_sdk/datasets/task_dataset.py create mode 100644 tests/python/sdk/test_datasets.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ca6d2021dd..7a39e413eaae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[Unreleased] ### Added - Multi-line text attributes supported () +- \{SDK\] `cvat_sdk.datasets`, a framework-agnostic equivalent of `cvat_sdk.pytorch` + () ### Changed - TDB diff --git a/cvat-sdk/cvat_sdk/datasets/__init__.py b/cvat-sdk/cvat_sdk/datasets/__init__.py new file mode 100644 index 000000000000..08dd89165eac --- /dev/null +++ b/cvat-sdk/cvat_sdk/datasets/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from .caching import UpdatePolicy +from .common import FrameAnnotations, MediaElement, Sample, UnsupportedDatasetError +from .task_dataset import TaskDataset diff --git a/cvat-sdk/cvat_sdk/pytorch/caching.py b/cvat-sdk/cvat_sdk/datasets/caching.py similarity index 100% rename from cvat-sdk/cvat_sdk/pytorch/caching.py rename to cvat-sdk/cvat_sdk/datasets/caching.py diff --git a/cvat-sdk/cvat_sdk/datasets/common.py b/cvat-sdk/cvat_sdk/datasets/common.py new file mode 100644 index 000000000000..2b8269dbd567 --- /dev/null +++ b/cvat-sdk/cvat_sdk/datasets/common.py @@ -0,0 +1,57 @@ +# Copyright (C) 2022-2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import abc +from typing import List + +import attrs +import attrs.validators +import PIL.Image + +import cvat_sdk.core +import cvat_sdk.core.exceptions +import cvat_sdk.models as models + + +class UnsupportedDatasetError(cvat_sdk.core.exceptions.CvatSdkException): + pass + + +@attrs.frozen +class FrameAnnotations: + """ + Contains annotations that pertain to a single frame. + """ + + tags: List[models.LabeledImage] = attrs.Factory(list) + shapes: List[models.LabeledShape] = attrs.Factory(list) + + +class MediaElement(metaclass=abc.ABCMeta): + """ + The media part of a dataset sample. + """ + + @abc.abstractmethod + def load_image(self) -> PIL.Image.Image: + """ + Loads the media data and returns it as a PIL Image object. + """ + ... + + +@attrs.frozen +class Sample: + """ + Represents an element of a dataset. + """ + + frame_index: int + """Index of the corresponding frame in its task.""" + + annotations: FrameAnnotations + """Annotations belonging to the frame.""" + + media: MediaElement + """Media data of the frame.""" diff --git a/cvat-sdk/cvat_sdk/datasets/task_dataset.py b/cvat-sdk/cvat_sdk/datasets/task_dataset.py new file mode 100644 index 000000000000..586070457934 --- /dev/null +++ b/cvat-sdk/cvat_sdk/datasets/task_dataset.py @@ -0,0 +1,164 @@ +# Copyright (C) 2022-2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import zipfile +from concurrent.futures import ThreadPoolExecutor +from typing import Sequence + +import PIL.Image + +import cvat_sdk.core +import cvat_sdk.core.exceptions +import cvat_sdk.models as models +from cvat_sdk.datasets.caching import UpdatePolicy, make_cache_manager +from cvat_sdk.datasets.common import FrameAnnotations, MediaElement, Sample, UnsupportedDatasetError + +_NUM_DOWNLOAD_THREADS = 4 + + +class TaskDataset: + """ + Represents a task on a CVAT server as a collection of samples. + + Each sample corresponds to one frame in the task, and provides access to + the corresponding annotations and media data. Deleted frames are omitted. + + This class caches all data and annotations for the task on the local file system + during construction. + + Limitations: + + * Only tasks with image (not video) data are supported at the moment. + * Track annotations are currently not accessible. + """ + + class _TaskMediaElement(MediaElement): + def __init__(self, dataset: TaskDataset, frame_index: int) -> None: + self._dataset = dataset + self._frame_index = frame_index + + def load_image(self) -> PIL.Image.Image: + return self._dataset._load_frame_image(self._frame_index) + + def __init__( + self, + client: cvat_sdk.core.Client, + task_id: int, + *, + update_policy: UpdatePolicy = UpdatePolicy.IF_MISSING_OR_STALE, + ) -> None: + """ + Creates a dataset corresponding to the task with ID `task_id` on the + server that `client` is connected to. + + `update_policy` determines when and if the local cache will be updated. + """ + + self._logger = client.logger + + cache_manager = make_cache_manager(client, update_policy) + self._task = cache_manager.retrieve_task(task_id) + + if not self._task.size or not self._task.data_chunk_size: + raise UnsupportedDatasetError("The task has no data") + + if self._task.data_original_chunk_type != "imageset": + raise UnsupportedDatasetError( + f"{self.__class__.__name__} only supports tasks with image chunks;" + f" current chunk type is {self._task.data_original_chunk_type!r}" + ) + + self._logger.info("Fetching labels...") + self._labels = tuple(self._task.get_labels()) + + data_meta = cache_manager.ensure_task_model( + self._task.id, + "data_meta.json", + models.DataMetaRead, + self._task.get_meta, + "data metadata", + ) + + active_frame_indexes = set(range(self._task.size)) - set(data_meta.deleted_frames) + + self._logger.info("Downloading chunks...") + + self._chunk_dir = cache_manager.chunk_dir(task_id) + self._chunk_dir.mkdir(exist_ok=True, parents=True) + + needed_chunks = {index // self._task.data_chunk_size for index in active_frame_indexes} + + with ThreadPoolExecutor(_NUM_DOWNLOAD_THREADS) as pool: + + def ensure_chunk(chunk_index): + cache_manager.ensure_chunk(self._task, chunk_index) + + for _ in pool.map(ensure_chunk, sorted(needed_chunks)): + # just need to loop through all results so that any exceptions are propagated + pass + + self._logger.info("All chunks downloaded") + + annotations = cache_manager.ensure_task_model( + self._task.id, + "annotations.json", + models.LabeledData, + self._task.get_annotations, + "annotations", + ) + + self._frame_annotations = { + frame_index: FrameAnnotations() for frame_index in sorted(active_frame_indexes) + } + + for tag in annotations.tags: + # Some annotations may belong to deleted frames; skip those. + if tag.frame in self._frame_annotations: + self._frame_annotations[tag.frame].tags.append(tag) + + for shape in annotations.shapes: + if shape.frame in self._frame_annotations: + self._frame_annotations[shape.frame].shapes.append(shape) + + # TODO: tracks? + + self._samples = [ + Sample(frame_index=k, annotations=v, media=self._TaskMediaElement(self, k)) + for k, v in self._frame_annotations.items() + ] + + @property + def labels(self) -> Sequence[models.ILabel]: + """ + Returns the labels configured in the task. + + Clients must not modify the object returned by this property or its components. + """ + return self._labels + + @property + def samples(self) -> Sequence[Sample]: + """ + Returns a sequence of all samples, in order of their frame indices. + + Note that the frame indices may not be contiguous, as deleted frames will not be included. + + Clients must not modify the object returned by this property or its components. + """ + return self._samples + + def _load_frame_image(self, frame_index: int) -> PIL.Image: + assert frame_index in self._frame_annotations + + chunk_index = frame_index // self._task.data_chunk_size + member_index = frame_index % self._task.data_chunk_size + + with zipfile.ZipFile(self._chunk_dir / f"{chunk_index}.zip", "r") as chunk_zip: + with chunk_zip.open(chunk_zip.infolist()[member_index]) as chunk_member: + image = PIL.Image.open(chunk_member) + image.load() + + return image diff --git a/cvat-sdk/cvat_sdk/pytorch/__init__.py b/cvat-sdk/cvat_sdk/pytorch/__init__.py index ba6609b268a4..3fa537ff99c0 100644 --- a/cvat-sdk/cvat_sdk/pytorch/__init__.py +++ b/cvat-sdk/cvat_sdk/pytorch/__init__.py @@ -2,8 +2,12 @@ # # SPDX-License-Identifier: MIT -from .caching import UpdatePolicy -from .common import FrameAnnotations, Target, UnsupportedDatasetError +from .common import Target from .project_dataset import ProjectVisionDataset from .task_dataset import TaskVisionDataset from .transforms import ExtractBoundingBoxes, ExtractSingleLabelIndex, LabeledBoxes + +# isort: split +# Compatibility imports +from ..datasets.caching import UpdatePolicy +from ..datasets.common import FrameAnnotations, UnsupportedDatasetError diff --git a/cvat-sdk/cvat_sdk/pytorch/common.py b/cvat-sdk/cvat_sdk/pytorch/common.py index ac5d8fb7ad96..97ef38bc33a8 100644 --- a/cvat-sdk/cvat_sdk/pytorch/common.py +++ b/cvat-sdk/cvat_sdk/pytorch/common.py @@ -2,28 +2,11 @@ # # SPDX-License-Identifier: MIT -from typing import List, Mapping +from typing import Mapping import attrs -import attrs.validators -import cvat_sdk.core -import cvat_sdk.core.exceptions -import cvat_sdk.models as models - - -class UnsupportedDatasetError(cvat_sdk.core.exceptions.CvatSdkException): - pass - - -@attrs.frozen -class FrameAnnotations: - """ - Contains annotations that pertain to a single frame. - """ - - tags: List[models.LabeledImage] = attrs.Factory(list) - shapes: List[models.LabeledShape] = attrs.Factory(list) +from cvat_sdk.datasets.common import FrameAnnotations @attrs.frozen diff --git a/cvat-sdk/cvat_sdk/pytorch/project_dataset.py b/cvat-sdk/cvat_sdk/pytorch/project_dataset.py index be834b1cedd9..ada554ee1210 100644 --- a/cvat-sdk/cvat_sdk/pytorch/project_dataset.py +++ b/cvat-sdk/cvat_sdk/pytorch/project_dataset.py @@ -12,7 +12,7 @@ import cvat_sdk.core import cvat_sdk.core.exceptions import cvat_sdk.models as models -from cvat_sdk.pytorch.caching import UpdatePolicy, make_cache_manager +from cvat_sdk.datasets.caching import UpdatePolicy, make_cache_manager from cvat_sdk.pytorch.task_dataset import TaskVisionDataset diff --git a/cvat-sdk/cvat_sdk/pytorch/task_dataset.py b/cvat-sdk/cvat_sdk/pytorch/task_dataset.py index 6edd3ec24aa2..8964d2db47db 100644 --- a/cvat-sdk/cvat_sdk/pytorch/task_dataset.py +++ b/cvat-sdk/cvat_sdk/pytorch/task_dataset.py @@ -2,21 +2,17 @@ # # SPDX-License-Identifier: MIT -import collections import os import types -import zipfile -from concurrent.futures import ThreadPoolExecutor -from typing import Callable, Dict, Mapping, Optional +from typing import Callable, Mapping, Optional -import PIL.Image import torchvision.datasets import cvat_sdk.core import cvat_sdk.core.exceptions -import cvat_sdk.models as models -from cvat_sdk.pytorch.caching import UpdatePolicy, make_cache_manager -from cvat_sdk.pytorch.common import FrameAnnotations, Target, UnsupportedDatasetError +from cvat_sdk.datasets.caching import UpdatePolicy, make_cache_manager +from cvat_sdk.datasets.task_dataset import TaskDataset +from cvat_sdk.pytorch.common import Target _NUM_DOWNLOAD_THREADS = 4 @@ -75,92 +71,31 @@ def __init__( `update_policy` determines when and if the local cache will be updated. """ - self._logger = client.logger + self._underlying = TaskDataset(client, task_id, update_policy=update_policy) cache_manager = make_cache_manager(client, update_policy) - self._task = cache_manager.retrieve_task(task_id) - - if not self._task.size or not self._task.data_chunk_size: - raise UnsupportedDatasetError("The task has no data") - - if self._task.data_original_chunk_type != "imageset": - raise UnsupportedDatasetError( - f"{self.__class__.__name__} only supports tasks with image chunks;" - f" current chunk type is {self._task.data_original_chunk_type!r}" - ) super().__init__( - os.fspath(cache_manager.task_dir(self._task.id)), + os.fspath(cache_manager.task_dir(task_id)), transforms=transforms, transform=transform, target_transform=target_transform, ) - data_meta = cache_manager.ensure_task_model( - self._task.id, - "data_meta.json", - models.DataMetaRead, - self._task.get_meta, - "data metadata", - ) - self._active_frame_indexes = sorted( - set(range(self._task.size)) - set(data_meta.deleted_frames) - ) - - self._logger.info("Downloading chunks...") - - self._chunk_dir = cache_manager.chunk_dir(task_id) - self._chunk_dir.mkdir(exist_ok=True, parents=True) - - needed_chunks = { - index // self._task.data_chunk_size for index in self._active_frame_indexes - } - - with ThreadPoolExecutor(_NUM_DOWNLOAD_THREADS) as pool: - - def ensure_chunk(chunk_index): - cache_manager.ensure_chunk(self._task, chunk_index) - - for _ in pool.map(ensure_chunk, sorted(needed_chunks)): - # just need to loop through all results so that any exceptions are propagated - pass - - self._logger.info("All chunks downloaded") - if label_name_to_index is None: self._label_id_to_index = types.MappingProxyType( { label.id: label_index for label_index, label in enumerate( - sorted(self._task.get_labels(), key=lambda l: l.id) + sorted(self._underlying.labels, key=lambda l: l.id) ) } ) else: self._label_id_to_index = types.MappingProxyType( - {label.id: label_name_to_index[label.name] for label in self._task.get_labels()} + {label.id: label_name_to_index[label.name] for label in self._underlying.labels} ) - annotations = cache_manager.ensure_task_model( - self._task.id, - "annotations.json", - models.LabeledData, - self._task.get_annotations, - "annotations", - ) - - self._frame_annotations: Dict[int, FrameAnnotations] = collections.defaultdict( - FrameAnnotations - ) - - for tag in annotations.tags: - self._frame_annotations[tag.frame].tags.append(tag) - - for shape in annotations.shapes: - self._frame_annotations[shape.frame].shapes.append(shape) - - # TODO: tracks? - def __getitem__(self, sample_index: int): """ Returns the sample with index `sample_index`. @@ -168,19 +103,10 @@ def __getitem__(self, sample_index: int): `sample_index` must satisfy the condition `0 <= sample_index < len(self)`. """ - frame_index = self._active_frame_indexes[sample_index] - chunk_index = frame_index // self._task.data_chunk_size - member_index = frame_index % self._task.data_chunk_size + sample = self._underlying.samples[sample_index] - with zipfile.ZipFile(self._chunk_dir / f"{chunk_index}.zip", "r") as chunk_zip: - with chunk_zip.open(chunk_zip.infolist()[member_index]) as chunk_member: - sample_image = PIL.Image.open(chunk_member) - sample_image.load() - - sample_target = Target( - annotations=self._frame_annotations[frame_index], - label_id_to_index=self._label_id_to_index, - ) + sample_image = sample.media.load_image() + sample_target = Target(sample.annotations, self._label_id_to_index) if self.transforms: sample_image, sample_target = self.transforms(sample_image, sample_target) @@ -188,4 +114,4 @@ def __getitem__(self, sample_index: int): def __len__(self) -> int: """Returns the number of samples in the dataset.""" - return len(self._active_frame_indexes) + return len(self._underlying.samples) diff --git a/cvat-sdk/cvat_sdk/pytorch/transforms.py b/cvat-sdk/cvat_sdk/pytorch/transforms.py index 259ebc045375..d63fdba65f68 100644 --- a/cvat-sdk/cvat_sdk/pytorch/transforms.py +++ b/cvat-sdk/cvat_sdk/pytorch/transforms.py @@ -10,7 +10,8 @@ import torch.utils.data from typing_extensions import TypedDict -from cvat_sdk.pytorch.common import Target, UnsupportedDatasetError +from cvat_sdk.datasets.common import UnsupportedDatasetError +from cvat_sdk.pytorch.common import Target @attrs.frozen diff --git a/tests/python/sdk/test_datasets.py b/tests/python/sdk/test_datasets.py new file mode 100644 index 000000000000..67204e4c26c9 --- /dev/null +++ b/tests/python/sdk/test_datasets.py @@ -0,0 +1,207 @@ +# Copyright (C) 2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import io +from logging import Logger +from pathlib import Path +from typing import Tuple + +import cvat_sdk.datasets as cvatds +import PIL.Image +import pytest +from cvat_sdk import Client, models +from cvat_sdk.core.proxies.tasks import ResourceType + +from shared.utils.helpers import generate_image_files + +from .util import restrict_api_requests + + +@pytest.fixture(autouse=True) +def _common_setup( + tmp_path: Path, + fxt_login: Tuple[Client, str], + fxt_logger: Tuple[Logger, io.StringIO], +): + logger = fxt_logger[0] + client = fxt_login[0] + client.logger = logger + client.config.cache_dir = tmp_path / "cache" + + api_client = client.api_client + for k in api_client.configuration.logger: + api_client.configuration.logger[k] = logger + + +class TestTaskDataset: + @pytest.fixture(autouse=True) + def setup( + self, + tmp_path: Path, + fxt_login: Tuple[Client, str], + ): + self.client = fxt_login[0] + self.images = generate_image_files(10) + + image_dir = tmp_path / "images" + image_dir.mkdir() + + image_paths = [] + for image in self.images: + image_path = image_dir / image.name + image_path.write_bytes(image.getbuffer()) + image_paths.append(image_path) + + self.task = self.client.tasks.create_from_data( + models.TaskWriteRequest( + "Dataset layer test task", + labels=[ + models.PatchedLabelRequest(name="person"), + models.PatchedLabelRequest(name="car"), + ], + ), + resource_type=ResourceType.LOCAL, + resources=image_paths, + data_params={"chunk_size": 3}, + ) + + self.expected_labels = sorted(self.task.get_labels(), key=lambda l: l.id) + + self.task.update_annotations( + models.PatchedLabeledDataRequest( + tags=[ + models.LabeledImageRequest(frame=8, label_id=self.expected_labels[0].id), + models.LabeledImageRequest(frame=8, label_id=self.expected_labels[1].id), + ], + shapes=[ + models.LabeledShapeRequest( + frame=6, + label_id=self.expected_labels[1].id, + type=models.ShapeType("rectangle"), + points=[1.0, 2.0, 3.0, 4.0], + ), + ], + ) + ) + + def test_basic(self): + dataset = cvatds.TaskDataset(self.client, self.task.id) + + # verify that the cache is not empty + assert list(self.client.config.cache_dir.iterdir()) + + for expected_label, actual_label in zip( + self.expected_labels, sorted(dataset.labels, key=lambda l: l.id) + ): + assert expected_label.id == actual_label.id + assert expected_label.name == actual_label.name + + assert len(dataset.samples) == self.task.size + + for index, sample in enumerate(dataset.samples): + assert sample.frame_index == index + + actual_image = sample.media.load_image() + expected_image = PIL.Image.open(self.images[index]) + + assert actual_image == expected_image + + assert not dataset.samples[0].annotations.tags + assert not dataset.samples[1].annotations.shapes + + assert {tag.label_id for tag in dataset.samples[8].annotations.tags} == { + label.id for label in self.expected_labels + } + assert not dataset.samples[8].annotations.shapes + + assert not dataset.samples[6].annotations.tags + assert len(dataset.samples[6].annotations.shapes) == 1 + assert dataset.samples[6].annotations.shapes[0].type.value == "rectangle" + assert dataset.samples[6].annotations.shapes[0].points == [1.0, 2.0, 3.0, 4.0] + + def test_deleted_frame(self): + self.task.remove_frames_by_ids([1]) + + dataset = cvatds.TaskDataset(self.client, self.task.id) + + assert len(dataset.samples) == self.task.size - 1 + + # sample #0 is still frame #0 + assert dataset.samples[0].frame_index == 0 + assert dataset.samples[0].media.load_image() == PIL.Image.open(self.images[0]) + + # sample #1 is now frame #2 + assert dataset.samples[1].frame_index == 2 + assert dataset.samples[1].media.load_image() == PIL.Image.open(self.images[2]) + + # sample #5 is now frame #6 + assert dataset.samples[5].frame_index == 6 + assert dataset.samples[5].media.load_image() == PIL.Image.open(self.images[6]) + assert len(dataset.samples[5].annotations.shapes) == 1 + + def test_offline(self, monkeypatch: pytest.MonkeyPatch): + dataset = cvatds.TaskDataset( + self.client, + self.task.id, + update_policy=cvatds.UpdatePolicy.IF_MISSING_OR_STALE, + ) + + fresh_samples = list(dataset.samples) + + restrict_api_requests(monkeypatch) + + dataset = cvatds.TaskDataset( + self.client, + self.task.id, + update_policy=cvatds.UpdatePolicy.NEVER, + ) + + cached_samples = list(dataset.samples) + + for fresh_sample, cached_sample in zip(fresh_samples, cached_samples): + assert fresh_sample.frame_index == cached_sample.frame_index + assert fresh_sample.annotations == cached_sample.annotations + assert fresh_sample.media.load_image() == cached_sample.media.load_image() + + def test_update(self, monkeypatch: pytest.MonkeyPatch): + dataset = cvatds.TaskDataset( + self.client, + self.task.id, + ) + + # Recreating the dataset should only result in minimal requests. + restrict_api_requests( + monkeypatch, allow_paths={f"/api/tasks/{self.task.id}", "/api/labels"} + ) + + dataset = cvatds.TaskDataset( + self.client, + self.task.id, + ) + + assert dataset.samples[6].annotations.shapes[0].label_id == self.expected_labels[1].id + + # After an update, the annotations should be redownloaded. + monkeypatch.undo() + + self.task.update_annotations( + models.PatchedLabeledDataRequest( + shapes=[ + models.LabeledShapeRequest( + id=dataset.samples[6].annotations.shapes[0].id, + frame=6, + label_id=self.expected_labels[0].id, + type=models.ShapeType("rectangle"), + points=[1.0, 2.0, 3.0, 4.0], + ), + ] + ) + ) + + dataset = cvatds.TaskDataset( + self.client, + self.task.id, + ) + + assert dataset.samples[6].annotations.shapes[0].label_id == self.expected_labels[0].id diff --git a/tests/python/sdk/test_pytorch.py b/tests/python/sdk/test_pytorch.py index bcfedf7b8b3b..722cb37ab003 100644 --- a/tests/python/sdk/test_pytorch.py +++ b/tests/python/sdk/test_pytorch.py @@ -7,12 +7,10 @@ import os from logging import Logger from pathlib import Path -from typing import Container, Tuple -from urllib.parse import urlparse +from typing import Tuple import pytest from cvat_sdk import Client, models -from cvat_sdk.api_client.rest import RESTClientObject from cvat_sdk.core.proxies.tasks import ResourceType try: @@ -30,6 +28,8 @@ from shared.utils.helpers import generate_image_files +from .util import restrict_api_requests + @pytest.fixture(autouse=True) def _common_setup( @@ -47,20 +47,6 @@ def _common_setup( api_client.configuration.logger[k] = logger -def _restrict_api_requests( - monkeypatch: pytest.MonkeyPatch, allow_paths: Container[str] = () -) -> None: - original_request = RESTClientObject.request - - def restricted_request(self, method, url, *args, **kwargs): - parsed_url = urlparse(url) - if parsed_url.path in allow_paths: - return original_request(self, method, url, *args, **kwargs) - raise RuntimeError("Disallowed!") - - monkeypatch.setattr(RESTClientObject, "request", restricted_request) - - @pytest.mark.skipif(cvatpt is None, reason="PyTorch dependencies are not installed") class TestTaskVisionDataset: @pytest.fixture(autouse=True) @@ -254,7 +240,7 @@ def test_offline(self, monkeypatch: pytest.MonkeyPatch): fresh_samples = list(dataset) - _restrict_api_requests(monkeypatch) + restrict_api_requests(monkeypatch) dataset = cvatpt.TaskVisionDataset( self.client, @@ -273,7 +259,7 @@ def test_update(self, monkeypatch: pytest.MonkeyPatch): ) # Recreating the dataset should only result in minimal requests. - _restrict_api_requests( + restrict_api_requests( monkeypatch, allow_paths={f"/api/tasks/{self.task.id}", "/api/labels"} ) @@ -447,7 +433,7 @@ def test_offline(self, monkeypatch: pytest.MonkeyPatch): fresh_samples = list(dataset) - _restrict_api_requests(monkeypatch) + restrict_api_requests(monkeypatch) dataset = cvatpt.ProjectVisionDataset( self.client, diff --git a/tests/python/sdk/util.py b/tests/python/sdk/util.py index 83e6b10e2908..5861c658111a 100644 --- a/tests/python/sdk/util.py +++ b/tests/python/sdk/util.py @@ -4,8 +4,11 @@ import textwrap from pathlib import Path -from typing import Tuple +from typing import Container, Tuple +from urllib.parse import urlparse +import pytest +from cvat_sdk.api_client.rest import RESTClientObject from cvat_sdk.core.helpers import TqdmProgressReporter from tqdm import tqdm @@ -82,3 +85,17 @@ def generate_coco_anno(image_path: str, image_width: int, image_height: int) -> "image_width": image_width, } ) + + +def restrict_api_requests( + monkeypatch: pytest.MonkeyPatch, allow_paths: Container[str] = () +) -> None: + original_request = RESTClientObject.request + + def restricted_request(self, method, url, *args, **kwargs): + parsed_url = urlparse(url) + if parsed_url.path in allow_paths: + return original_request(self, method, url, *args, **kwargs) + raise RuntimeError("Disallowed!") + + monkeypatch.setattr(RESTClientObject, "request", restricted_request) From 783c414cfd08b7d0bc1510936bca955fa0c5a733 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov Date: Wed, 19 Jul 2023 07:06:40 +0300 Subject: [PATCH 18/48] Close av contaner after use (#6501) --- CHANGELOG.md | 1 + cvat/apps/engine/media_extractors.py | 126 +++++++++++++-------------- 2 files changed, 62 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a39e413eaae..604f34deb964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - \[SDK\] SDK should not change input data in models () - 3D job can not be opened in validation mode () +- Memory leak related to unclosed av container () ### Security - TDB diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index c79bd84a8ea0..65ab80ae4745 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -429,32 +429,27 @@ def _has_frame(self, i): return False - def _decode(self, container): - frame_num = 0 - for packet in container.demux(): - if packet.stream.type == 'video': + def __iter__(self): + with self._get_av_container() as container: + stream = container.streams.video[0] + stream.thread_type = 'AUTO' + frame_num = 0 + for packet in container.demux(stream): for image in packet.decode(): frame_num += 1 if self._has_frame(frame_num - 1): if packet.stream.metadata.get('rotate'): - old_image = image + pts = image.pts image = av.VideoFrame().from_ndarray( rotate_image( image.to_ndarray(format='bgr24'), - 360 - int(container.streams.video[0].metadata.get('rotate')) + 360 - int(stream.metadata.get('rotate')) ), format ='bgr24' ) - image.pts = old_image.pts + image.pts = pts yield (image, self._source_path[0], image.pts) - def __iter__(self): - container = self._get_av_container() - source_video_stream = container.streams.video[0] - source_video_stream.thread_type = 'AUTO' - - return self._decode(container) - def get_progress(self, pos): duration = self._get_duration() return pos / duration if duration else None @@ -465,38 +460,38 @@ def _get_av_container(self): 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 + with self._get_av_container() as 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, frame): - container = self._get_av_container() - stream = container.streams.video[0] - tb_denominator = stream.time_base.denominator - needed_time = int((frame / stream.guessed_rate) * tb_denominator) - container.seek(offset=needed_time, stream=stream) - for packet in container.demux(stream): - for frame in packet.decode(): - return self._get_preview(frame.to_image() if not stream.metadata.get('rotate') \ - else av.VideoFrame().from_ndarray( - rotate_image( - frame.to_ndarray(format='bgr24'), - 360 - int(container.streams.video[0].metadata.get('rotate')) - ), - format ='bgr24' - ).to_image() - ) + with self._get_av_container() as container: + stream = container.streams.video[0] + tb_denominator = stream.time_base.denominator + needed_time = int((frame / stream.guessed_rate) * tb_denominator) + container.seek(offset=needed_time, stream=stream) + for packet in container.demux(stream): + for frame in packet.decode(): + return self._get_preview(frame.to_image() if not stream.metadata.get('rotate') \ + else av.VideoFrame().from_ndarray( + rotate_image( + frame.to_ndarray(format='bgr24'), + 360 - int(container.streams.video[0].metadata.get('rotate')) + ), + format ='bgr24' + ).to_image() + ) def get_image_size(self, i): image = (next(iter(self)))[0] @@ -700,6 +695,8 @@ def save_as_chunk( return image_sizes class Mpeg4ChunkWriter(IChunkWriter): + FORMAT = 'mp4' + def __init__(self, quality=67): # translate inversed range [1:100] to [0:51] quality = round(51 * (100 - quality) / 99) @@ -722,21 +719,20 @@ def __init__(self, quality=67): "preset": "ultrafast", } - def _create_av_container(self, path, w, h, rate, options, f='mp4'): + def _add_video_stream(self, container, w, h, rate, options): # x264 requires width and height must be divisible by 2 for yuv420p if h % 2: h += 1 if w % 2: w += 1 - container = av.open(path, 'w',format=f) video_stream = container.add_stream(self._codec_name, rate=rate) video_stream.pix_fmt = "yuv420p" video_stream.width = w video_stream.height = h video_stream.options = options - return container, video_stream + return video_stream def save_as_chunk(self, images, chunk_path): if not images: @@ -745,16 +741,16 @@ def save_as_chunk(self, images, chunk_path): input_w = images[0][0].width input_h = images[0][0].height - output_container, output_v_stream = self._create_av_container( - path=chunk_path, - w=input_w, - h=input_h, - rate=self._output_fps, - options=self._codec_opts, - ) + with av.open(chunk_path, 'w', format=self.FORMAT) as output_container: + output_v_stream = self._add_video_stream( + container=output_container, + w=input_w, + h=input_h, + rate=self._output_fps, + options=self._codec_opts, + ) - self._encode_images(images, output_container, output_v_stream) - output_container.close() + self._encode_images(images, output_container, output_v_stream) return [(input_w, input_h)] @staticmethod @@ -797,16 +793,16 @@ def save_as_chunk(self, images, chunk_path): output_h = input_h // downscale_factor output_w = input_w // downscale_factor - output_container, output_v_stream = self._create_av_container( - path=chunk_path, - w=output_w, - h=output_h, - rate=self._output_fps, - options=self._codec_opts, - ) + with av.open(chunk_path, 'w', format=self.FORMAT) as output_container: + output_v_stream = self._add_video_stream( + container=output_container, + w=output_w, + h=output_h, + rate=self._output_fps, + options=self._codec_opts, + ) - self._encode_images(images, output_container, output_v_stream) - output_container.close() + self._encode_images(images, output_container, output_v_stream) return [(input_w, input_h)] def _is_archive(path): From 19fefc5e3bc3f8cc4a94d269e2125dda950eedc5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 19 Jul 2023 15:22:21 +0300 Subject: [PATCH 19/48] Fixed calculating statistics in case with removed frames (#6493) ### Motivation and context ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [x] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- CHANGELOG.md | 2 ++ cvat-core/package.json | 2 +- cvat-core/src/annotations-collection.ts | 24 +++++++++++++++++++----- cvat-core/tests/api/annotations.js | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 604f34deb964..759993515dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - TDB ### Fixed +- Calculating number of objects on annotation view when frames are deleted + () - \[SDK\] Ability to create attributes with blank default values () - \[SDK\] SDK should not change input data in models () diff --git a/cvat-core/package.json b/cvat-core/package.json index b35b9122a9f0..5aca1d1ab763 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "9.2.0", + "version": "9.2.1", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "src/api.ts", "scripts": { diff --git a/cvat-core/src/annotations-collection.ts b/cvat-core/src/annotations-collection.ts index 8755867c4e79..cf6be26c2da3 100644 --- a/cvat-core/src/annotations-collection.ts +++ b/cvat-core/src/annotations-collection.ts @@ -659,18 +659,32 @@ export default class Collection { fillBody(Object.values(this.labels).filter((label) => !label.hasParent)); const scanTrack = (track, prefix = ''): void => { + const countInterpolatedFrames = (start: number, stop: number, lastIsKeyframe: boolean): number => { + let count = stop - start; + if (lastIsKeyframe) { + count -= 1; + } + for (let i = start + 1; lastIsKeyframe ? i < stop : i <= stop; i++) { + if (this.frameMeta.deleted_frames[i]) { + count--; + } + } + return count; + }; + const pref = prefix ? `${prefix}${sep}` : ''; const label = `${pref}${track.label.name}`; labels[label][track.shapeType].track++; const keyframes = Object.keys(track.shapes) .sort((a, b) => +a - +b) - .map((el) => +el); + .map((el) => +el) + .filter((frame) => !this.frameMeta.deleted_frames[frame]); let prevKeyframe = keyframes[0]; let visible = false; for (const keyframe of keyframes) { if (visible) { - const interpolated = keyframe - prevKeyframe - 1; + const interpolated = countInterpolatedFrames(prevKeyframe, keyframe, true); labels[label].interpolated += interpolated; labels[label].total += interpolated; } @@ -692,7 +706,7 @@ export default class Collection { } if (lastKey !== this.stopFrame && !track.get(lastKey).outside) { - const interpolated = this.stopFrame - lastKey; + const interpolated = countInterpolatedFrames(lastKey, this.stopFrame, false); labels[label].interpolated += interpolated; labels[label].total += interpolated; } @@ -719,13 +733,13 @@ export default class Collection { } const { name: label } = object.label; - if (objectType === 'tag') { + if (objectType === 'tag' && !this.frameMeta.deleted_frames[object.frame]) { labels[label].tag++; labels[label].manually++; labels[label].total++; } else if (objectType === 'track') { scanTrack(object); - } else { + } else if (!this.frameMeta.deleted_frames[object.frame]) { const { shapeType } = object as Shape; labels[label][shapeType].shape++; labels[label].manually++; diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index 3fd6727b78a9..a8bb9ba57154 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -872,7 +872,29 @@ describe('Feature: get statistics', () => { expect(statistics.label[labelName].manually).toBe(2); expect(statistics.label[labelName].interpolated).toBe(3); expect(statistics.label[labelName].total).toBe(5); + }); + test('get statistics from a job with skeletons', async () => { + const job = (await window.cvat.jobs.get({ jobID: 102 }))[0]; + await job.annotations.clear(true); + let statistics = await job.annotations.statistics(); + expect(statistics.total.manually).toBe(5); + expect(statistics.total.interpolated).toBe(443); + expect(statistics.total.tag).toBe(1); + expect(statistics.total.rectangle.shape).toBe(1); + expect(statistics.total.rectangle.track).toBe(1); + await job.frames.delete(500); // track frame + await job.frames.delete(510); // rectangle shape frame + await job.frames.delete(550); // the first keyframe of a track + statistics = await job.annotations.statistics(); + expect(statistics.total.manually).toBe(2); + expect(statistics.total.tag).toBe(0); + expect(statistics.total.rectangle.shape).toBe(0); + expect(statistics.total.interpolated).toBe(394); + await job.frames.delete(650); // intermediate frame in a track + statistics = await job.annotations.statistics(); + expect(statistics.total.interpolated).toBe(393); + await job.close(); }); }); From 8de7722e09b3df7e38078fda163d05e950ef1a4e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 19 Jul 2023 15:23:21 +0300 Subject: [PATCH 20/48] Fixed using initial frame from query parameter (#6506) ### Motivation and context Resolved #6505 ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [x] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [x] I have added tests to cover my changes - [x] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- CHANGELOG.md | 2 ++ cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 9 +++++---- ...ontinue_frame_n.js => navigate_specific_frame.js} | 12 +++++++++++- 4 files changed, 19 insertions(+), 6 deletions(-) rename tests/cypress/e2e/actions_tasks/{continue_frame_n.js => navigate_specific_frame.js} (80%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 759993515dd3..57dd773edb26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[SDK\] SDK should not change input data in models () - 3D job can not be opened in validation mode () - Memory leak related to unclosed av container () +- Using initial frame from query parameter to open specific frame in a job + () ### Security - TDB diff --git a/cvat-ui/package.json b/cvat-ui/package.json index f506b0e81a1b..ef964a392d1c 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.53.1", + "version": "1.53.3", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 33ad6d48b9df..8b7a899b9ec3 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -942,10 +942,11 @@ export function getJobAsync( if (report) conflicts = await cvat.analytics.quality.conflicts({ reportId: report.id }); } - // navigate to correct first frame according to setup - const frameNumber = (await job.frames.search( - { notDeleted: !showDeletedFrames }, job.startFrame, job.stopFrame, - )) || job.startFrame; + // frame query parameter does not work for GT job + const frameNumber = Number.isInteger(initialFrame) && groundTruthJobId !== job.id ? + initialFrame : (await job.frames.search( + { notDeleted: !showDeletedFrames }, job.startFrame, job.stopFrame, + )) || job.startFrame; const frameData = await job.frames.get(frameNumber); // call first getting of frame data before rendering interface diff --git a/tests/cypress/e2e/actions_tasks/continue_frame_n.js b/tests/cypress/e2e/actions_tasks/navigate_specific_frame.js similarity index 80% rename from tests/cypress/e2e/actions_tasks/continue_frame_n.js rename to tests/cypress/e2e/actions_tasks/navigate_specific_frame.js index a369ad969ef0..ea34133eebd4 100644 --- a/tests/cypress/e2e/actions_tasks/continue_frame_n.js +++ b/tests/cypress/e2e/actions_tasks/navigate_specific_frame.js @@ -6,7 +6,7 @@ context('Paste labels from one task to another.', { browser: '!firefox' }, () => { const task = { - name: 'Test "Continue frame N"', + name: 'Test "Continue/open frame N"', label: 'Test label', attrName: 'Test attribute', attrValue: 'Test attribute value', @@ -54,5 +54,15 @@ context('Paste labels from one task to another.', { browser: '!firefox' }, () => cy.get('.cvat-notification-continue-job-button').click(); cy.checkFrameNum(2); }); + + it('Trying to open a frame using query parameter', () => { + cy.url().then(($url) => { + cy.visit('/projects'); + cy.get('.cvat-projects-page').should('exist'); + cy.visit($url, { qs: { frame: 2 } }); + cy.get('.cvat-canvas-container').should('exist').and('be.visible'); + cy.checkFrameNum(2); + }); + }); }); }); From f861aef9e9cce546af224ae70288ecb60ea7112d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 19 Jul 2023 15:25:12 +0300 Subject: [PATCH 21/48] Fixed default value for an attribute (RAW editor), refactored labels editor (#6474) ### Motivation and context Resolved #6415 Related #3113 (need to fix server part and probably throw code 400, not 500) Related #2927 (need to fix server) ![image](https://github.com/opencv/cvat/assets/40690378/016f9def-bd5f-4dcf-9a00-c6b75b0dd50f) ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [x] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [x] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- CHANGELOG.md | 2 + cvat-ui/package.json | 2 +- .../components/labels-editor/label-form.tsx | 212 ++++++++++++------ .../labels-editor/labels-editor.tsx | 4 +- 4 files changed, 150 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57dd773edb26..64e8240b5646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[Unreleased] ### Added - Multi-line text attributes supported () +- Now you can configure default attribute value for SELECT, RADIO types on UI + () - \{SDK\] `cvat_sdk.datasets`, a framework-agnostic equivalent of `cvat_sdk.pytorch` () diff --git a/cvat-ui/package.json b/cvat-ui/package.json index ef964a392d1c..43dc9dfc4320 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.53.3", + "version": "1.53.4", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 72fdbc90b0a5..600dc11af72b 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -10,8 +10,10 @@ import Input from 'antd/lib/input'; import Button from 'antd/lib/button'; import Checkbox from 'antd/lib/checkbox'; import Select from 'antd/lib/select'; +import Tag from 'antd/lib/tag'; import Form, { FormInstance } from 'antd/lib/form'; import Badge from 'antd/lib/badge'; +import Modal from 'antd/lib/modal'; import { Store } from 'antd/lib/form/interface'; import { SerializedAttribute, LabelType } from 'cvat-core-wrapper'; @@ -94,6 +96,8 @@ export default class LabelForm extends React.Component { return { ...attribute, values: attrValues, + default_value: attribute.default_value && attrValues.includes(attribute.default_value) ? + attribute.default_value : attrValues[0], input_type: attribute.type.toLowerCase(), }; }), @@ -117,7 +121,18 @@ export default class LabelForm extends React.Component { private addAttribute = (): void => { if (this.formRef.current) { const attributes = this.formRef.current.getFieldValue('attributes'); - this.formRef.current.setFieldsValue({ attributes: [...(attributes || []), { id: idGenerator() }] }); + this.formRef.current.setFieldsValue({ + attributes: [ + ...(attributes || []), + { + id: idGenerator(), + type: AttributeType.SELECT, + name: '', + values: [], + mutable: false, + }, + ], + }); } }; @@ -131,17 +146,15 @@ export default class LabelForm extends React.Component { }; /* eslint-disable class-methods-use-this */ - private renderAttributeNameInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element { + private renderAttributeNameInput(fieldInstance: any, attr: any): JSX.Element { const { key } = fieldInstance; - const locked = attr ? attr.id as number >= 0 : false; - const value = attr ? attr.name : ''; + const attrNames = this.formRef.current?.getFieldValue('attributes') + .filter((_attr: any) => _attr.id !== attr.id).map((_attr: any) => _attr.name); return ( { pattern: patterns.validateAttributeName.pattern, message: patterns.validateAttributeName.message, }, + { + validator: (_rule: any, attrName: string) => { + if (attrNames.includes(attrName) && attr.name !== attrName) { + return Promise.reject(new Error('Attribute name must be unique for the label')); + } + return Promise.resolve(); + }, + }, ]} > - + = 0} className='cvat-attribute-name-input' placeholder='Name' /> ); } - private renderAttributeTypeInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element { + private renderAttributeTypeInput(fieldInstance: any, attr: any): JSX.Element { const { key } = fieldInstance; - const locked = attr ? attr.id as number >= 0 : false; - const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT; + const locked = attr.id as number >= 0; return ( - - { + const attrs = this.formRef.current?.getFieldValue('attributes'); + if (value === AttributeType.CHECKBOX) { + attrs[key].values = ['false']; + } else if (value === AttributeType.TEXT && !attrs[key].values.length) { + attrs[key].values = ''; + } else if (value === AttributeType.NUMBER || attr.type === AttributeType.CHECKBOX) { + attrs[key].values = []; + } + this.formRef.current?.setFieldsValue({ + attributes: attrs, + }); + }} + > Select @@ -188,10 +224,10 @@ export default class LabelForm extends React.Component { ); } - private renderAttributeValuesInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element { + private renderAttributeValuesInput(fieldInstance: any, attr: any): JSX.Element { const { key } = fieldInstance; - const locked = attr ? attr.id as number >= 0 : false; - const existingValues = attr ? attr.values : []; + const locked = attr.id as number >= 0; + const existingValues = attr.values; const validator = (_: any, values: string[]): Promise => { if (locked && existingValues) { @@ -213,8 +249,6 @@ export default class LabelForm extends React.Component { { mode='tags' placeholder='Attribute values' dropdownStyle={{ display: 'none' }} + tagRender={(props) => { + const attrs = this.formRef.current?.getFieldValue('attributes'); + const isDefault = props.value === attrs[key].default_value; + return ( + + { + const parent = window.document.getElementsByClassName('cvat-attribute-values-input')[0]; + if (parent) { + parent.dispatchEvent(new MouseEvent('mouseout', { bubbles: true })); + } + }} + color={isDefault ? 'blue' : undefined} + onClose={() => { + if (isDefault) { + attrs[key].default_value = undefined; + } + props.onClose(); + }} + onClick={() => { + attrs[key].default_value = props.value; + this.formRef.current?.setFieldsValue({ + attributes: attrs, + }); + }} + closable={props.closable} + > + {props.label} + + + ); + }} /> @@ -248,7 +318,6 @@ export default class LabelForm extends React.Component { message: 'Please, specify a default value', }]} name={[key, 'values']} - fieldKey={[fieldInstance.fieldKey, 'values']} > + + ); } - private renderMutableAttributeInput(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element { + private renderMutableAttributeInput(fieldInstance: any, attr: any): JSX.Element { const { key } = fieldInstance; - const locked = attr ? attr.id as number >= 0 : false; - const value = attr ? attr.mutable : false; + const locked = attr.id as number >= 0; return ( @@ -353,19 +409,31 @@ export default class LabelForm extends React.Component { ); } - private renderDeleteAttributeButton(fieldInstance: any, attr: SerializedAttribute | null): JSX.Element { + private renderDeleteAttributeButton(fieldInstance: any, attr: any): JSX.Element { const { key } = fieldInstance; - const locked = attr ? attr.id as number >= 0 : false; return (
diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx index 2344c1025cf2..74ac036c6270 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx @@ -26,7 +26,7 @@ interface StateToProps { contextMenuParentID: number | null; contextMenuClientID: number | null; canvasInstance: Canvas | null; - objectStates: any[]; + objectStates: ObjectState[]; frameConflicts: QualityConflict[]; visible: boolean; top: number; @@ -155,7 +155,7 @@ class CanvasContextMenuContainer extends React.PureComponent { }; } - static getDerivedStateFromProps(props: Props, state: State): State | null { + static getDerivedStateFromProps(props: Readonly, state: State): State | null { if (props.left === state.latestLeft && props.top === state.latestTop) { return null; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index ac2cba8493be..56bdff93be13 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -81,7 +81,7 @@ class LabelItemContainer extends React.PureComponent { }; } - static getDerivedStateFromProps(props: Props, state: State): State | null { + static getDerivedStateFromProps(props: Readonly, state: State): State | null { if (props.objectStates === state.objectStates) { return null; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index ad9d3491e966..be9817ee6b68 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -23,6 +23,7 @@ import { import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; import { getColor } from 'components/annotation-page/standard-workspace/objects-side-bar/shared'; import { shift } from 'utils/math'; +import { Label, ObjectState } from 'cvat-core-wrapper'; import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { filterApplicableLabels } from 'utils/filter-applicable-labels'; @@ -128,7 +129,34 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { } type Props = StateToProps & DispatchToProps & OwnProps; -class ObjectItemContainer extends React.PureComponent { +interface State { + labels: Label[]; + elements: number[]; +} + +class ObjectItemContainer extends React.PureComponent { + public constructor(props: Props) { + super(props); + this.state = { + labels: props.labels, + elements: props.objectState.elements.map((el: ObjectState) => el.clientID), + }; + } + + public static getDerivedStateFromProps(props: Readonly, state: Readonly): State | null { + const { objectState, labels } = props; + const applicableLabels = filterApplicableLabels(objectState, labels); + if (state.labels.length !== applicableLabels.length || + state.labels.some((label, idx) => label.id !== applicableLabels[idx].id)) { + return { + ...state, + labels: applicableLabels, + }; + } + + return null; + } + private copy = (): void => { const { objectState, readonly, copyShape } = this.props; if (!readonly) { @@ -312,9 +340,9 @@ class ObjectItemContainer extends React.PureComponent { } public render(): JSX.Element { + const { labels, elements } = this.state; const { objectState, - labels, attributes, activated, colorBy, @@ -323,8 +351,6 @@ class ObjectItemContainer extends React.PureComponent { jobInstance, } = this.props; - const applicableLabels = filterApplicableLabels(objectState, labels); - return ( { isGroundTruth={objectState.isGroundTruth} color={getColor(objectState, colorBy)} attributes={attributes} - elements={objectState.elements} + elements={elements} normalizedKeyMap={normalizedKeyMap} - labels={applicableLabels} + labels={labels} colorBy={colorBy} activate={this.activate} remove={this.remove} @@ -354,7 +380,7 @@ class ObjectItemContainer extends React.PureComponent { changeColor={this.changeColor} changeLabel={this.changeLabel} edit={this.edit} - resetCuboidPerspective={() => this.resetCuboidPerspective()} + resetCuboidPerspective={this.resetCuboidPerspective} /> ); } From 6e5ac4b10b5484eaacee7f9fabd7568ac42bd252 Mon Sep 17 00:00:00 2001 From: Kirill Sizov Date: Mon, 24 Jul 2023 15:50:24 +0300 Subject: [PATCH 36/48] Fix keyframe removing (#6494) Steps to reproduce this bug: Create a track that starts on the frame i and has keyframe on the frame j (i < j) Save Remove the keyframe for the created track on the frame i Save --- CHANGELOG.md | 1 + cvat/apps/dataset_manager/task.py | 1 + tests/python/rest_api/test_tasks.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d530f6e285e..dd363d96d59a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed SAM plugin (403 code for workers in organizations) () - Using initial frame from query parameter to open specific frame in a job () +- Problem with first keyframe removing () - Server-side validation for attribute specifications () - \[API\] File downloading failures for filenames with special characters l() diff --git a/cvat/apps/dataset_manager/task.py b/cvat/apps/dataset_manager/task.py index 66fd499a6002..1269883e30fa 100644 --- a/cvat/apps/dataset_manager/task.py +++ b/cvat/apps/dataset_manager/task.py @@ -160,6 +160,7 @@ def _add_missing_shape(self, track, first_shape): missing_shape = deepcopy(first_shape) missing_shape["frame"] = track["frame"] missing_shape["outside"] = True + missing_shape.pop("id") track["shapes"].append(missing_shape) def _correct_frame_of_tracked_shapes(self, track): diff --git a/tests/python/rest_api/test_tasks.py b/tests/python/rest_api/test_tasks.py index 0184f66e56c9..98c443b290bd 100644 --- a/tests/python/rest_api/test_tasks.py +++ b/tests/python/rest_api/test_tasks.py @@ -517,6 +517,22 @@ def test_member_update_task_annotation( self._test_check_response(is_allow, response, data) + def test_remove_first_keyframe(self): + endpoint = "tasks/8/annotations" + shapes0 = [ + {"type": "rectangle", "frame": 1, "points": [1, 2, 3, 4]}, + {"type": "rectangle", "frame": 4, "points": [5, 6, 7, 8]}, + ] + + annotations = {"tracks": [{"label_id": 13, "frame": 0, "shapes": shapes0}]} + + response = patch_method("admin1", endpoint, annotations, action="create") + assert response.status_code == HTTPStatus.OK, response.content + + annotations["tracks"][0]["shapes"] = shapes0[1:] + response = patch_method("admin1", endpoint, annotations, action="update") + assert response.status_code == HTTPStatus.OK + @pytest.mark.usefixtures("restore_db_per_class") class TestGetTaskDataset: From d72e954b4fc28d78821583d8eb1a16beca6e5021 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov Date: Mon, 24 Jul 2023 19:17:13 +0300 Subject: [PATCH 37/48] Fix BrowsableAPIRenderer for server viewset (#6553) ### Motivation and context Resolved #6550 ```python ERROR:django.request:Internal Server Error: /api/server/annotation/formats Traceback (most recent call last): File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/django/core/handlers/base.py", line 220, in _get_response response = response.render() File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/django/template/response.py", line 114, in render self.content = self.rendered_content File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/response.py", line 70, in rendered_content ret = renderer.render(self.data, accepted_media_type, context) File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/renderers.py", line 723, in render context = self.get_context(data, accepted_media_type, renderer_context) File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/renderers.py", line 697, in get_context 'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request), File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/renderers.py", line 475, in get_rendered_html_form if not self.show_form_for_method(view, method, request, instance): File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/renderers.py", line 432, in show_form_for_method view.check_object_permissions(request, obj) File "/home/andrey/workspace/cvat.ai/cvat/.env/lib/python3.10/site-packages/rest_framework/views.py", line 345, in check_object_permissions if not permission.has_object_permission(request, self, obj): File "/home/andrey/workspace/cvat.ai/cvat/cvat/apps/iam/permissions.py", line 2015, in has_object_permission return self.check_permission(request, view, obj) File "/home/andrey/workspace/cvat.ai/cvat/cvat/apps/iam/permissions.py", line 1990, in check_permission iam_context = get_iam_context(request, obj) File "/home/andrey/workspace/cvat.ai/cvat/cvat/apps/iam/permissions.py", line 88, in get_iam_context organization = get_organization(request, obj) File "/home/andrey/workspace/cvat.ai/cvat/cvat/apps/iam/permissions.py", line 68, in get_organization raise exc File "/home/andrey/workspace/cvat.ai/cvat/cvat/apps/iam/permissions.py", line 61, in get_organization organization_id = getattr(obj, 'organization_id') AttributeError: 'dict' object has no attribute 'organization_id' ```` ### How has this been tested? ```terminal curl --header "Accept: text/html" --user "user:pass" http://localhost:8080/api/server/annotation/formats ``` ### Checklist - [x] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. --- cvat/apps/engine/views.py | 4 ++-- cvat/apps/iam/permissions.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 4d498d495a0a..3740d2a47568 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -194,13 +194,13 @@ def annotation_formats(request): }) @action(detail=False, methods=['GET'], url_path='plugins', serializer_class=PluginsSerializer) def plugins(request): - response = { + data = { 'GIT_INTEGRATION': apps.is_installed('cvat.apps.dataset_repo'), 'ANALYTICS': strtobool(os.environ.get("CVAT_ANALYTICS", '0')), 'MODELS': strtobool(os.environ.get("CVAT_SERVERLESS", '0')), 'PREDICT': False, # FIXME: it is unused anymore (for UI only) } - return Response(response) + return Response(PluginsSerializer(data).data) @extend_schema(tags=['projects']) @extend_schema_view( diff --git a/cvat/apps/iam/permissions.py b/cvat/apps/iam/permissions.py index 2a1893e8b1b9..f568415fbec9 100644 --- a/cvat/apps/iam/permissions.py +++ b/cvat/apps/iam/permissions.py @@ -62,7 +62,7 @@ def get_organization(request, obj): except AttributeError as exc: # Skip initialization of organization for those objects that don't related with organization view = request.parser_context.get('view') - if view and view.basename in ('user', 'function', 'request',): + if view and view.basename in ('user', 'function', 'request', 'server'): return request.iam_context['organization'] raise exc From a79052f69dac92bcf4d20c13e64bdc69c31cceeb Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Tue, 25 Jul 2023 12:21:55 +0300 Subject: [PATCH 38/48] Project/Task/Job Analytics (#6371) --- .github/workflows/black.yml | 1 + .github/workflows/isort.yml | 1 + .stylelintrc.json | 6 +- .vscode/launch.json | 43 ++ CHANGELOG.md | 1 + cvat-canvas/src/typescript/shared.ts | 2 +- cvat-core/package.json | 2 +- cvat-core/src/analytics-report.ts | 183 ++++++ cvat-core/src/api-implementation.ts | 34 ++ cvat-core/src/api.ts | 6 + cvat-core/src/enums.ts | 1 + cvat-core/src/object-state.ts | 2 +- cvat-core/src/server-proxy.ts | 20 + cvat-ui/package.json | 4 +- .../analytics-page/analytics-page.tsx | 364 ++++++++++++ .../analytics-page/analytics-performance.tsx | 194 +++++++ .../quality/common.ts | 0 .../quality/empty-job.tsx | 0 .../quality/gt-conflicts.tsx | 2 +- .../quality/issues.tsx | 2 +- .../quality/job-list.tsx | 0 .../quality/mean-quality.tsx | 2 +- .../quality/quality-settings-modal.tsx | 0 .../quality/task-quality-component.tsx | 0 .../styles.scss | 22 +- .../views}/analytics-card.tsx | 2 - .../analytics-page/views/histogram-view.tsx | 69 +++ cvat-ui/src/components/cvat-app.tsx | 6 +- .../components/job-item/job-actions-menu.tsx | 3 + .../components/projects-page/actions-menu.tsx | 21 +- .../task-analytics-page.tsx | 143 ----- cvat-ui/src/cvat-core-wrapper.ts | 4 + cvat/apps/analytics_report/__init__.py | 0 cvat/apps/analytics_report/apps.py | 20 + .../apps/analytics_report/default_settings.py | 8 + .../migrations/0001_initial.py | 58 ++ .../analytics_report/migrations/__init__.py | 0 cvat/apps/analytics_report/models.py | 132 +++++ cvat/apps/analytics_report/pyproject.toml | 12 + cvat/apps/analytics_report/report/__init__.py | 0 cvat/apps/analytics_report/report/create.py | 533 ++++++++++++++++++ .../report/derived_metrics/__init__.py | 13 + .../derived_metrics/annotation_speed.py | 55 ++ .../report/derived_metrics/annotation_time.py | 31 + .../report/derived_metrics/base.py | 12 + .../report/derived_metrics/objects.py | 61 ++ .../derived_metrics/total_annotation_speed.py | 66 +++ .../derived_metrics/total_object_count.py | 59 ++ cvat/apps/analytics_report/report/get.py | 164 ++++++ .../report/primary_metrics/__init__.py | 8 + .../primary_metrics/annotation_speed.py | 136 +++++ .../report/primary_metrics/annotation_time.py | 54 ++ .../report/primary_metrics/base.py | 66 +++ .../report/primary_metrics/objects.py | 58 ++ .../report/primary_metrics/utils.py | 21 + cvat/apps/analytics_report/serializers.py | 83 +++ cvat/apps/analytics_report/signals.py | 30 + cvat/apps/analytics_report/urls.py | 12 + cvat/apps/analytics_report/views.py | 197 +++++++ cvat/apps/dataset_manager/bindings.py | 6 +- cvat/apps/dataset_manager/formats/cvat.py | 10 +- cvat/apps/dataset_manager/task.py | 22 +- ...urce_alter_labeledshape_source_and_more.py | 57 ++ cvat/apps/engine/models.py | 1 + cvat/apps/engine/views.py | 2 - cvat/apps/events/handlers.py | 2 +- cvat/apps/iam/permissions.py | 79 +++ cvat/apps/iam/rules/analytics_reports.rego | 33 ++ cvat/schema.yml | 234 ++++++++ cvat/settings/base.py | 8 + cvat/settings/development.py | 1 + cvat/settings/testing_rest.py | 1 + cvat/urls.py | 3 + dev/format_python_code.sh | 8 +- docker-compose.dev.yml | 13 + docker-compose.yml | 23 + helm-chart/Chart.yaml | 2 +- .../worker_analyticsreports/deployment.yml | 125 ++++ helm-chart/values.yaml | 10 + package.json | 2 + supervisord/worker.analytics_reports.conf | 27 + .../python/rest_api/test_analytics_reports.py | 379 +++++++++++++ yarn.lock | 111 +++- 83 files changed, 3995 insertions(+), 193 deletions(-) create mode 100644 cvat-core/src/analytics-report.ts create mode 100644 cvat-ui/src/components/analytics-page/analytics-page.tsx create mode 100644 cvat-ui/src/components/analytics-page/analytics-performance.tsx rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/common.ts (100%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/empty-job.tsx (100%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/gt-conflicts.tsx (98%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/issues.tsx (97%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/job-list.tsx (100%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/mean-quality.tsx (98%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/quality-settings-modal.tsx (100%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/quality/task-quality-component.tsx (100%) rename cvat-ui/src/components/{task-analytics-page => analytics-page}/styles.scss (89%) rename cvat-ui/src/components/{task-analytics-page/quality => analytics-page/views}/analytics-card.tsx (98%) create mode 100644 cvat-ui/src/components/analytics-page/views/histogram-view.tsx delete mode 100644 cvat-ui/src/components/task-analytics-page/task-analytics-page.tsx create mode 100644 cvat/apps/analytics_report/__init__.py create mode 100644 cvat/apps/analytics_report/apps.py create mode 100644 cvat/apps/analytics_report/default_settings.py create mode 100644 cvat/apps/analytics_report/migrations/0001_initial.py create mode 100644 cvat/apps/analytics_report/migrations/__init__.py create mode 100644 cvat/apps/analytics_report/models.py create mode 100644 cvat/apps/analytics_report/pyproject.toml create mode 100644 cvat/apps/analytics_report/report/__init__.py create mode 100644 cvat/apps/analytics_report/report/create.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/__init__.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/annotation_speed.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/annotation_time.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/base.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/objects.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/total_annotation_speed.py create mode 100644 cvat/apps/analytics_report/report/derived_metrics/total_object_count.py create mode 100644 cvat/apps/analytics_report/report/get.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/__init__.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/annotation_speed.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/annotation_time.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/base.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/objects.py create mode 100644 cvat/apps/analytics_report/report/primary_metrics/utils.py create mode 100644 cvat/apps/analytics_report/serializers.py create mode 100644 cvat/apps/analytics_report/signals.py create mode 100644 cvat/apps/analytics_report/urls.py create mode 100644 cvat/apps/analytics_report/views.py create mode 100644 cvat/apps/engine/migrations/0074_alter_labeledimage_source_alter_labeledshape_source_and_more.py create mode 100644 cvat/apps/iam/rules/analytics_reports.rego create mode 100644 helm-chart/templates/cvat_backend/worker_analyticsreports/deployment.yml create mode 100644 supervisord/worker.analytics_reports.conf create mode 100644 tests/python/rest_api/test_analytics_reports.py diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 1fa9da8a9634..6f75c038359c 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -13,6 +13,7 @@ jobs: cvat-cli/**/*.py tests/python/**/*.py cvat/apps/quality_control/**/*.py + cvat/apps/analytics_report/**/*.py dir_names: true - name: Run checks diff --git a/.github/workflows/isort.yml b/.github/workflows/isort.yml index 87e2d9957b28..4ed521885111 100644 --- a/.github/workflows/isort.yml +++ b/.github/workflows/isort.yml @@ -13,6 +13,7 @@ jobs: cvat-cli/**/*.py tests/python/**/*.py cvat/apps/quality_control/**/*.py + cvat/apps/analytics_report/**/*.py dir_names: true - name: Run checks diff --git a/.stylelintrc.json b/.stylelintrc.json index 44333abf546d..86f582a6960e 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,5 +1,5 @@ { - "extends": "stylelint-config-standard", + "extends": "stylelint-config-standard-scss", "rules": { "indentation": 4, "value-keyword-case": null, @@ -16,7 +16,9 @@ { "ignoreTypes": ["first-child"] } - ] + ], + "scss/comment-no-empty": null, + "scss/at-extend-no-missing-placeholder": null }, "ignoreFiles": ["**/*.js", "**/*.ts", "**/*.py"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index a9e3d672c97b..366bd49a3fa1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -104,6 +104,26 @@ ], "justMyCode": false, }, + { + "name": "REST API tests: Attach to RQ analytics reports worker", + "type": "python", + "request": "attach", + "connect": { + "host": "127.0.0.1", + "port": 9095 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/home/django/" + }, + { + "localRoot": "${workspaceFolder}/.env", + "remoteRoot": "/opt/venv", + } + ], + "justMyCode": false, + }, { "type": "pwa-chrome", "request": "launch", @@ -240,6 +260,28 @@ }, "console": "internalConsole" }, + { + "name": "server: RQ - analytics reports", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "justMyCode": false, + "python": "${command:python.interpreterPath}", + "program": "${workspaceRoot}/manage.py", + "args": [ + "rqworker", + "analytics_reports", + "--worker-class", + "cvat.rqworker.SimpleWorker", + ], + "django": true, + "cwd": "${workspaceFolder}", + "env": { + "DJANGO_LOG_SERVER_HOST": "localhost", + "DJANGO_LOG_SERVER_PORT": "8282" + }, + "console": "internalConsole" + }, { "name": "server: RQ - scheduler", "type": "python", @@ -499,6 +541,7 @@ "server: RQ - webhooks", "server: RQ - scheduler", "server: RQ - quality reports", + "server: RQ - analytics reports", "server: RQ - cleaning", "server: git", ] diff --git a/CHANGELOG.md b/CHANGELOG.md index dd363d96d59a..14ae97882608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - \[SDK\] `cvat_sdk.datasets`, a framework-agnostic equivalent of `cvat_sdk.pytorch` () +- Analytics for Jobs, Tasks and Projects () ### Changed - TDB diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 75d69be5ac75..9e89616a86f5 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -41,7 +41,7 @@ export interface DrawnState { occluded?: boolean; hidden?: boolean; lock: boolean; - source: 'AUTO' | 'SEMI-AUTO' | 'MANUAL'; + source: 'AUTO' | 'SEMI-AUTO' | 'MANUAL' | 'FILE'; shapeType: string; points?: number[]; rotation: number; diff --git a/cvat-core/package.json b/cvat-core/package.json index 5aca1d1ab763..d8970e107443 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "9.2.1", + "version": "9.3.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "src/api.ts", "scripts": { diff --git a/cvat-core/src/analytics-report.ts b/cvat-core/src/analytics-report.ts new file mode 100644 index 000000000000..8d390046d4ab --- /dev/null +++ b/cvat-core/src/analytics-report.ts @@ -0,0 +1,183 @@ +// Copyright (C) 2023 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { ArgumentError } from './exceptions'; + +export interface SerializedDataEntry { + date?: string; + value?: number | Record +} + +export interface SerializedTransformBinaryOp { + left: string; + operator: string; + right: string; +} + +export interface SerializedTransformationEntry { + name: string; + binary?: SerializedTransformBinaryOp; +} + +export interface SerializedAnalyticsEntry { + name?: string; + title?: string; + description?: string; + granularity?: string; + default_view?: string; + data_series?: Record; + transformations?: SerializedTransformationEntry[]; +} + +export interface SerializedAnalyticsReport { + id?: number; + target?: string; + created_date?: string; + statistics?: SerializedAnalyticsEntry[]; +} + +export enum AnalyticsReportTarget { + JOB = 'job', + TASK = 'task', + PROJECT = 'project', +} + +export enum AnalyticsEntryViewType { + HISTOGRAM = 'histogram', + NUMERIC = 'numeric', +} + +export class AnalyticsEntry { + #name: string; + #title: string; + #description: string; + #granularity: string; + #defaultView: AnalyticsEntryViewType; + #dataSeries: Record; + #transformations: SerializedTransformationEntry[]; + + constructor(initialData: SerializedAnalyticsEntry) { + this.#name = initialData.name; + this.#title = initialData.title; + this.#description = initialData.description; + this.#granularity = initialData.granularity; + this.#defaultView = initialData.default_view as AnalyticsEntryViewType; + this.#transformations = initialData.transformations; + this.#dataSeries = this.applyTransformations(initialData.data_series); + } + + get name(): string { + return this.#name; + } + + get title(): string { + return this.#title; + } + + get description(): string { + return this.#description; + } + + // Probably need to create enum for this + get granularity(): string { + return this.#granularity; + } + + get defaultView(): AnalyticsEntryViewType { + return this.#defaultView; + } + + get dataSeries(): Record { + return this.#dataSeries; + } + + get transformations(): SerializedTransformationEntry[] { + return this.#transformations; + } + + private applyTransformations( + dataSeries: Record, + ): Record { + this.#transformations.forEach((transform) => { + if (transform.binary) { + let operator: (left: number, right: number) => number; + switch (transform.binary.operator) { + case '+': { + operator = (left: number, right: number) => left + right; + break; + } + case '-': { + operator = (left: number, right: number) => left - right; + break; + } + case '*': { + operator = (left: number, right: number) => left * right; + break; + } + case '/': { + operator = (left: number, right: number) => (right !== 0 ? left / right : 0); + break; + } + default: { + throw new ArgumentError( + `Cannot apply transformation: got unsupported operator type ${transform.binary.operator}.`, + ); + } + } + + const leftName = transform.binary.left; + const rightName = transform.binary.right; + dataSeries[transform.name] = dataSeries[leftName].map((left, i) => { + const right = dataSeries[rightName][i]; + if (typeof left.value === 'number' && typeof right.value === 'number') { + return { + value: operator(left.value, right.value), + date: left.date, + }; + } + return { + value: 0, + date: left.date, + }; + }); + delete dataSeries[leftName]; + delete dataSeries[rightName]; + } + }); + return dataSeries; + } +} + +export default class AnalyticsReport { + #id: number; + #target: AnalyticsReportTarget; + #createdDate: string; + #statistics: AnalyticsEntry[]; + + constructor(initialData: SerializedAnalyticsReport) { + this.#id = initialData.id; + this.#target = initialData.target as AnalyticsReportTarget; + this.#createdDate = initialData.created_date; + this.#statistics = []; + for (const analyticsEntry of initialData.statistics) { + this.#statistics.push(new AnalyticsEntry(analyticsEntry)); + } + } + + get id(): number { + return this.#id; + } + + get target(): AnalyticsReportTarget { + return this.#target; + } + + get createdDate(): string { + return this.#createdDate; + } + + get statistics(): AnalyticsEntry[] { + return this.#statistics; + } +} diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts index 6e17a98b4e9d..56e8c1f69701 100644 --- a/cvat-core/src/api-implementation.ts +++ b/cvat-core/src/api-implementation.ts @@ -32,6 +32,7 @@ import QualityReport from './quality-report'; import QualityConflict from './quality-conflict'; import QualitySettings from './quality-settings'; import { FramesMetaData } from './frames'; +import AnalyticsReport from './analytics-report'; export default function implementAPI(cvat) { cvat.plugins.list.implementation = PluginRegistry.list; @@ -404,5 +405,38 @@ export default function implementAPI(cvat) { return new FramesMetaData({ ...result }); }; + cvat.analytics.performance.reports.implementation = async (filter) => { + checkFilter(filter, { + jobID: isInteger, + taskID: isInteger, + projectID: isInteger, + startDate: isString, + endDate: isString, + }); + + checkExclusiveFields(filter, ['jobID', 'taskID', 'projectID'], ['startDate', 'endDate']); + + const updatedParams: Record = {}; + + if ('taskID' in filter) { + updatedParams.task_id = filter.taskID; + } + if ('jobID' in filter) { + updatedParams.job_id = filter.jobID; + } + if ('projectID' in filter) { + updatedParams.project_id = filter.projectID; + } + if ('startDate' in filter) { + updatedParams.start_date = filter.startDate; + } + if ('endDate' in filter) { + updatedParams.end_date = filter.endDate; + } + + const reportData = await serverProxy.analytics.performance.reports(updatedParams); + return new AnalyticsReport(reportData); + }; + return cvat; } diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index 72075a2cd97b..4ff47db6ad12 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -279,6 +279,12 @@ function build() { }, }, analytics: { + performance: { + async reports(filter = {}) { + const result = await PluginRegistry.apiWrapper(cvat.analytics.performance.reports, filter); + return result; + }, + }, quality: { async reports(filter: any) { const result = await PluginRegistry.apiWrapper(cvat.analytics.quality.reports, filter); diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts index 10d381a56e4e..1de28c95237a 100644 --- a/cvat-core/src/enums.ts +++ b/cvat-core/src/enums.ts @@ -86,6 +86,7 @@ export enum Source { MANUAL = 'manual', SEMI_AUTO = 'semi-auto', AUTO = 'auto', + FILE = 'file', } export enum LogType { diff --git a/cvat-core/src/object-state.ts b/cvat-core/src/object-state.ts index 45b652b32b3d..4f7e78ad61c9 100644 --- a/cvat-core/src/object-state.ts +++ b/cvat-core/src/object-state.ts @@ -466,7 +466,7 @@ export default class ObjectState { }), ); - if ([Source.MANUAL, Source.SEMI_AUTO, Source.AUTO].includes(serialized.source)) { + if ([Source.MANUAL, Source.SEMI_AUTO, Source.AUTO, Source.FILE].includes(serialized.source)) { data.source = serialized.source; } if (typeof serialized.zOrder === 'number') { diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index 057d1c3ed8ef..6b438d6ca168 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -15,6 +15,7 @@ import { } from 'server-response-types'; import { SerializedQualityReportData } from 'quality-report'; import { SerializedQualitySettingsData } from 'quality-settings'; +import { SerializedAnalyticsReport } from './analytics-report'; import { Storage } from './storage'; import { StorageLocation, WebhookSourceType } from './enums'; import { isEmail, isResourceURL } from './common'; @@ -2317,6 +2318,22 @@ async function getQualityReports(filter): Promise } } +async function getAnalyticsReports(filter): Promise { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/analytics/reports`, { + params: { + ...filter, + }, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + export default Object.freeze({ server: Object.freeze({ setAuthData, @@ -2474,6 +2491,9 @@ export default Object.freeze({ }), analytics: Object.freeze({ + performance: Object.freeze({ + reports: getAnalyticsReports, + }), quality: Object.freeze({ reports: getQualityReports, conflicts: getQualityConflicts, diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 43dc9dfc4320..0274e31bb68e 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.53.4", + "version": "1.54.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -37,6 +37,7 @@ "@types/resize-observer-browser": "^0.1.6", "@uiw/react-md-editor": "^3.22.0", "antd": "~4.18.9", + "chart.js": "^4.3.0", "copy-to-clipboard": "^3.3.1", "cvat-canvas": "link:./../cvat-canvas", "cvat-canvas3d": "link:./../cvat-canvas3d", @@ -53,6 +54,7 @@ "prop-types": "^15.7.2", "react": "^16.14.0", "react-awesome-query-builder": "^4.5.1", + "react-chartjs-2": "^5.2.0", "react-color": "^2.19.3", "react-cookie": "^4.0.3", "react-dom": "^16.14.0", diff --git a/cvat-ui/src/components/analytics-page/analytics-page.tsx b/cvat-ui/src/components/analytics-page/analytics-page.tsx new file mode 100644 index 000000000000..bb0a54d98888 --- /dev/null +++ b/cvat-ui/src/components/analytics-page/analytics-page.tsx @@ -0,0 +1,364 @@ +// Copyright (C) 2023 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useCallback, useEffect, useState } from 'react'; +import { useLocation, useParams } from 'react-router'; +import { Link } from 'react-router-dom'; +import { Row, Col } from 'antd/lib/grid'; +import Tabs from 'antd/lib/tabs'; +import Text from 'antd/lib/typography/Text'; +import Title from 'antd/lib/typography/Title'; +import notification from 'antd/lib/notification'; +import { useIsMounted } from 'utils/hooks'; +import { Project, Task } from 'reducers'; +import { AnalyticsReport, Job, getCore } from 'cvat-core-wrapper'; +import moment from 'moment'; +import CVATLoadingSpinner from 'components/common/loading-spinner'; +import GoBackButton from 'components/common/go-back-button'; +import AnalyticsOverview, { DateIntervals } from './analytics-performance'; +import TaskQualityComponent from './quality/task-quality-component'; + +const core = getCore(); + +function handleTimePeriod(interval: DateIntervals): [string, string] { + const now = moment.utc(); + switch (interval) { + case DateIntervals.LAST_WEEK: { + return [now.format(), now.subtract(7, 'd').format()]; + } + case DateIntervals.LAST_MONTH: { + return [now.format(), now.subtract(30, 'd').format()]; + } + case DateIntervals.LAST_QUARTER: { + return [now.format(), now.subtract(90, 'd').format()]; + } + case DateIntervals.LAST_YEAR: { + return [now.format(), now.subtract(365, 'd').format()]; + } + default: { + throw Error(`Date interval is not supported: ${interval}`); + } + } +} + +function AnalyticsPage(): JSX.Element { + const location = useLocation(); + let instanceType = ''; + if (location.pathname.includes('projects')) { + instanceType = 'project'; + } else if (location.pathname.includes('jobs')) { + instanceType = 'job'; + } else { + instanceType = 'task'; + } + + const [fetching, setFetching] = useState(true); + const [instance, setInstance] = useState(null); + const [analyticsReportInstance, setAnalyticsReportInstance] = useState(null); + const isMounted = useIsMounted(); + + let instanceID: number | null = null; + let reportRequestID: number | null = null; + switch (instanceType) { + case 'project': { + instanceID = +useParams<{ pid: string }>().pid; + reportRequestID = +useParams<{ pid: string }>().pid; + break; + } + case 'task': { + instanceID = +useParams<{ tid: string }>().tid; + reportRequestID = +useParams<{ tid: string }>().tid; + break; + } + case 'job': { + instanceID = +useParams<{ jid: string }>().jid; + reportRequestID = +useParams<{ jid: string }>().jid; + break; + } + default: { + throw new Error(`Unsupported instance type ${instanceType}`); + } + } + + const receieveInstance = (): void => { + let instanceRequest = null; + switch (instanceType) { + case 'project': { + instanceRequest = core.projects.get({ id: instanceID }); + break; + } + case 'task': { + instanceRequest = core.tasks.get({ id: instanceID }); + break; + } + case 'job': + { + instanceRequest = core.jobs.get({ jobID: instanceID }); + break; + } + default: { + throw new Error(`Unsupported instance type ${instanceType}`); + } + } + + if (Number.isInteger(instanceID)) { + instanceRequest + .then(([_instance]: Task[] | Project[] | Job[]) => { + if (isMounted() && _instance) { + setInstance(_instance); + } + }).catch((error: Error) => { + if (isMounted()) { + notification.error({ + message: 'Could not receive the requested instance from the server', + description: error.toString(), + }); + } + }).finally(() => { + if (isMounted()) { + setFetching(false); + } + }); + } else { + notification.error({ + message: 'Could not receive the requested task from the server', + description: `Requested "${instanceID}" is not valid`, + }); + setFetching(false); + } + }; + + const receieveReport = (timeInterval: DateIntervals): void => { + if (Number.isInteger(instanceID) && Number.isInteger(reportRequestID)) { + let reportRequest = null; + const [endDate, startDate] = handleTimePeriod(timeInterval); + + switch (instanceType) { + case 'project': { + reportRequest = core.analytics.performance.reports({ + projectID: reportRequestID, + endDate, + startDate, + }); + break; + } + case 'task': { + reportRequest = core.analytics.performance.reports({ + taskID: reportRequestID, + endDate, + startDate, + }); + break; + } + case 'job': { + reportRequest = core.analytics.performance.reports({ + jobID: reportRequestID, + endDate, + startDate, + }); + break; + } + default: { + throw new Error(`Unsupported instance type ${instanceType}`); + } + } + + reportRequest + .then((report: AnalyticsReport) => { + if (isMounted() && report) { + setAnalyticsReportInstance(report); + } + }).catch((error: Error) => { + if (isMounted()) { + notification.error({ + message: 'Could not receive the requested report from the server', + description: error.toString(), + }); + } + }); + } + }; + + useEffect((): void => { + Promise.all([receieveInstance(), receieveReport(DateIntervals.LAST_WEEK)]).finally(() => { + if (isMounted()) { + setFetching(false); + } + }); + }, []); + + const onJobUpdate = useCallback((job: Job): void => { + setFetching(true); + job.save().then(() => { + if (isMounted()) { + receieveInstance(); + } + }).catch((error: Error) => { + if (isMounted()) { + notification.error({ + message: 'Could not update the job', + description: error.toString(), + }); + } + }).finally(() => { + if (isMounted()) { + setFetching(false); + } + }); + }, []); + + const onAnalyticsTimePeriodChange = useCallback((val: DateIntervals): void => { + receieveReport(val); + }, []); + + let backNavigation: JSX.Element | null = null; + let title: JSX.Element | null = null; + let tabs: JSX.Element | null = null; + if (instance) { + switch (instanceType) { + case 'project': { + backNavigation = ( + + + + ); + title = ( + + + Analytics for + {' '} + <Link to={`/projects/${instance.id}`}>{`Project #${instance.id}`}</Link> + + + ); + tabs = ( + + + Performance + + )} + key='Overview' + > + + + + ); + break; + } + case 'task': { + backNavigation = ( + + + + ); + title = ( + + + Analytics for + {' '} + <Link to={`/tasks/${instance.id}`}>{`Task #${instance.id}`}</Link> + + + ); + tabs = ( + + + Performance + + )} + key='overview' + > + + + + Quality + + )} + key='quality' + > + + + + ); + break; + } + case 'job': + { + backNavigation = ( + + + + ); + title = ( + + + Analytics for + {' '} + <Link to={`/tasks/${instance.taskId}/jobs/${instance.id}`}>{`Job #${instance.id}`}</Link> + + + ); + tabs = ( + + + Performance + + )} + key='overview' + > + + + + ); + break; + } + default: { + throw new Error(`Unsupported instance type ${instanceType}`); + } + } + } + + return ( +
+ { + fetching ? ( +
+ +
+ ) : ( + + {backNavigation} + + {title} + {tabs} + + + ) + } +
+ ); +} + +export default React.memo(AnalyticsPage); diff --git a/cvat-ui/src/components/analytics-page/analytics-performance.tsx b/cvat-ui/src/components/analytics-page/analytics-performance.tsx new file mode 100644 index 000000000000..061486039cfb --- /dev/null +++ b/cvat-ui/src/components/analytics-page/analytics-performance.tsx @@ -0,0 +1,194 @@ +// Copyright (C) 2023 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; + +import React from 'react'; +import moment from 'moment'; +import RGL, { WidthProvider } from 'react-grid-layout'; +import Text from 'antd/lib/typography/Text'; +import Select from 'antd/lib/select'; +import Notification from 'antd/lib/notification'; +import { AnalyticsReport, AnalyticsEntryViewType } from 'cvat-core-wrapper'; +import { Col, Row } from 'antd/lib/grid'; +import HistogramView from './views/histogram-view'; +import AnalyticsCard from './views/analytics-card'; + +const ReactGridLayout = WidthProvider(RGL); + +export enum DateIntervals { + LAST_WEEK = 'Last 7 days', + LAST_MONTH = 'Last 30 days', + LAST_QUARTER = 'Last 90 days', + LAST_YEAR = 'Last year', +} + +interface Props { + report: AnalyticsReport | null; + onTimePeriodChange: (val: DateIntervals) => void; +} + +const colors = [ + 'rgba(255, 99, 132, 0.5)', + 'rgba(53, 162, 235, 0.5)', + 'rgba(170, 83, 85, 0.5)', + 'rgba(44, 70, 94, 0.5)', + 'rgba(28, 66, 98, 0.5)', +]; + +function AnalyticsOverview(props: Props): JSX.Element | null { + const { report, onTimePeriodChange } = props; + + if (!report) return null; + const layout: any = []; + let histogramCount = 0; + let numericCount = 0; + const views: any = []; + report.statistics.forEach((entry) => { + const tooltip = ( +
+ + {entry.description} + +
+ ); + switch (entry.defaultView) { + case AnalyticsEntryViewType.NUMERIC: { + layout.push({ + i: entry.name, + w: 2, + h: 1, + x: 2, + y: numericCount, + }); + numericCount += 1; + const { value } = entry.dataSeries[Object.keys(entry.dataSeries)[0]][0]; + + views.push({ + view: ( + + ), + key: entry.name, + }); + break; + } + case AnalyticsEntryViewType.HISTOGRAM: { + const firstDataset = Object.keys(entry.dataSeries)[0]; + const dateLabels = entry.dataSeries[firstDataset].map((dataEntry) => ( + moment.utc(dataEntry.date).local().format('YYYY-MM-DD') + )); + + const { dataSeries } = entry; + let colorIndex = -1; + const datasets = Object.entries(dataSeries).map(([key, series]) => { + let label = key.split('_').join(' '); + label = label.charAt(0).toUpperCase() + label.slice(1); + + const data: number[] = series.map((s) => { + if (typeof s.value === 'number') { + return s.value as number; + } + + if (typeof s.value === 'object') { + return Object.keys(s.value).reduce((acc, k) => acc + s.value[k], 0); + } + + return 0; + }); + + colorIndex = colorIndex >= colors.length - 1 ? 0 : colorIndex + 1; + return { + label, + data, + backgroundColor: colors[colorIndex], + }; + }); + layout.push({ + i: entry.name, + h: 1, + w: 2, + x: 0, + y: histogramCount, + }); + histogramCount += 1; + views.push({ + view: ( + + ), + key: entry.name, + }); + break; + } + default: { + Notification.warning({ + message: `Cannot display analytics view with view type ${entry.defaultView}`, + }); + } + } + }); + return ( +
+ + + + Created + {report?.createdDate ? moment(report?.createdDate).fromNow() : ''} + + + +