Merge branch 'release-1.7.0'

main
Nikita Manovich 4 years ago
commit 967b0fee2c

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
module.exports = {
root: true,
env: {
node: true,
browser: true,
@ -12,12 +13,43 @@ module.exports = {
sourceType: 'module',
ecmaVersion: 2018,
},
plugins: ['eslint-plugin-header'],
extends: ['eslint:recommended', 'prettier'],
ignorePatterns: [
'.eslintrc.js',
'lint-staged.config.js',
],
plugins: ['security', 'no-unsanitized', 'eslint-plugin-header', 'import'],
extends: [
'eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM',
'airbnb-base', 'plugin:import/errors', 'plugin:import/warnings',
'plugin:import/typescript',
],
rules: {
'header/header': [2, 'line', [{
pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2021 Intel Corporation',
template: ' Copyright (C) 2021 Intel Corporation'
}, '', ' SPDX-License-Identifier: MIT']],
'no-plusplus': 0,
'no-continue': 0,
'no-console': 0,
'no-param-reassign': ['error', { 'props': false }],
'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
'no-await-in-loop': 0,
'indent': ['error', 4, { 'SwitchCase': 1 }],
'max-len': ['error', { code: 120, ignoreStrings: true }],
'func-names': 0,
'valid-typeof': 0,
'no-useless-constructor': 0, // sometimes constructor is necessary to generate right documentation in cvat-core
'quotes': ['error', 'single'],
'lines-between-class-members': 0,
'class-methods-use-this': 0,
'no-underscore-dangle': ['error', { allowAfterThis: true }],
'max-classes-per-file': 0,
'operator-linebreak': ['error', 'after'],
'newline-per-chained-call': 0,
'global-require': 0,
'arrow-parens': ['error', 'always'],
'security/detect-object-injection': 0, // the rule is relevant for user input data on the node.js environment
'import/order': ['error', {'groups': ['builtin', 'external', 'internal']}],
'import/prefer-default-export': 0, // works incorrect with interfaces
},
};

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ develop, hotfix-*, master, release-* ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '25 19 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

@ -7,7 +7,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Run checks
run: |
@ -21,10 +21,8 @@ jobs:
done
if [[ ! -z ${changed_files_eslint} ]]; then
for package_files in `find -maxdepth 2 -name "package.json" -type f`; do
cd $(dirname $package_files) && npm ci && cd ${{ github.workspace }}
done
npm install eslint-detailed-reporter --save-dev
npm ci
npm install eslint-detailed-reporter --save-dev --legacy-peer-deps
mkdir -p eslint_report
echo "ESLint version: "`npx eslint --version`

@ -23,7 +23,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '16.x'
- name: Install npm packages
working-directory: ./site
@ -32,7 +32,7 @@ jobs:
- name: Build docs
run: |
pip install gitpython packaging
pip install gitpython packaging toml
python site/build_docs.py
- name: Deploy

@ -5,9 +5,14 @@ on:
- 'master'
- 'develop'
pull_request:
types: [edited, ready_for_review, opened, synchronize, reopened]
jobs:
Unit_testing:
if: |
github.event.pull_request.draft == false &&
!startsWith(github.event.pull_request.title, '[WIP]') &&
!startsWith(github.event.pull_request.title, '[Dependent]')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -75,6 +80,10 @@ jobs:
${{ github.workspace }}/lcov.info
E2E_testing:
if: |
github.event.pull_request.draft == false &&
!startsWith(github.event.pull_request.title, '[WIP]') &&
!startsWith(github.event.pull_request.title, '[Dependent]')
runs-on: ubuntu-latest
strategy:
fail-fast: false
@ -126,7 +135,7 @@ jobs:
key: ${{ runner.os }}-build-ui-${{ steps.get-sha.outputs.sha }}
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.1.2
- name: Building CVAT server image
@ -158,23 +167,23 @@ jobs:
DJANGO_SU_PASSWORD: '12qwaszx'
API_ABOUT_PAGE: "localhost:8080/api/v1/server/about"
run: |
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml up -d
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f tests/docker-compose.file_share.yml up -d
/bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done'
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
cd ./tests
npm ci
if [[ ${{ github.ref }} == 'refs/heads/develop' ]]; then
if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then
npx cypress run --browser chrome --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
npx cypress run --headed --browser chrome --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
else
npx cypress run --headless --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
npx cypress run --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
fi
mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json
else
if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then
npx cypress run --browser chrome --env coverage=false --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
npx cypress run --headed --browser chrome --env coverage=false --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
else
npx cypress run --headless --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
npx cypress run --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
fi
fi
- name: Creating a log file from "cvat" container logs

@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Run end-to-end tests
env:
DJANGO_SU_NAME: 'admin'
@ -31,7 +31,7 @@ jobs:
API_ABOUT_PAGE: "localhost:8080/api/v1/server/about"
run: |
docker-compose -f docker-compose.yml -f docker-compose.dev.yml build
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml up -d
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f tests/docker-compose.file_share.yml up -d
/bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done'
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
cd ./tests

@ -7,7 +7,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Run checks
run: |

@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Build CVAT
env:
DJANGO_SU_NAME: "admin"
@ -18,7 +18,7 @@ jobs:
DJANGO_SU_PASSWORD: "12qwaszx"
API_ABOUT_PAGE: "localhost:8080/api/v1/server/about"
run: |
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml -f components/serverless/docker-compose.serverless.yml up -d --build
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml -f tests/docker-compose.file_share.yml -f components/serverless/docker-compose.serverless.yml up -d --build
/bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done'
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
- name: End-to-end testing

@ -7,7 +7,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: '16.x'
- name: Run checks
run: |

24
.gitignore vendored

@ -10,7 +10,6 @@
/profiles
/ssh/*
!/ssh/README.md
node_modules
/Mask_RCNN/
/letsencrypt-webroot/
@ -21,9 +20,6 @@ __pycache__
._*
.coverage
# Ignore development npm files
node_modules
# Ignore npm logs file
npm-debug.log*
yarn-debug.log*
@ -46,4 +42,22 @@ yarn-error.log*
/site/public/
/site/resources/
/site/node_modules/
/site/tech-doc-hugo
/site/tech-doc-hugo
# Ignore all the installed packages
node_modules
# Ignore all js dists
cvat-data/dist
cvat-core/dist
cvat-canvas/dist
cvat-canvas3d/dist
cvat-ui/dist
# produced by npm run docs in cvat-core
cvat-core/docs
# produced by npm run test in cvat-core
cvat-core/reports
# produced by prepare in the root package.json script
.husky

@ -5,31 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## \[1.7.0] - Unreleased
## \[1.7.0] - 2021-11-15
### Added
- TDB
- cvat-ui: support cloud storages (<https://github.com/openvinotoolkit/cvat/pull/3372>)
- interactor: add HRNet interactive segmentation serverless function (<https://github.com/openvinotoolkit/cvat/pull/3740>)
- Added GPU implementation for SiamMask, reworked tracking approach (<https://github.com/openvinotoolkit/cvat/pull/3571>)
- Progress bar for manifest creating (<https://github.com/openvinotoolkit/cvat/pull/3712>)
- Add a tutorial on attaching cloud storage AWS-S3 (<https://github.com/openvinotoolkit/cvat/pull/3745>)
and Azure Blob Container (<https://github.com/openvinotoolkit/cvat/pull/3778>)
- The feature to remove annotations in a specified range of frames (<https://github.com/openvinotoolkit/cvat/pull/3617>)
### Changed
- TDB
### Deprecated
- TDB
### Removed
- TDB
- UI tracking has been reworked (<https://github.com/openvinotoolkit/cvat/pull/3571>)
- Manifest generation: Reduce creating time (<https://github.com/openvinotoolkit/cvat/pull/3712>)
- Migration from NPM 6 to NPM 7 (<https://github.com/openvinotoolkit/cvat/pull/3773>)
- Update Datumaro dependency to 0.2.0 (<https://github.com/openvinotoolkit/cvat/pull/3813>)
### Fixed
- TDB
- Fixed JSON transform issues in network requests (<https://github.com/openvinotoolkit/cvat/pull/3706>)
- Display a more user-friendly exception message (<https://github.com/openvinotoolkit/cvat/pull/3721>)
- Exception `DataCloneError: The object could not be cloned` (<https://github.com/openvinotoolkit/cvat/pull/3733>)
- Fixed extension comparison in task frames CLI (<https://github.com/openvinotoolkit/cvat/pull/3674>)
- Incorrect work when copy job list with "Copy" button (<https://github.com/openvinotoolkit/cvat/pull/3749>)
- Iterating over manifest (<https://github.com/openvinotoolkit/cvat/pull/3792>)
- Manifest removing (<https://github.com/openvinotoolkit/cvat/pull/3791>)
- Fixed project updated date (<https://github.com/openvinotoolkit/cvat/pull/3814>)
- Fixed dextr deployment (<https://github.com/openvinotoolkit/cvat/pull/3820>)
- Migration of `dataset_repo` application (<https://github.com/openvinotoolkit/cvat/pull/3827>)
- Helm settings for external psql database were unused by backend (<https://github.com/openvinotoolkit/cvat/pull/3779>)
- Updated WSL setup for development (<https://github.com/openvinotoolkit/cvat/pull/3828>)
- Helm chart config (<https://github.com/openvinotoolkit/cvat/pull/3784>)
### Security
- TDB
- Fix security issues on the documentation website unsafe use of target blank
and potential clickjacking on legacy browsers (<https://github.com/openvinotoolkit/cvat/pull/3789>)
## \[1.6.0] - 2021-09-17

@ -14,7 +14,7 @@ RUN apt-get update && \
&& \
curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \
curl https://deb.nodesource.com/setup_12.x | bash - && \
curl https://deb.nodesource.com/setup_16.x | bash - && \
DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \
google-chrome-stable \
nodejs \

@ -1,4 +1,4 @@
FROM node:lts-buster AS cvat-ui
FROM node:lts-slim AS cvat-ui
ARG http_proxy
ARG https_proxy
@ -16,31 +16,16 @@ ENV TERM=xterm \
LC_ALL='C.UTF-8'
# Install dependencies
COPY package*.json /tmp/
COPY cvat-core/package*.json /tmp/cvat-core/
COPY cvat-canvas/package*.json /tmp/cvat-canvas/
COPY cvat-canvas3d/package*.json /tmp/cvat-canvas3d/
COPY cvat-ui/package*.json /tmp/cvat-ui/
COPY cvat-data/package*.json /tmp/cvat-data/
# Install cvat-data dependencies
WORKDIR /tmp/cvat-data/
RUN npm ci
# Install cvat-core dependencies
WORKDIR /tmp/cvat-core/
RUN npm ci
# Install cvat-canvas dependencies
WORKDIR /tmp/cvat-canvas/
RUN npm ci
# Install cvat-canvas dependencies
WORKDIR /tmp/cvat-canvas3d/
RUN npm ci
# Install cvat-ui dependencies
WORKDIR /tmp/cvat-ui/
RUN npm ci
# Install common dependencies
WORKDIR /tmp/
RUN npm ci --ignore-scripts
# Build source code
COPY cvat-data/ /tmp/cvat-data/
@ -48,7 +33,7 @@ COPY cvat-core/ /tmp/cvat-core/
COPY cvat-canvas3d/ /tmp/cvat-canvas3d/
COPY cvat-canvas/ /tmp/cvat-canvas/
COPY cvat-ui/ /tmp/cvat-ui/
RUN npm run build
RUN npm run build:cvat-ui
FROM nginx:mainline-alpine
# Replace default.conf configuration to remove unnecessary rules

@ -86,8 +86,9 @@ For more information about supported formats look at the
| [Object reidentification](/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio) | reid | OpenVINO | X | |
| [Semantic segmentation for ADAS](/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio) | detector | OpenVINO | X | |
| [Text detection v4](/serverless/openvino/omz/intel/text-detection-0004/nuclio) | detector | OpenVINO | X | |
| [SiamMask](/serverless/pytorch/foolwood/siammask/nuclio) | tracker | PyTorch | X | |
| [SiamMask](/serverless/pytorch/foolwood/siammask/nuclio) | tracker | PyTorch | X | X |
| [f-BRS](/serverless/pytorch/saic-vul/fbrs/nuclio) | interactor | PyTorch | X | |
| [HRNet](/serverless/pytorch/saic-vul/hrnet/nuclio) | interactor | PyTorch | | X |
| [Inside-Outside Guidance](/serverless/pytorch/shiyinzhang/iog/nuclio) | interactor | PyTorch | X | |
| [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow | X | X |
| [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow | X | X |

@ -1,46 +1,45 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
const globalConfig = require('../.eslintrc.js');
module.exports = {
env: {
node: true,
},
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
'node_modules/**',
'dist/**',
],
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 6,
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'import'],
extends: [
'plugin:@typescript-eslint/recommended',
'airbnb-typescript/base',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
],
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript/base'],
rules: {
...globalConfig.rules,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['warn', 4],
'no-plusplus': 0,
'no-restricted-syntax': [
0,
'@typescript-eslint/indent': ['error', 4],
'@typescript-eslint/lines-between-class-members': 0,
'@typescript-eslint/no-explicit-any': [0],
'@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/ban-types': [
'error',
{
selector: 'ForOfStatement',
types: {
'{}': false, // TODO: try to fix with Record<string, unknown>
object: false, // TODO: try to fix with Record<string, unknown>
Function: false, // TODO: try to fix somehow
},
},
],
'max-len': ['error', { code: 120 }],
'no-continue': 0,
'func-names': 0,
'no-console': 0, // this rule deprecates console.log, console.warn etc. because 'it is not good in production code'
'lines-between-class-members': 0,
'import/prefer-default-export': 0, // works incorrect with interfaces
'newline-per-chained-call': 0, // makes code uglier
},
settings: {
'import/resolver': {
node: {
extensions: ['.ts', '.js', '.json'],
},
},
},
};

@ -1 +0,0 @@
dist

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.7.0",
"version": "2.8.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {
@ -15,39 +15,12 @@
"not IE 11",
"> 2%"
],
"devDependencies": {},
"dependencies": {
"svg.draggable.js": "2.2.2",
"svg.draw.js": "^2.0.4",
"svg.js": "2.7.1",
"svg.resize.js": "1.4.3",
"svg.select.js": "3.0.1"
},
"devDependencies": {
"@babel/cli": "^7.12.13",
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@babel/preset-env": "^7.5.5",
"@babel/preset-typescript": "^7.3.3",
"@types/node": "^12.6.8",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"dts-bundle-webpack": "^1.0.2",
"eslint": "^6.1.0",
"eslint-config-airbnb-typescript": "^4.0.1",
"eslint-config-typescript-recommended": "^1.4.17",
"eslint-plugin-import": "^2.22.1",
"node-sass": "^4.14.1",
"nodemon": "^2.0.7",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.0.0",
"typescript": "^3.5.3",
"webpack": "^5.20.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
}

@ -46,6 +46,12 @@ polyline.cvat_shape_drawing_opacity {
pointer-events: none;
}
.cvat_canvas_text_description {
font-size: 14px;
fill: yellow;
font-style: oblique 40deg;
}
.cvat_canvas_crosshair {
stroke: red;
}

@ -172,9 +172,9 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
} else {
// sign defines bypass direction
const landmarks = this.auxiliaryClicks;
const sign = Math.sign(landmarks[2] - landmarks[0])
* Math.sign(landmarks[1] - landmarks[0])
* Math.sign(landmarks[2] - landmarks[1]);
const sign = Math.sign(landmarks[2] - landmarks[0]) *
Math.sign(landmarks[1] - landmarks[0]) *
Math.sign(landmarks[2] - landmarks[1]);
// go via a polygon and get vertices
// the first vertex has been already drawn
@ -198,10 +198,10 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
// remove the latest cursor position from drawing array
for (const wayPoint of way) {
const [_x, _y] = wayPoint
const [pX, pY] = wayPoint
.split(',')
.map((coordinate: string): number => +coordinate);
this.addPointToCurrentShape(_x, _y);
this.addPointToCurrentShape(pX, pY);
}
this.resetAuxiliaryShape();

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -108,7 +108,7 @@ class CanvasImpl implements Canvas {
this.model.rotate(rotationAngle);
}
public focus(clientID: number, padding: number = 0): void {
public focus(clientID: number, padding = 0): void {
this.model.focus(clientID, padding);
}

@ -675,8 +675,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
}
public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode)
|| (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode) ||
(this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
return !isUnable;
}

@ -164,8 +164,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
private onInteraction(
shapes: InteractionResult[] | null,
shapesUpdated: boolean = true,
isDone: boolean = false,
shapesUpdated = true,
isDone = false,
threshold: number | null = null,
): void {
const { zLayer } = this.controller;
@ -514,8 +514,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Transform all text
for (const key in this.svgShapes) {
if (
Object.prototype.hasOwnProperty.call(this.svgShapes, key)
&& Object.prototype.hasOwnProperty.call(this.svgTexts, key)
Object.prototype.hasOwnProperty.call(this.svgShapes, key) &&
Object.prototype.hasOwnProperty.call(this.svgTexts, key)
) {
this.updateTextPosition(this.svgTexts[key], this.svgShapes[key]);
}
@ -740,9 +740,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
private selectize(value: boolean, shape: SVG.Element): void {
const self = this;
function mousedownHandler(e: MouseEvent): void {
const mousedownHandler = (e: MouseEvent): void => {
if (e.button !== 0) return;
e.preventDefault();
@ -751,26 +749,26 @@ export class CanvasViewImpl implements CanvasView, Listener {
e.target,
);
if (self.activeElement.clientID !== null) {
const [state] = self.controller.objects.filter(
(_state: any): boolean => _state.clientID === self.activeElement.clientID,
if (this.activeElement.clientID !== null) {
const [state] = this.controller.objects.filter(
(_state: any): boolean => _state.clientID === this.activeElement.clientID,
);
if (['polygon', 'polyline', 'points'].includes(state.shapeType)) {
if (e.altKey) {
const { points } = state;
self.onEditDone(state, points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2)));
this.onEditDone(state, points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2)));
} else if (e.shiftKey) {
self.canvas.dispatchEvent(
this.canvas.dispatchEvent(
new CustomEvent('canvas.editstart', {
bubbles: false,
cancelable: true,
}),
);
self.mode = Mode.EDIT;
self.deactivate();
self.editHandler.edit({
this.mode = Mode.EDIT;
this.deactivate();
this.editHandler.edit({
enabled: true,
state,
pointID,
@ -778,37 +776,37 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
}
}
};
function dblClickHandler(e: MouseEvent): void {
const dblClickHandler = (e: MouseEvent): void => {
e.preventDefault();
if (self.activeElement.clientID !== null) {
const [state] = self.controller.objects.filter(
(_state: any): boolean => _state.clientID === self.activeElement.clientID,
if (this.activeElement.clientID !== null) {
const [state] = this.controller.objects.filter(
(_state: any): boolean => _state.clientID === this.activeElement.clientID,
);
if (state.shapeType === 'cuboid') {
if (e.shiftKey) {
const points = self.translateFromCanvas(
const points = this.translateFromCanvas(
pointsToNumberArray((e.target as any).parentElement.parentElement.instance.attr('points')),
);
self.onEditDone(state, points);
this.onEditDone(state, points);
}
}
}
}
};
function contextMenuHandler(e: MouseEvent): void {
const contextMenuHandler = (e: MouseEvent): void => {
const pointID = Array.prototype.indexOf.call(
((e.target as HTMLElement).parentElement as HTMLElement).children,
e.target,
);
if (self.activeElement.clientID !== null) {
const [state] = self.controller.objects.filter(
(_state: any): boolean => _state.clientID === self.activeElement.clientID,
if (this.activeElement.clientID !== null) {
const [state] = this.controller.objects.filter(
(_state: any): boolean => _state.clientID === this.activeElement.clientID,
);
self.canvas.dispatchEvent(
this.canvas.dispatchEvent(
new CustomEvent('canvas.contextmenu', {
bubbles: false,
cancelable: true,
@ -821,12 +819,14 @@ export class CanvasViewImpl implements CanvasView, Listener {
);
}
e.preventDefault();
}
};
const getGeometry = (): Geometry => this.geometry;
if (value) {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / self.geometry.scale,
pointSize: (2 * consts.BASE_POINT_SIZE) / this.geometry.scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
@ -836,12 +836,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
.center(cx, cy)
.attr({
'fill-opacity': 1,
'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_STROKE_WIDTH / getGeometry().scale,
});
circle.on('mouseenter', (): void => {
circle.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / getGeometry().scale,
});
circle.on('dblclick', dblClickHandler);
@ -852,7 +852,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
circle.on('mouseleave', (): void => {
circle.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_STROKE_WIDTH / getGeometry().scale,
});
circle.off('dblclick', dblClickHandler);
@ -989,8 +989,6 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.canvas.appendChild(this.content);
this.canvas.appendChild(this.attachmentBoard);
const self = this;
// Setup API handlers
this.autoborderHandler = new AutoborderHandlerImpl(this.content);
this.drawHandler = new DrawHandlerImpl(
@ -1033,25 +1031,24 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Setup event handlers
this.content.addEventListener('dblclick', (e: MouseEvent): void => {
self.controller.fit();
this.controller.fit();
e.preventDefault();
});
this.content.addEventListener('mousedown', (event): void => {
if ([0, 1].includes(event.button)) {
if (
[Mode.IDLE, Mode.DRAG_CANVAS, Mode.MERGE, Mode.SPLIT].includes(this.mode)
|| event.button === 1
|| event.altKey
[Mode.IDLE, Mode.DRAG_CANVAS, Mode.MERGE, Mode.SPLIT]
.includes(this.mode) || event.button === 1 || event.altKey
) {
self.controller.enableDrag(event.clientX, event.clientY);
this.controller.enableDrag(event.clientX, event.clientY);
}
}
});
window.document.addEventListener('mouseup', (event): void => {
if (event.which === 1 || event.which === 2) {
self.controller.disableDrag();
this.controller.disableDrag();
}
});
@ -1059,7 +1056,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (event.ctrlKey) return;
const { offset } = this.controller.geometry;
const point = translateToSVG(this.content, [event.clientX, event.clientY]);
self.controller.zoom(point[0] - offset, point[1] - offset, event.deltaY > 0 ? -1 : 1);
this.controller.zoom(point[0] - offset, point[1] - offset, event.deltaY > 0 ? -1 : 1);
this.canvas.dispatchEvent(
new CustomEvent('canvas.zoom', {
bubbles: false,
@ -1070,7 +1067,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
});
this.content.addEventListener('mousemove', (e): void => {
self.controller.drag(e.clientX, e.clientY);
this.controller.drag(e.clientX, e.clientY);
if (this.mode !== Mode.IDLE) return;
if (e.ctrlKey || e.altKey) return;
@ -1458,6 +1455,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
shapeType: state.shapeType,
points: [...state.points],
attributes: { ...state.attributes },
descriptions: [...state.descriptions],
zOrder: state.zOrder,
pinned: state.pinned,
updated: state.updated,
@ -1516,8 +1514,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
if (
state.points.length !== drawnState.points.length
|| state.points.some((p: number, id: number): boolean => p !== drawnState.points[id])
state.points.length !== drawnState.points.length ||
state.points.some((p: number, id: number): boolean => p !== drawnState.points[id])
) {
const translatedPoints: number[] = this.translateToCanvas(state.points);
@ -1544,7 +1542,14 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
if (drawnState.label.id !== state.label.id) {
const stateDescriptions = state.descriptions;
const drawnStateDescriptions = drawnState.descriptions;
if (
drawnState.label.id !== state.label.id ||
drawnStateDescriptions.length !== stateDescriptions.length ||
drawnStateDescriptions.some((desc: string, id: number): boolean => desc !== stateDescriptions[id])
) {
// need to remove created text and create it again
if (text) {
text.remove();
@ -1792,10 +1797,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
const dy2 = (p1.y - p2.y) ** 2;
if (Math.sqrt(dx2 + dy2) >= delta) {
const points = pointsToNumberArray(
shape.attr('points')
|| `${shape.attr('x')},${shape.attr('y')} `
+ `${shape.attr('x') + shape.attr('width')},`
+ `${shape.attr('y') + shape.attr('height')}`,
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},` +
`${shape.attr('y') + shape.attr('height')}`,
).map((x: number): number => x - offset);
this.drawnStates[state.clientID].points = points;
@ -1866,10 +1870,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
const { offset } = this.controller.geometry;
const points = pointsToNumberArray(
shape.attr('points')
|| `${shape.attr('x')},${shape.attr('y')} `
+ `${shape.attr('x') + shape.attr('width')},`
+ `${shape.attr('y') + shape.attr('height')}`,
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},` +
`${shape.attr('y') + shape.attr('height')}`,
).map((x: number): number => x - offset);
this.drawnStates[state.clientID].points = points;
@ -1945,8 +1948,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Find the best place for a text
let [clientX, clientY]: number[] = [box.x + box.width, box.y];
if (
clientX + ((text.node as any) as SVGTextElement).getBBox().width + consts.TEXT_MARGIN
> this.canvas.offsetWidth
clientX + ((text.node as any) as SVGTextElement)
.getBBox().width + consts.TEXT_MARGIN > this.canvas.offsetWidth
) {
[clientX, clientY] = [box.x, box.y];
}
@ -1967,7 +1970,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration;
const {
label, clientID, attributes, source,
label, clientID, attributes, source, descriptions,
} = state;
const attrNames = label.attributes.reduce((acc: any, val: any): void => {
acc[val.id] = val.name;
@ -1977,13 +1980,25 @@ export class CanvasViewImpl implements CanvasView, Listener {
return this.adoptedText
.text((block): void => {
block.tspan(`${label.name} ${clientID} (${source})`).style('text-transform', 'uppercase');
for (const desc of descriptions) {
block
.tspan(`${desc}`)
.attr({
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_description');
}
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,
});
block
.tspan(`${attrNames[attrID]}: ${value}`)
.attr({
attrID,
dy: '1em',
x: 0,
})
.addClass('cvat_canvas_text_attribute');
}
})
.move(0, 0)

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -14,8 +14,8 @@ const MIN_EDGE_LENGTH = 3;
const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5;
const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75;
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 '
+ '0 0-.012.59L4.2 6.72.096 12.192a.479.479 0 0 0 .585.724l12.48-5.76a.48.48 0 0 0 0-.872z';
const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' +
'0 0-.012.59L4.2 6.72.096 12.192a.479.479 0 0 0 .585.724l12.48-5.76a.48.48 0 0 0 0-.872z';
const BASE_PATTERN_SIZE = 5;
export default {

@ -348,9 +348,8 @@ function setupCuboidPoints(points: Point[]): any[] {
let p3;
let p4;
const height = Math.abs(points[0].x - points[1].x) < Math.abs(points[1].x - points[2].x)
? Math.abs(points[1].y - points[0].y)
: Math.abs(points[1].y - points[2].y);
const height = Math.abs(points[0].x - points[1].x) < Math.abs(points[1].x - points[2].x) ?
Math.abs(points[1].y - points[0].y) : Math.abs(points[1].y - points[2].y);
// separate into left and right point
// we pick the first and third point because we know assume they will be on

@ -111,9 +111,9 @@ export class DrawHandlerImpl implements DrawHandler {
const findInersection = (p1: Point, p2: Point, p3: Point, p4: Point): number[] => {
const intersectionPoint = intersection(p1, p2, p3, p4);
if (
intersectionPoint
&& isBetween(p1.x, p2.x, intersectionPoint.x)
&& isBetween(p1.y, p2.y, intersectionPoint.y)
intersectionPoint &&
isBetween(p1.x, p2.x, intersectionPoint.x) &&
isBetween(p1.y, p2.y, intersectionPoint.y)
) {
return [intersectionPoint.x, intersectionPoint.y];
}
@ -149,8 +149,8 @@ export class DrawHandlerImpl implements DrawHandler {
if (resultPoints.length === 4) {
if (
(p1.x === p2.x || Math.sign(resultPoints[0] - resultPoints[2]) !== Math.sign(p1.x - p2.x))
&& (p1.y === p2.y || Math.sign(resultPoints[1] - resultPoints[3]) !== Math.sign(p1.y - p2.y))
(p1.x === p2.x || Math.sign(resultPoints[0] - resultPoints[2]) !== Math.sign(p1.x - p2.x)) &&
(p1.y === p2.y || Math.sign(resultPoints[1] - resultPoints[3]) !== Math.sign(p1.y - p2.y))
) {
[resultPoints[0], resultPoints[2]] = [resultPoints[2], resultPoints[0]];
[resultPoints[1], resultPoints[3]] = [resultPoints[3], resultPoints[1]];
@ -173,9 +173,9 @@ export class DrawHandlerImpl implements DrawHandler {
if (isLastPoint && (isPolyline || (isPolygon && shapePoints.length === 4))) {
break;
}
const nextPoint = isLastPoint
? { x: shapePoints[0], y: shapePoints[1] }
: { x: shapePoints[i + 2], y: shapePoints[i + 3] };
const nextPoint = isLastPoint ?
{ x: shapePoints[0], y: shapePoints[1] } :
{ x: shapePoints[i + 2], y: shapePoints[i + 3] };
const intersectionPoints = findIntersectionsWithFrameBorders(curPoint, nextPoint, direction);
if (intersectionPoints.length !== 0) {
resultPoints.push(...intersectionPoints);
@ -312,9 +312,9 @@ export class DrawHandlerImpl implements DrawHandler {
// We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) {
if (
['polygon', 'polyline', 'points'].includes(this.drawData.shapeType)
|| (this.drawData.shapeType === 'cuboid'
&& this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)
['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) ||
(this.drawData.shapeType === 'cuboid' &&
this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)
) {
// Check for unsaved drawn shapes
this.drawInstance.draw('done');
@ -489,22 +489,22 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points'));
const { shapeType, redraw: clientID } = this.drawData;
const { points, box } = shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
const { points, box } = shapeType === 'cuboid' ?
this.getFinalCuboidCoordinates(targetPoints) :
this.getFinalPolyshapeCoordinates(targetPoints);
this.release();
if (this.canceled) return;
if (
shapeType === 'polygon'
&& (box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD
&& points.length >= 3 * 2
shapeType === 'polygon' &&
(box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD &&
points.length >= 3 * 2
) {
this.onDrawDone({ clientID, shapeType, points }, Date.now() - this.startTimestamp);
} else if (
shapeType === 'polyline'
&& (box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD)
&& points.length >= 2 * 2
shapeType === 'polyline' &&
(box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) &&
points.length >= 2 * 2
) {
this.onDrawDone({ clientID, shapeType, points }, Date.now() - this.startTimestamp);
} else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') {
@ -611,9 +611,9 @@ export class DrawHandlerImpl implements DrawHandler {
.split(/[,\s]/g)
.map((coord: string): number => +coord);
const { points } = this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
const { points } = this.drawData.initialState.shapeType === 'cuboid' ?
this.getFinalCuboidCoordinates(targetPoints) :
this.getFinalPolyshapeCoordinates(targetPoints);
if (!e.detail.originalEvent.ctrlKey) {
this.release();
@ -886,12 +886,12 @@ export class DrawHandlerImpl implements DrawHandler {
public configurate(configuration: Configuration): void {
this.configuration = configuration;
const isFillableRect = this.drawData
&& this.drawData.shapeType === 'rectangle'
&& (this.drawData.rectDrawingMethod === RectDrawingMethod.CLASSIC || this.drawData.initialState);
const isFillableCuboid = this.drawData
&& this.drawData.shapeType === 'cuboid'
&& (this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CLASSIC || this.drawData.initialState);
const isFillableRect = this.drawData &&
this.drawData.shapeType === 'rectangle' &&
(this.drawData.rectDrawingMethod === RectDrawingMethod.CLASSIC || this.drawData.initialState);
const isFillableCuboid = this.drawData &&
this.drawData.shapeType === 'cuboid' &&
(this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CLASSIC || this.drawData.initialState);
const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon';
if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) {

@ -292,27 +292,28 @@ export class EditHandlerImpl implements EditHandler {
}
private setupPoints(enabled: boolean): void {
const self = this;
const stopEdit = self.stopEdit.bind(self);
const stopEdit = this.stopEdit.bind(this);
const getGeometry = (): Geometry => this.geometry;
const fill = this.editedShape.attr('fill') || 'inherit';
if (enabled) {
(this.editedShape as any).selectize(true, {
deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / self.geometry.scale,
pointSize: (2 * consts.BASE_POINT_SIZE) / getGeometry().scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
.circle(this.options.pointSize)
.stroke('black')
.fill(self.editedShape.attr('fill') || 'inherit')
.fill(fill)
.center(cx, cy)
.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_STROKE_WIDTH / getGeometry().scale,
});
circle.node.addEventListener('mouseenter', (): void => {
circle.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / getGeometry().scale,
});
circle.node.addEventListener('click', stopEdit);
@ -321,7 +322,7 @@ export class EditHandlerImpl implements EditHandler {
circle.node.addEventListener('mouseleave', (): void => {
circle.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale,
'stroke-width': consts.POINTS_STROKE_WIDTH / getGeometry().scale,
});
circle.node.removeEventListener('click', stopEdit);

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -96,11 +96,11 @@ export class GroupHandlerImpl implements GroupHandler {
const bbox = shape.bbox();
const clientID = shape.attr('clientID');
if (
bbox.x > box.xtl
&& bbox.y > box.ytl
&& bbox.x + bbox.width < box.xbr
&& bbox.y + bbox.height < box.ybr
&& !(clientID in this.highlightedShapes)
bbox.x > box.xtl &&
bbox.y > box.ytl &&
bbox.x + bbox.width < box.xbr &&
bbox.y + bbox.height < box.ybr &&
!(clientID in this.highlightedShapes)
) {
const objectState = this.getStates().filter(
(state: any): boolean => state.clientID === clientID,

@ -22,19 +22,33 @@ export interface InteractionHandler {
export class InteractionHandlerImpl implements InteractionHandler {
private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void;
private configuration: Configuration;
private geometry: Geometry;
private canvas: SVG.Container;
private interactionData: InteractionData;
private cursorPosition: { x: number; y: number };
private shapesWereUpdated: boolean;
private interactionShapes: SVG.Shape[];
private currentInteractionShape: SVG.Shape | null;
private crosshair: Crosshair;
private threshold: SVG.Rect | null;
private thresholdRectSize: number;
private intermediateShape: PropType<InteractionData, 'intermediateShape'>;
private drawnIntermediateShape: SVG.Shape;
private thresholdWasModified: boolean;
private prepareResult(): InteractionResult[] {
@ -310,6 +324,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
}
private selectize(value: boolean, shape: SVG.Element, erroredShape = false): void {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
if (value) {
@ -348,12 +363,12 @@ export class InteractionHandlerImpl implements InteractionHandler {
private visualComponentsChanged(interactionData: InteractionData): boolean {
const allowedKeys = ['enabled', 'crosshair', 'enableThreshold', 'onChangeToolsBlockerState'];
if (Object.keys(interactionData).every((key: string): boolean => allowedKeys.includes(key))) {
if (this.interactionData.enableThreshold !== undefined && interactionData.enableThreshold !== undefined
&& this.interactionData.enableThreshold !== interactionData.enableThreshold) {
if (this.interactionData.enableThreshold !== undefined && interactionData.enableThreshold !== undefined &&
this.interactionData.enableThreshold !== interactionData.enableThreshold) {
return true;
}
if (this.interactionData.crosshair !== undefined && interactionData.crosshair !== undefined
&& this.interactionData.crosshair !== interactionData.crosshair) {
if (this.interactionData.crosshair !== undefined && interactionData.crosshair !== undefined &&
this.interactionData.crosshair !== interactionData.crosshair) {
return true;
}
}
@ -450,7 +465,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
});
window.addEventListener('keydown', (e: KeyboardEvent): void => {
if (this.interactionData.enabled && e.keyCode === 17) {
if (!e.repeat && this.interactionData.enabled && e.keyCode === 17) {
if (this.interactionData.onChangeToolsBlockerState && !this.thresholdWasModified) {
this.interactionData.onChangeToolsBlockerState('keydown');
}
@ -466,9 +481,9 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.crosshair.scale(this.geometry.scale);
}
const shapesToBeScaled = this.currentInteractionShape
? [...this.interactionShapes, this.currentInteractionShape]
: [...this.interactionShapes];
const shapesToBeScaled = this.currentInteractionShape ?
[...this.interactionShapes, this.currentInteractionShape] :
[...this.interactionShapes];
for (const shape of shapesToBeScaled) {
if (shape.type === 'circle') {
if (shape.hasClass('cvat_canvas_removable_interaction_point')) {

@ -45,6 +45,7 @@ export interface DrawnState {
shapeType: string;
points?: number[];
attributes: Record<number, string>;
descriptions: string[];
zOrder?: number;
pinned?: boolean;
updated: number;

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -47,11 +47,11 @@ export class ZoomHandlerImpl implements ZoomHandler {
private getSelectionBox(
event: MouseEvent,
): {
x: number;
y: number;
width: number;
height: number;
} {
x: number;
y: number;
width: number;
height: number;
} {
const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
const stopSelectionPoint = {
x: point[0],

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT
const globalConfig = require('../.eslintrc.js');
module.exports = {
env: {
node: true,
@ -9,38 +11,35 @@ module.exports = {
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 6,
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'import'],
extends: [
'plugin:@typescript-eslint/recommended',
'airbnb-typescript/base',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
'node_modules/**',
'dist/**',
],
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript/base'],
rules: {
...globalConfig.rules,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['warn', 4],
'no-plusplus': 0,
'no-restricted-syntax': [
0,
'@typescript-eslint/indent': ['error', 4],
'@typescript-eslint/lines-between-class-members': 0,
'@typescript-eslint/no-explicit-any': [0],
'@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/ban-types': [
'error',
{
selector: 'ForOfStatement',
types: {
'{}': false, // TODO: try to fix with Record<string, unknown>
object: false, // TODO: try to fix with Record<string, unknown>
Function: false, // TODO: try to fix somehow
},
},
],
'max-len': ['error', { code: 120 }],
'no-continue': 0,
'func-names': 0,
'no-console': 0, // this rule deprecates console.log, console.warn etc. because 'it is not good in production code'
'lines-between-class-members': 0,
'import/prefer-default-export': 0, // works incorrect with interfaces
'newline-per-chained-call': 0, // makes code uglier
},
settings: {
'import/resolver': {
node: {
extensions: ['.ts', '.js', '.json'],
},
},
},
};

@ -1 +0,0 @@
dist

File diff suppressed because it is too large Load Diff

@ -15,37 +15,10 @@
"not IE 11",
"> 2%"
],
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@babel/preset-env": "^7.5.5",
"@babel/preset-typescript": "^7.3.3",
"@types/node": "^12.6.8",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"dts-bundle-webpack": "^1.0.2",
"eslint": "^6.1.0",
"eslint-config-airbnb-typescript": "^4.0.1",
"eslint-config-typescript-recommended": "^1.4.17",
"eslint-plugin-import": "^2.18.2",
"node-sass": "^4.14.1",
"nodemon": "^1.19.4",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.0.0",
"typescript": "^3.5.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"devDependencies": {},
"dependencies": {
"@types/three": "^0.125.3",
"camera-controls": "^1.25.3",
"three": "^0.125.0"
"three": "^0.126.1"
}
}

@ -1 +0,0 @@
webpack.config.js

@ -1,4 +1,4 @@
// Copyright (C) 2018-2020 Intel Corporation
// Copyright (C) 2018-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -9,39 +9,25 @@ module.exports = {
es6: true,
'jest/globals': true,
},
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
'jest.config.js',
'jsdoc.config.js',
'src/3rdparty/**',
'node_modules/**',
'dist/**',
],
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module',
ecmaVersion: 2018,
},
plugins: ['security', 'jest', 'no-unsafe-innerhtml', 'no-unsanitized'],
extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'],
plugins: ['jest'],
rules: {
'no-await-in-loop': [0],
'global-require': [0],
'no-new': [0],
'class-methods-use-this': [0],
'no-restricted-properties': [
0,
{
object: 'Math',
property: 'pow',
},
],
'no-plusplus': [0],
'no-param-reassign': [0],
'no-underscore-dangle': ['error', { allowAfterThis: true }],
'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
'no-continue': [0],
'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
// This rule actual for user input data on the node.js environment mainly.
'security/detect-object-injection': 0,
indent: ['warn', 4],
'no-useless-constructor': 0,
'func-names': [0],
'valid-typeof': [0],
'no-console': [0],
'max-classes-per-file': [0],
'max-len': ['warn', { code: 120 }],
},
'jest/no-disabled-tests': 'warn',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'warn',
}
};

@ -1,5 +0,0 @@
docs
node_modules
reports
yarn.lock
dist

@ -1,4 +1,4 @@
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2020 Intel Corporation
//
// SPDX-License-Identifier: MIT

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.16.0",
"version": "3.17.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {
@ -18,31 +18,16 @@
"> 2%"
],
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"airbnb": "0.0.2",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"coveralls": "^3.0.5",
"eslint": "6.1.0",
"eslint-config-airbnb-base": "14.0.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-no-unsafe-innerhtml": "^1.0.16",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-security": "^1.4.0",
"jest": "^26.6.3",
"jest-junit": "^6.4.0",
"jsdoc": "^3.6.6",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
"jsdoc": "^3.6.6"
},
"dependencies": {
"axios": "^0.21.3",
"axios": "^0.21.4",
"browser-or-node": "^1.2.1",
"cvat-data": "../cvat-data",
"detect-browser": "^5.2.0",
"detect-browser": "^5.2.1",
"error-stack-parser": "^2.0.2",
"form-data": "^2.5.0",
"jest-config": "^26.6.3",
@ -50,7 +35,6 @@
"js-cookie": "^2.2.0",
"platform": "^1.3.5",
"quickhull": "^1.0.3",
"store": "^2.0.12",
"worker-loader": "^2.0.0"
"store": "^2.0.12"
}
}

@ -36,23 +36,23 @@
let shapeModel = null;
switch (type) {
case 'rectangle':
shapeModel = new RectangleShape(shapeData, clientID, color, injection);
break;
case 'polygon':
shapeModel = new PolygonShape(shapeData, clientID, color, injection);
break;
case 'polyline':
shapeModel = new PolylineShape(shapeData, clientID, color, injection);
break;
case 'points':
shapeModel = new PointsShape(shapeData, clientID, color, injection);
break;
case 'cuboid':
shapeModel = new CuboidShape(shapeData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of shape "${type}"`);
case 'rectangle':
shapeModel = new RectangleShape(shapeData, clientID, color, injection);
break;
case 'polygon':
shapeModel = new PolygonShape(shapeData, clientID, color, injection);
break;
case 'polyline':
shapeModel = new PolylineShape(shapeData, clientID, color, injection);
break;
case 'points':
shapeModel = new PointsShape(shapeData, clientID, color, injection);
break;
case 'cuboid':
shapeModel = new CuboidShape(shapeData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of shape "${type}"`);
}
return shapeModel;
@ -65,23 +65,23 @@
let trackModel = null;
switch (type) {
case 'rectangle':
trackModel = new RectangleTrack(trackData, clientID, color, injection);
break;
case 'polygon':
trackModel = new PolygonTrack(trackData, clientID, color, injection);
break;
case 'polyline':
trackModel = new PolylineTrack(trackData, clientID, color, injection);
break;
case 'points':
trackModel = new PointsTrack(trackData, clientID, color, injection);
break;
case 'cuboid':
trackModel = new CuboidTrack(trackData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of track "${type}"`);
case 'rectangle':
trackModel = new RectangleTrack(trackData, clientID, color, injection);
break;
case 'polygon':
trackModel = new PolygonTrack(trackData, clientID, color, injection);
break;
case 'polyline':
trackModel = new PolylineTrack(trackData, clientID, color, injection);
break;
case 'points':
trackModel = new PointsTrack(trackData, clientID, color, injection);
break;
case 'cuboid':
trackModel = new CuboidTrack(trackData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of track "${type}"`);
}
return trackModel;
@ -336,22 +336,20 @@
occluded: shape.occluded,
outside: shape.outside,
zOrder: shape.zOrder,
attributes: updatedAttributes
? Object.keys(attributes).reduce((accumulator, attrID) => {
accumulator.push({
spec_id: +attrID,
value: attributes[attrID],
});
return accumulator;
}, [])
: [],
attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => {
accumulator.push({
spec_id: +attrID,
value: attributes[attrID],
});
return accumulator;
}, []) : [],
};
}
} else {
throw new ArgumentError(
`Trying to merge unknown object type: ${object.constructor.name}. `
+ 'Only shapes and tracks are expected.',
`Trying to merge unknown object type: ${object.constructor.name}. ` +
'Only shapes and tracks are expected.',
);
}
}
@ -553,14 +551,40 @@
return groupIdx;
}
clear() {
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.count = 0;
this.flush = true;
clear(startframe, endframe, delTrackKeyframesOnly) {
if (startframe !== undefined && endframe !== undefined) {
// If only a range of annotations need to be cleared
for (let frame = startframe; frame <= endframe; frame++) {
this.shapes[frame] = [];
this.tags[frame] = [];
}
const { tracks } = this;
tracks.forEach((track) => {
if (track.frame <= endframe) {
if (delTrackKeyframesOnly) {
for (const keyframe in track.shapes) {
if (keyframe >= startframe && keyframe <= endframe) { delete track.shapes[keyframe]; }
}
} else if (track.frame >= startframe) {
const index = tracks.indexOf(track);
if (index > -1) { tracks.splice(index, 1); }
}
}
});
} else if (startframe === undefined && endframe === undefined) {
// If all annotations need to be cleared
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.count = 0;
this.flush = true;
} else {
// If inputs provided were wrong
throw Error('Could not remove the annotations, please provide both inputs or' +
' leave the inputs below empty to remove all the annotations from this job');
}
}
statistics() {
@ -722,6 +746,8 @@
checkObjectType('state occluded', state.occluded, 'boolean', null);
checkObjectType('state points', state.points, null, Array);
checkObjectType('state zOrder', state.zOrder, 'integer', null);
checkObjectType('state descriptions', state.descriptions, null, Array);
state.descriptions.forEach((desc) => checkObjectType('state description', desc, 'string'));
for (const coord of state.points) {
checkObjectType('point coordinate', coord, 'number', null);
@ -736,6 +762,7 @@
if (state.objectType === 'shape') {
constructed.shapes.push({
attributes,
descriptions: state.descriptions,
frame: state.frame,
group: 0,
label_id: state.label.id,
@ -748,6 +775,7 @@
} else if (state.objectType === 'track') {
constructed.tracks.push({
attributes: attributes.filter((attr) => !labelAttributes[attr.spec_id].mutable),
descriptions: state.descriptions,
frame: state.frame,
group: 0,
source: state.source,

@ -253,8 +253,8 @@
for (const attribute of redoLabel.attributes) {
for (const oldAttribute of undoLabel.attributes) {
if (
attribute.name === oldAttribute.name
&& validateAttributeValue(undoAttributes[oldAttribute.id], attribute)
attribute.name === oldAttribute.name &&
validateAttributeValue(undoAttributes[oldAttribute.id], attribute)
) {
this.attributes[attribute.id] = undoAttributes[oldAttribute.id];
}
@ -332,6 +332,14 @@
}
}
if (updated.descriptions) {
if (!Array.isArray(data.descriptions) || data.descriptions.some((desc) => typeof desc !== 'string')) {
throw new ArgumentError(
`Descriptions are expected to be an array of strings but got ${data.descriptions}`,
);
}
}
if (updated.points) {
checkObjectType('points', data.points, null, Array);
checkNumberOfPoints(this.shapeType, data.points);
@ -402,17 +410,7 @@
}
updateTimestamp(updated) {
const anyChanges = updated.label
|| updated.attributes
|| updated.points
|| updated.outside
|| updated.occluded
|| updated.keyframe
|| updated.zOrder
|| updated.hidden
|| updated.lock
|| updated.pinned;
const anyChanges = Object.keys(updated).some((key) => !!updated[key]);
if (anyChanges) {
this.updated = Date.now();
}
@ -446,11 +444,16 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.frameMeta = injection.frameMeta;
this.descriptions = data.descriptions || [];
this.hidden = false;
this.pinned = true;
this.shapeType = null;
}
_saveDescriptions(descriptions) {
this.descriptions = [...descriptions];
}
_savePinned(pinned, frame) {
const undoPinned = this.pinned;
const redoPinned = pinned;
@ -533,6 +536,7 @@
zOrder: this.zOrder,
points: [...this.points],
attributes: { ...this.attributes },
descriptions: [...this.descriptions],
label: this.label,
group: this.groupObject,
color: this.color,
@ -643,6 +647,10 @@
this._saveAttributes(data.attributes, frame);
}
if (updated.descriptions) {
this._saveDescriptions(data.descriptions);
}
if (updated.points && fittedPoints.length) {
this._savePoints(fittedPoints, frame);
}
@ -760,6 +768,7 @@
return {
...this.getPosition(frame, prev, next),
attributes: this.getAttributes(frame),
descriptions: [...this.descriptions],
group: this.groupObject,
objectType: ObjectType.TRACK,
shapeType: this.shapeType,
@ -910,13 +919,13 @@
if (!labelAttributes[attrID].mutable) {
redoAttributes[attrID] = attributes[attrID];
} else if (attributes[attrID] !== current.attributes[attrID]) {
mutableAttributesUpdated = mutableAttributesUpdated
mutableAttributesUpdated = mutableAttributesUpdated ||
// not keyframe yet
|| !(frame in this.shapes)
!(frame in this.shapes) ||
// keyframe, but without this attrID
|| !(attrID in this.shapes[frame].attributes)
!(attrID in this.shapes[frame].attributes) ||
// keyframe with attrID, but with another value
|| this.shapes[frame].attributes[attrID] !== attributes[attrID];
this.shapes[frame].attributes[attrID] !== attributes[attrID];
}
}
let redoShape;
@ -1006,9 +1015,9 @@
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe
? { ...this.shapes[frame], points }
: {
const redoShape = wasKeyframe ?
{ ...this.shapes[frame], points } :
{
frame,
points,
zOrder: current.zOrder,
@ -1035,9 +1044,9 @@
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe
? { ...this.shapes[frame], outside }
: {
const redoShape = wasKeyframe ?
{ ...this.shapes[frame], outside } :
{
frame,
outside,
zOrder: current.zOrder,
@ -1064,9 +1073,9 @@
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe
? { ...this.shapes[frame], occluded }
: {
const redoShape = wasKeyframe ?
{ ...this.shapes[frame], occluded } :
{
frame,
occluded,
zOrder: current.zOrder,
@ -1093,9 +1102,9 @@
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe
? { ...this.shapes[frame], zOrder }
: {
const redoShape = wasKeyframe ?
{ ...this.shapes[frame], zOrder } :
{
frame,
zOrder,
occluded: current.occluded,
@ -1127,8 +1136,8 @@
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = keyframe
? {
const redoShape = keyframe ?
{
frame,
zOrder: current.zOrder,
points: current.points,
@ -1136,8 +1145,8 @@
occluded: current.occluded,
attributes: {},
source: current.source,
}
: undefined;
} :
undefined;
this.source = Source.MANUAL;
if (redoShape) {
@ -1204,6 +1213,10 @@
this._saveAttributes(data.attributes, frame);
}
if (updated.descriptions) {
this._saveDescriptions(data.descriptions);
}
if (updated.keyframe) {
this._saveKeyframe(frame, data.keyframe);
}
@ -1251,17 +1264,13 @@
}
throw new DataError(
'No one left position or right position was found. '
+ `Interpolation impossible. Client ID: ${this.clientID}`,
'No one left position or right position was found. ' +
`Interpolation impossible. Client ID: ${this.clientID}`,
);
}
}
class Tag extends Annotation {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
}
// Method is used to export data to the server
toJSON() {
return {
@ -1360,11 +1369,7 @@
}
}
class PolyShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
}
}
class PolyShape extends Shape {}
class PolygonShape extends PolyShape {
constructor(data, clientID, color, injection) {
@ -1418,12 +1423,12 @@
if ((xCross - x1) * (x2 - xCross) >= 0 && (yCross - y1) * (y2 - yCross) >= 0) {
// Cross point is on segment between p1(x1,y1) and p2(x2,y2)
distances.push(Math.sqrt(Math.pow(x - xCross, 2) + Math.pow(y - yCross, 2)));
distances.push(Math.sqrt((x - xCross) ** 2 + (y - yCross) ** 2));
} else {
distances.push(
Math.min(
Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)),
Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)),
Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2),
Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2),
),
);
}
@ -1460,8 +1465,8 @@
// Find the length of a perpendicular
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
distances.push(
Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1)
/ Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)),
Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) /
Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2),
);
} else {
// The link below works for lines (which have infinite length)
@ -1470,8 +1475,8 @@
// Instead we use just distance to the nearest point
distances.push(
Math.min(
Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)),
Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)),
Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2),
Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2),
),
);
}
@ -1494,7 +1499,7 @@
const x1 = points[i];
const y1 = points[i + 1];
distances.push(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)));
distances.push(Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2));
}
return Math.min.apply(null, distances);
@ -1545,10 +1550,10 @@
lowerHull.pop();
if (
upperHull.length === 1
&& lowerHull.length === 1
&& upperHull[0].x === lowerHull[0].x
&& upperHull[0].y === lowerHull[0].y
upperHull.length === 1 &&
lowerHull.length === 1 &&
upperHull[0].x === lowerHull[0].x &&
upperHull[0].y === lowerHull[0].y
) return upperHull;
return upperHull.concat(lowerHull);
}
@ -1566,11 +1571,11 @@
return makeHullPresorted(newPoints);
}
static contain(points, x, y) {
static contain(shapePoints, x, y) {
function isLeft(P0, P1, P2) {
return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y);
}
points = CuboidShape.makeHull(points);
const points = CuboidShape.makeHull(shapePoints);
let wn = 0;
for (let i = 0; i < points.length; i += 1) {
const p1 = points[`${i}`];
@ -1607,13 +1612,13 @@
const p2 = points[i + 1] || points[0];
// perpendicular from point to straight length
const distance = Math.abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x)
/ Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2));
const distance = Math.abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x) /
Math.sqrt((p2.y - p1.y) ** 2 + (p2.x - p1.x) ** 2);
// check if perpendicular belongs to the straight segment
const a = Math.pow(p1.x - x, 2) + Math.pow(p1.y - y, 2);
const b = Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2);
const c = Math.pow(p2.x - x, 2) + Math.pow(p2.y - y, 2);
const a = (p1.x - x) ** 2 + (p1.y - y) ** 2;
const b = (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2;
const c = (p2.x - x) ** 2 + (p2.y - y) ** 2;
if (distance < minDistance && a + b - c >= 0 && c + b - a >= 0) {
minDistance = distance;
}
@ -1645,10 +1650,6 @@
}
class PolyTrack extends Track {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
}
interpolatePosition(leftPosition, rightPosition, offset) {
if (offset === 0) {
return {

@ -187,12 +187,10 @@
return indexes;
}
async save(onUpdate) {
if (typeof onUpdate !== 'function') {
onUpdate = (message) => {
console.log(message);
};
}
async save(onUpdateArg) {
const onUpdate = typeof onUpdateArg === 'function' ? onUpdateArg : (message) => {
console.log(message);
};
const exported = this.collection.export();
const { flush } = this.collection;

@ -172,13 +172,13 @@
return false;
}
async function clearAnnotations(session, reload) {
async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly) {
checkObjectType('reload', reload, 'boolean', null);
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
cache.get(session).collection.clear();
cache.get(session).collection.clear(startframe, endframe, delTrackKeyframesOnly);
}
if (reload) {

@ -267,7 +267,8 @@
return projects;
};
cvat.projects.searchNames.implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit);
cvat.projects.searchNames
.implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit);
cvat.cloudStorages.get.implementation = async (filter) => {
checkFilter(filter, {

@ -24,7 +24,6 @@ function build() {
const { FrameData } = require('./frames');
const { CloudStorage } = require('./cloud-storage');
const enums = require('./enums');
const {
@ -822,7 +821,7 @@ function build() {
const implementAPI = require('./api-implementation');
Math.clamp = function (value, min, max) {
Math.clamp = function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
};

@ -75,8 +75,8 @@
if (!(value instanceof instance)) {
if (value !== undefined) {
throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but `
+ `"${value.constructor.name}" has been got`,
`"${name}" is expected to be ${instance.name}, but ` +
`"${value.constructor.name}" has been got`,
);
}

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -20,7 +20,6 @@ onmessage = (e) => {
.catch((error) => {
postMessage({
id: e.data.id,
error: error,
status: error.response.status,
responseData: error.response.data,
isSuccess: false,

@ -158,8 +158,8 @@
const onDecodeAll = async (frameNumber) => {
if (
frameDataCache[this.tid].activeChunkRequest
&& chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber
frameDataCache[this.tid].activeChunkRequest &&
chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber
) {
const callbackArray = frameDataCache[this.tid].activeChunkRequest.callbacks;
for (let i = callbackArray.length - 1; i >= 0; --i) {
@ -177,8 +177,8 @@
const rejectRequestAll = () => {
if (
frameDataCache[this.tid].activeChunkRequest
&& chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber
frameDataCache[this.tid].activeChunkRequest &&
chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber
) {
for (const r of frameDataCache[this.tid].activeChunkRequest.callbacks) {
r.reject(r.frameNumber);
@ -236,10 +236,10 @@
const activeRequest = frameDataCache[this.tid].activeChunkRequest;
if (!provider.isChunkCached(start, stop)) {
if (
!activeRequest
|| (activeRequest
&& activeRequest.completed
&& activeRequest.chunkNumber !== chunkNumber)
!activeRequest ||
(activeRequest &&
activeRequest.completed &&
activeRequest.chunkNumber !== chunkNumber)
) {
if (activeRequest && activeRequest.rejectRequestAll) {
activeRequest.rejectRequestAll();
@ -305,10 +305,10 @@
}
} else {
if (
this.number % chunkSize > chunkSize / 4
&& provider.decodedBlocksCacheSize > 1
&& this.decodeForward
&& !provider.isNextChunkExists(this.number)
this.number % chunkSize > chunkSize / 4 &&
provider.decodedBlocksCacheSize > 1 &&
this.decodeForward &&
!provider.isNextChunkExists(this.number)
) {
const nextChunkNumber = Math.floor(this.number / chunkSize) + 1;
if (nextChunkNumber * chunkSize < this.stopFrame) {
@ -421,8 +421,8 @@
.data()
.then(() => {
if (
!(chunkIdx in this._requestedChunks)
|| !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)
!(chunkIdx in this._requestedChunks) ||
!this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)
) {
reject(chunkIdx);
} else {
@ -531,10 +531,10 @@
delete this._buffer[frameNumber];
const cachedFrames = this.cachedFrames();
if (
fillBuffer
&& !this._activeFillBufferRequest
&& this._size > this._chunkSize
&& cachedFrames.length < (this._size * 3) / 4
fillBuffer &&
!this._activeFillBufferRequest &&
this._size > this._chunkSize &&
cachedFrames.length < (this._size * 3) / 4
) {
const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber;
if (maxFrame < this._stopFrame) {
@ -560,8 +560,8 @@
clear() {
for (const chunkIdx in this._requestedChunks) {
if (
Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx)
&& this._requestedChunks[chunkIdx].reject
Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) &&
this._requestedChunks[chunkIdx].reject
) {
this._requestedChunks[chunkIdx].reject('not needed');
}
@ -648,8 +648,8 @@
const meta = await serverProxy.frames.getMeta(taskID);
const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length;
const stdDev = Math.sqrt(
meta.frames.map((x) => Math.pow(x.width * x.height - mean, 2)).reduce((a, b) => a + b)
/ meta.frames.length,
meta.frames.map((x) => (x.width * x.height - mean) ** 2).reduce((a, b) => a + b) /
meta.frames.length,
);
// limit of decoded frames cache by 2GB

@ -147,8 +147,8 @@
data.attributes = [];
if (
Object.prototype.hasOwnProperty.call(initialData, 'attributes')
&& Array.isArray(initialData.attributes)
Object.prototype.hasOwnProperty.call(initialData, 'attributes') &&
Array.isArray(initialData.attributes)
) {
for (const attrData of initialData.attributes) {
data.attributes.push(new Attribute(attrData));

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -143,9 +143,10 @@ class LogWithWorkingTime extends Log {
Log.prototype.validatePayload.call(this);
if (
!('working_time' in this.payload)
|| !typeof this.payload.working_time === 'number'
|| this.payload.working_time < 0
!(
'working_time' in this.payload) ||
!typeof this.payload.working_time === 'number' ||
this.payload.working_time < 0
) {
const message = `
The field "working_time" is required for ${this.type} log. It must be a number not less than 0

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -38,8 +38,8 @@ class LoggerStorage {
lastLog: null,
ignore(previousLog, currentPayload) {
return (
currentPayload.object_id === previousLog.payload.object_id
&& currentPayload.id === previousLog.payload.id
currentPayload.object_id === previousLog.payload.object_id &&
currentPayload.id === previousLog.payload.id
);
},
};

@ -101,7 +101,12 @@ class MLModel {
}
/**
* @param {(event:string)=>void} onChangeToolsBlockerState Set canvas onChangeToolsBlockerState callback
* @callback onRequestStatusChange
* @param {string} event
* @global
*/
/**
* @param {onRequestStatusChange} onRequestStatusChange Set canvas onChangeToolsBlockerState callback
* @returns {void}
*/
set onChangeToolsBlockerState(onChangeToolsBlockerState) {

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -25,6 +25,7 @@ const { Source } = require('./enums');
const data = {
label: null,
attributes: {},
descriptions: [],
points: null,
outside: null,
@ -55,6 +56,7 @@ const { Source } = require('./enums');
value: function reset() {
this.label = false;
this.attributes = false;
this.descriptions = false;
this.points = false;
this.outside = false;
@ -70,6 +72,7 @@ const { Source } = require('./enums');
return reset;
},
writable: false,
enumerable: false,
});
Object.defineProperties(
@ -193,8 +196,8 @@ const { Source } = require('./enums');
data.points = [...points];
} else {
throw new ArgumentError(
'Points are expected to be an array '
+ `but got ${
'Points are expected to be an array ' +
`but got ${
typeof points === 'object' ? points.constructor.name : typeof points
}`,
);
@ -338,11 +341,11 @@ const { Source } = require('./enums');
set: (attributes) => {
if (typeof attributes !== 'object') {
throw new ArgumentError(
'Attributes are expected to be an object '
+ `but got ${
typeof attributes === 'object'
? attributes.constructor.name
: typeof attributes
'Attributes are expected to be an object ' +
`but got ${
typeof attributes === 'object' ?
attributes.constructor.name :
typeof attributes
}`,
);
}
@ -353,6 +356,30 @@ const { Source } = require('./enums');
}
},
},
descriptions: {
/**
* Additional text information displayed on canvas
* @name descripttions
* @type {string[]}
* @memberof module:API.cvat.classes.ObjectState
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
*/
get: () => [...data.descriptions],
set: (descriptions) => {
if (
!Array.isArray(descriptions) ||
descriptions.some((description) => typeof description !== 'string')
) {
throw new ArgumentError(
`Descriptions are expected to be an array of strings but got ${data.descriptions}`,
);
}
data.updateFlags.descriptions = true;
data.descriptions = [...descriptions];
},
},
}),
);
@ -386,6 +413,12 @@ const { Source } = require('./enums');
if (Array.isArray(serialized.points)) {
this.points = serialized.points;
}
if (
Array.isArray(serialized.descriptions) &&
serialized.descriptions.every((desc) => typeof desc === 'string')
) {
this.descriptions = serialized.descriptions;
}
if (typeof serialized.attributes === 'object') {
this.attributes = serialized.attributes;
}
@ -429,7 +462,7 @@ const { Source } = require('./enums');
}
// Updates element in collection which contains it
ObjectState.prototype.save.implementation = async function () {
ObjectState.prototype.save.implementation = function () {
if (this.__internal && this.__internal.save) {
return this.__internal.save();
}
@ -438,7 +471,7 @@ const { Source } = require('./enums');
};
// Delete element from a collection which contains it
ObjectState.prototype.delete.implementation = async function (frame, force) {
ObjectState.prototype.delete.implementation = function (frame, force) {
if (this.__internal && this.__internal.delete) {
if (!Number.isInteger(+frame) || +frame < 0) {
throw new ArgumentError('Frame argument must be a non negative integer');

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -74,9 +74,9 @@
traverse(plugin[key], api[key]);
}
} else if (
['enter', 'leave'].includes(key)
&& typeof api === 'function'
&& typeof (plugin[key] === 'function')
['enter', 'leave'].includes(key) &&
typeof api === 'function' &&
typeof (plugin[key] === 'function')
) {
decorator.callback = api;
decorator[key] = plugin[key];

@ -57,7 +57,6 @@
requests[e.data.id].resolve(e.data.responseData);
} else {
requests[e.data.id].reject({
error: e.data.error,
response: {
status: e.data.status,
data: e.data.responseData,
@ -135,9 +134,9 @@
return response.data;
}
async function share(directory) {
async function share(directoryArg) {
const { backendAPI } = config;
directory = encodeURIComponent(directory);
const directory = encodeURIComponent(directoryArg);
let response = null;
try {
@ -839,7 +838,6 @@
);
} catch (errorData) {
throw generateError({
...errorData,
message: '',
response: {
...errorData.response,
@ -1145,7 +1143,8 @@
const closureId = Date.now();
predictAnnotations.latestRequest.id = closureId;
const predicate = () => !predictAnnotations.latestRequest.fetching || predictAnnotations.latestRequest.id !== closureId;
const predicate = () => !predictAnnotations.latestRequest.fetching ||
predictAnnotations.latestRequest.id !== closureId;
if (predictAnnotations.latestRequest.fetching) {
waitFor(5, predicate).then(() => {
if (predictAnnotations.latestRequest.id !== closureId) {
@ -1256,7 +1255,6 @@
});
} catch (errorData) {
throw generateError({
...errorData,
message: '',
response: {
...errorData.response,

@ -37,8 +37,12 @@
return result;
},
async clear(reload = false) {
const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload);
async clear(
reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true,
) {
const result = await PluginRegistry.apiWrapper.call(
this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly,
);
return result;
},
@ -1721,17 +1725,17 @@
for (const [field, isUpdated] of Object.entries(this.__updatedFields)) {
if (isUpdated) {
switch (field) {
case 'status':
jobData.status = this.status;
break;
case 'assignee':
jobData.assignee_id = this.assignee ? this.assignee.id : null;
break;
case 'reviewer':
jobData.reviewer_id = this.reviewer ? this.reviewer.id : null;
break;
default:
break;
case 'status':
jobData.status = this.status;
break;
case 'assignee':
jobData.assignee_id = this.assignee ? this.assignee.id : null;
break;
case 'reviewer':
jobData.reviewer_id = this.reviewer ? this.reviewer.id : null;
break;
default:
break;
}
}
}
@ -1897,8 +1901,10 @@
return result;
};
Job.prototype.annotations.clear.implementation = async function (reload) {
const result = await clearAnnotations(this, reload);
Job.prototype.annotations.clear.implementation = async function (
reload, startframe, endframe, delTrackKeyframesOnly,
) {
const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
return result;
};
@ -1996,26 +2002,26 @@
for (const [field, isUpdated] of Object.entries(this.__updatedFields)) {
if (isUpdated) {
switch (field) {
case 'assignee':
taskData.assignee_id = this.assignee ? this.assignee.id : null;
break;
case 'name':
taskData.name = this.name;
break;
case 'bug_tracker':
taskData.bug_tracker = this.bugTracker;
break;
case 'subset':
taskData.subset = this.subset;
break;
case 'project_id':
taskData.project_id = this.projectId;
break;
case 'labels':
taskData.labels = [...this._internalData.labels.map((el) => el.toJSON())];
break;
default:
break;
case 'assignee':
taskData.assignee_id = this.assignee ? this.assignee.id : null;
break;
case 'name':
taskData.name = this.name;
break;
case 'bug_tracker':
taskData.bug_tracker = this.bugTracker;
break;
case 'subset':
taskData.subset = this.subset;
break;
case 'project_id':
taskData.project_id = this.projectId;
break;
case 'labels':
taskData.labels = [...this._internalData.labels.map((el) => el.toJSON())];
break;
default:
break;
}
}
}

@ -640,44 +640,44 @@ describe('Feature: clear annotations', () => {
test('clear annotations in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
await task.annotations.clear();
annotations = await task.annotations.get(0);
expect(annotations.length).toBe(0);
expect(annotations).toHaveLength(0);
});
test('clear annotations in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
await job.annotations.clear();
annotations = await job.annotations.get(0);
expect(annotations.length).toBe(0);
expect(annotations).toHaveLength(0);
});
test('clear annotations with reload in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
annotations[0].occluded = true;
await annotations[0].save();
expect(task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.clear(true);
annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
expect(task.annotations.hasUnsavedChanges()).toBe(false);
});
test('clear annotations with reload in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
annotations[0].occluded = true;
await annotations[0].save();
expect(job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.clear(true);
annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(annotations).not.toHaveLength(0);
expect(job.annotations.hasUnsavedChanges()).toBe(false);
});
@ -714,16 +714,16 @@ describe('Feature: select object', () => {
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
result = await task.annotations.select(annotations, 1415, 765);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(10);
expect(result.state.points).toHaveLength(10);
result = await task.annotations.select(annotations, 1083, 543);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POINTS);
expect(result.state.points.length).toBe(16);
expect(result.state.points).toHaveLength(16);
result = await task.annotations.select(annotations, 613, 811);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(94);
expect(result.state.points).toHaveLength(94);
result = await task.annotations.select(annotations, 600, 900);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID);
expect(result.state.points.length).toBe(16);
expect(result.state.points).toHaveLength(16);
});
test('select object in a job', async () => {
@ -737,10 +737,10 @@ describe('Feature: select object', () => {
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
result = await job.annotations.select(annotations, 1490, 237);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(94);
expect(result.state.points).toHaveLength(94);
result = await job.annotations.select(annotations, 600, 900);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID);
expect(result.state.points.length).toBe(16);
expect(result.state.points).toHaveLength(16);
});
test('trying to select from not object states', async () => {

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -69,7 +69,6 @@ const plugin = {
},
};
async function test() {
await window.cvat.plugins.register(plugin);
await window.cvat.server.login('admin', 'nimda760');

@ -99,7 +99,7 @@ describe('Feature: save a project', () => {
const labelsLength = result[0].labels.length;
const newLabel = new window.cvat.classes.Label({
name: "My boss's car",
name: 'My boss\'s car',
attributes: [
{
default_value: 'false',
@ -119,7 +119,7 @@ describe('Feature: save a project', () => {
});
expect(result[0].labels).toHaveLength(labelsLength + 1);
const appendedLabel = result[0].labels.filter((el) => el.name === "My boss's car");
const appendedLabel = result[0].labels.filter((el) => el.name === 'My boss\'s car');
expect(appendedLabel).toHaveLength(1);
expect(appendedLabel[0].attributes).toHaveLength(1);
expect(appendedLabel[0].attributes[0].name).toBe('parked');

@ -115,7 +115,7 @@ describe('Feature: save a task', () => {
const labelsLength = result[0].labels.length;
const newLabel = new window.cvat.classes.Label({
name: "My boss's car",
name: 'My boss\'s car',
attributes: [
{
default_value: 'false',
@ -135,7 +135,7 @@ describe('Feature: save a task', () => {
});
expect(result[0].labels).toHaveLength(labelsLength + 1);
const appendedLabel = result[0].labels.filter((el) => el.name === "My boss's car");
const appendedLabel = result[0].labels.filter((el) => el.name === 'My boss\'s car');
expect(appendedLabel).toHaveLength(1);
expect(appendedLabel[0].attributes).toHaveLength(1);
expect(appendedLabel[0].attributes[0].name).toBe('parked');
@ -149,7 +149,7 @@ describe('Feature: save a task', () => {
name: 'New Task',
labels: [
{
name: "My boss's car",
name: 'My boss\'s car',
attributes: [
{
default_value: 'false',

@ -1,2 +0,0 @@
**/3rdparty/*.js
webpack.config.js

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -8,36 +8,5 @@ module.exports = {
sourceType: 'module',
ecmaVersion: 2018,
},
plugins: ['security', 'no-unsanitized', 'no-unsafe-innerhtml'],
extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'],
rules: {
'no-await-in-loop': [0],
'global-require': [0],
'no-new': [0],
'class-methods-use-this': [0],
'no-restricted-properties': [
0,
{
object: 'Math',
property: 'pow',
},
],
'no-plusplus': [0],
'no-param-reassign': [0],
'no-underscore-dangle': ['error', { allowAfterThis: true }],
'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
'no-continue': [0],
'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
// This rule actual for user input data on the node.js environment mainly.
'security/detect-object-injection': 0,
indent: ['warn', 4],
'no-useless-constructor': 0,
'func-names': [0],
'valid-typeof': [0],
'no-console': [0],
'max-classes-per-file': [0],
'max-len': ['error', { code: 120 }],
quotes: ['error', 'single'],
'operator-linebreak': ['error', 'after'],
},
ignorePatterns: ['.eslintrc.js', 'webpack.config.js', 'src/3rdparty/**', 'node_modules/**', 'dist/**'],
};

@ -1 +0,0 @@
dist

File diff suppressed because it is too large Load Diff

@ -16,25 +16,9 @@
"not IE 11",
"> 2%"
],
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"babel-loader": "^8.0.6",
"copy-webpack-plugin": "^7.0.0",
"eslint": "^6.4.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-no-unsafe-innerhtml": "^1.0.16",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-security": "^1.4.0",
"nodemon": "^1.19.2",
"webpack": "^5.20.2",
"webpack-cli": "^3.3.7",
"worker-loader": "^2.0.0"
},
"devDependencies": {},
"dependencies": {
"async-mutex": "^0.3.1",
"async-mutex": "^0.3.2",
"jszip": "3.7.1"
}
}

@ -313,13 +313,14 @@ class FrameProvider {
const createImageBitmap = async function (blob) {
return new Promise((resolve) => {
const img = document.createElement('img');
img.addEventListener('load', function () {
img.addEventListener('load', function loadListener() {
resolve(this);
});
img.src = URL.createObjectURL(blob);
});
};
// eslint-disable-next-line
event.data.data = await createImageBitmap(event.data.data);
}

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT
const globalConfig = require('../.eslintrc.js');
module.exports = {
env: {
node: true,
@ -12,38 +14,22 @@ module.exports = {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'import'],
extends: [
'plugin:@typescript-eslint/recommended',
'airbnb-typescript',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
'node_modules/**',
'dist/**',
],
ignorePatterns: ['.eslintrc.js'],
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript'],
rules: {
'@typescript-eslint/indent': ['warn', 4],
...globalConfig.rules,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['error', 4],
'@typescript-eslint/lines-between-class-members': 0,
'react/static-property-placement': ['error', 'static public field'],
'react/jsx-indent': ['warn', 4],
'react/jsx-indent-props': ['warn', 4],
'react/jsx-props-no-spreading': 0,
'implicit-arrow-linebreak': 0,
'jsx-quotes': ['error', 'prefer-single'],
'arrow-parens': ['error', 'always'],
'@typescript-eslint/no-explicit-any': [0],
'@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],
'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
'no-plusplus': [0],
'lines-between-class-members': [0],
'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875
quotes: ['error', 'single'],
'max-len': ['error', { code: 120, ignoreStrings: true }],
'func-names': ['warn', 'never'],
'operator-linebreak': ['error', 'after'],
'react/require-default-props': 'off',
'react/no-unused-prop-types': 'off',
'react/no-array-index-key': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/ban-types': [
'error',
@ -55,18 +41,22 @@ module.exports = {
},
},
],
'import/order': [
'error',
{
'groups': ['builtin', 'external', 'internal'],
}
]
},
settings: {
'import/resolver': {
node: {
paths: ['src', `${__dirname}/src`],
},
},
'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875
'react/require-default-props': 'off',
'react/no-unused-prop-types': 'off',
'react/no-array-index-key': 'off',
'react/static-property-placement': ['error', 'static public field'],
'react/jsx-indent': ['warn', 4],
'react/jsx-indent-props': ['warn', 4],
'react/jsx-props-no-spreading': 0,
'jsx-quotes': ['error', 'prefer-single'],
},
// settings: {
// 'import/resolver': {
// node: {
// paths: ['src', `${__dirname}/src`],
// },
// },
// },
};

@ -1,8 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/node_modules
/dist
/build
/yarn.lock
.eslintcache

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.23.1",
"version": "1.25.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
@ -19,55 +19,22 @@
],
"author": "Intel",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.6.0",
"@types/mousetrap": "^1.6.5",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"babel-loader": "^8.0.6",
"babel-plugin-import": "^1.12.2",
"copy-webpack-plugin": "^5.1.2",
"css-loader": "^3.2.0",
"eslint": "^7.11.0",
"eslint-config-airbnb-typescript": "^12.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.14.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"react-svg-loader": "^3.0.3",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "^3.7.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"worker-loader": "^2.0.0"
},
"devDependencies": {},
"dependencies": {
"@ant-design/icons": "^4.6.2",
"@ant-design/icons": "^4.6.3",
"@types/lodash": "^4.14.172",
"@types/platform": "^1.3.4",
"@types/react": "^16.14.12",
"@types/react": "^16.14.15",
"@types/react-color": "^3.0.5",
"@types/react-dom": "^16.9.14",
"@types/react-redux": "^7.1.18",
"@types/react-resizable": "^1.7.3",
"@types/react-router": "^5.1.16",
"@types/react-router-dom": "^5.1.8",
"@types/react-router-dom": "^5.1.9",
"@types/react-share": "^3.0.3",
"@types/redux-logger": "^3.0.9",
"@types/resize-observer-browser": "^0.1.6",
"antd": "^4.13.0",
"antd": "^4.16.13",
"copy-to-clipboard": "^3.3.1",
"cvat-canvas": "file:../cvat-canvas",
"cvat-canvas3d": "file:../cvat-canvas3d",
@ -79,18 +46,17 @@
"mousetrap": "^1.6.5",
"platform": "^1.3.6",
"prop-types": "^15.7.2",
"rc-menu": "^8.10.8",
"react": "^16.14.0",
"react-awesome-query-builder": "^3.0.0",
"react-awesome-query-builder": "^4.5.1",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-cookie": "^4.0.3",
"react-dom": "^16.14.0",
"react-moment": "^1.1.1",
"react-redux": "^7.2.4",
"react-redux": "^7.2.5",
"react-resizable": "^1.11.1",
"react-router": "^5.1.0",
"react-router-dom": "^5.1.0",
"react-share": "^3.0.1",
"react-share": "^4.4.0",
"redux": "^4.1.1",
"redux-devtools-extension": "^2.13.9",
"redux-logger": "^3.0.6",

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import { MutableRefObject } from 'react';
import {
ActionCreator, AnyAction, Dispatch, Store,
} from 'redux';
@ -150,7 +149,6 @@ export enum AnnotationActionTypes {
COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE',
COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS',
ACTIVATE_OBJECT = 'ACTIVATE_OBJECT',
SELECT_OBJECTS = 'SELECT_OBJECTS',
REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS',
REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED',
PROPAGATE_OBJECT = 'PROPAGATE_OBJECT',
@ -184,7 +182,6 @@ export enum AnnotationActionTypes {
SAVE_LOGS_SUCCESS = 'SAVE_LOGS_SUCCESS',
SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED',
INTERACT_WITH_CANVAS = 'INTERACT_WITH_CANVAS',
SET_AI_TOOLS_REF = 'SET_AI_TOOLS_REF',
GET_DATA_FAILED = 'GET_DATA_FAILED',
SWITCH_REQUEST_REVIEW_DIALOG = 'SWITCH_REQUEST_REVIEW_DIALOG',
SWITCH_SUBMIT_REVIEW_DIALOG = 'SWITCH_SUBMIT_REVIEW_DIALOG',
@ -197,6 +194,7 @@ export enum AnnotationActionTypes {
GET_CONTEXT_IMAGE = 'GET_CONTEXT_IMAGE',
GET_CONTEXT_IMAGE_SUCCESS = 'GET_CONTEXT_IMAGE_SUCCESS',
GET_CONTEXT_IMAGE_FAILED = 'GET_CONTEXT_IMAGE_FAILED',
SWITCH_NAVIGATION_BLOCKED = 'SWITCH_NAVIGATION_BLOCKED',
}
export function saveLogsAsync(): ThunkAction {
@ -259,12 +257,14 @@ export function fetchAnnotationsAsync(): ThunkAction {
filters, frame, showAllInterpolationTracks, jobInstance,
} = receiveAnnotationsParameters();
const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters);
const history = await jobInstance.actions.get();
const [minZ, maxZ] = computeZRange(states);
dispatch({
type: AnnotationActionTypes.FETCH_ANNOTATIONS_SUCCESS,
payload: {
states,
history,
minZ,
maxZ,
},
@ -306,17 +306,24 @@ export function updateCanvasContextMenu(
};
}
export function removeAnnotationsAsync(sessionInstance: any): ThunkAction {
export function removeAnnotationsAsync(
startFrame: number, endFrame: number, delTrackKeyframesOnly: boolean,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
await sessionInstance.annotations.clear();
await sessionInstance.actions.clear();
const history = await sessionInstance.actions.get();
const {
filters, frame, showAllInterpolationTracks, jobInstance,
} = receiveAnnotationsParameters();
await jobInstance.annotations.clear(false, startFrame, endFrame, delTrackKeyframesOnly);
await jobInstance.actions.clear();
const history = await jobInstance.actions.get();
const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters);
dispatch({
type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS,
payload: {
history,
states,
},
});
} catch (error) {
@ -556,15 +563,6 @@ export function copyShape(objectState: any): AnyAction {
};
}
export function selectObjects(selectedStatesID: number[]): AnyAction {
return {
type: AnnotationActionTypes.SELECT_OBJECTS,
payload: {
selectedStatesID,
},
};
}
export function activateObject(activatedStateID: number | null, activatedAttributeID: number | null): AnyAction {
return {
type: AnnotationActionTypes.ACTIVATE_OBJECT,
@ -639,18 +637,17 @@ export function getPredictionsAsync(): ThunkAction {
return;
}
annotations = annotations.map(
(data: any): any =>
new cvat.classes.ObjectState({
shapeType: data.type,
label: job.task.labels.filter((label: any): boolean => label.id === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
(data: any): any => new cvat.classes.ObjectState({
shapeType: data.type,
label: job.task.labels.filter((label: any): boolean => label.id === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
);
dispatch({
@ -689,8 +686,12 @@ export function getPredictionsAsync(): ThunkAction {
};
}
export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number,
forceUpdate?: boolean): ThunkAction {
export function changeFrameAsync(
toFrame: number,
fillBuffer?: boolean,
frameStep?: number,
forceUpdate?: boolean,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState();
const { instance: job } = state.annotation.job;
@ -1466,15 +1467,6 @@ export function interactWithCanvas(activeInteractor: Model | OpenCVTool, activeL
};
}
export function setAIToolsRef(ref: MutableRefObject<any>): AnyAction {
return {
type: AnnotationActionTypes.SET_AI_TOOLS_REF,
payload: {
aiToolsRef: ref,
},
};
}
export function repeatDrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
@ -1666,3 +1658,12 @@ export function getContextImageAsync(): ThunkAction {
}
};
}
export function switchNavigationBlocked(navigationBlocked: boolean): AnyAction {
return {
type: AnnotationActionTypes.SWITCH_NAVIGATION_BLOCKED,
payload: {
navigationBlocked,
},
};
}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -51,8 +51,9 @@ export const authActions = {
changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD),
changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS),
changePasswordFailed: (error: any) => createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error }),
switchChangePasswordDialog: (showChangePasswordDialog: boolean) =>
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog }),
switchChangePasswordDialog: (showChangePasswordDialog: boolean) => (
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog })
),
requestPasswordReset: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET),
requestPasswordResetSuccess: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS),
requestPasswordResetFailed: (error: any) => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED, { error }),
@ -60,11 +61,12 @@ export const authActions = {
resetPasswordSuccess: () => createAction(AuthActionTypes.RESET_PASSWORD_SUCCESS),
resetPasswordFailed: (error: any) => createAction(AuthActionTypes.RESET_PASSWORD_FAILED, { error }),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean, allowResetPassword: boolean) =>
loadServerAuthActionsSuccess: (allowChangePassword: boolean, allowResetPassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, {
allowChangePassword,
allowResetPassword,
}),
})
),
loadServerAuthActionsFailed: (error: any) => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error }),
};

@ -25,16 +25,15 @@ export const boundariesActions = {
minZ: number,
maxZ: number,
colors: string[],
) =>
createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
job,
states,
frameNumber,
frameData,
minZ,
maxZ,
colors,
}),
) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
job,
states,
frameNumber,
frameData,
minZ,
maxZ,
colors,
}),
throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR),
};

@ -0,0 +1,210 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { Dispatch, ActionCreator } from 'redux';
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { CloudStoragesQuery, CloudStorage } from 'reducers/interfaces';
const cvat = getCore();
export enum CloudStorageActionTypes {
UPDATE_CLOUD_STORAGES_GETTING_QUERY = 'UPDATE_CLOUD_STORAGES_GETTING_QUERY',
GET_CLOUD_STORAGES = 'GET_CLOUD_STORAGES',
GET_CLOUD_STORAGE_SUCCESS = 'GET_CLOUD_STORAGES_SUCCESS',
GET_CLOUD_STORAGE_FAILED = 'GET_CLOUD_STORAGES_FAILED',
GET_CLOUD_STORAGE_STATUS = 'GET_CLOUD_STORAGE_STATUS',
GET_CLOUD_STORAGE_STATUS_SUCCESS = 'GET_CLOUD_STORAGE_STATUS_SUCCESS',
GET_CLOUD_STORAGE_STATUS_FAILED = 'GET_CLOUD_STORAGE_STATUS_FAILED',
GET_CLOUD_STORAGE_PREVIEW = 'GET_CLOUD_STORAGE_PREVIEW',
GET_CLOUD_STORAGE_PREVIEW_SUCCESS = 'GET_CLOUD_STORAGE_PREVIEW_SUCCESS',
GET_CLOUD_STORAGE_PREVIEW_FAILED = 'GET_CLOUD_STORAGE_PREVIEW_FAILED',
CREATE_CLOUD_STORAGE = 'CREATE_CLOUD_STORAGE',
CREATE_CLOUD_STORAGE_SUCCESS = 'CREATE_CLOUD_STORAGE_SUCCESS',
CREATE_CLOUD_STORAGE_FAILED = 'CREATE_CLOUD_STORAGE_FAILED',
DELETE_CLOUD_STORAGE = 'DELETE_CLOUD_STORAGE',
DELETE_CLOUD_STORAGE_SUCCESS = 'DELETE_CLOUD_STORAGE_SUCCESS',
DELETE_CLOUD_STORAGE_FAILED = 'DELETE_CLOUD_STORAGE_FAILED',
UPDATE_CLOUD_STORAGE = 'UPDATE_CLOUD_STORAGE',
UPDATE_CLOUD_STORAGE_SUCCESS = 'UPDATE_CLOUD_STORAGE_SUCCESS',
UPDATE_CLOUD_STORAGE_FAILED = 'UPDATE_CLOUD_STORAGE_FAILED',
LOAD_CLOUD_STORAGE_CONTENT = 'LOAD_CLOUD_STORAGE_CONTENT',
LOAD_CLOUD_STORAGE_CONTENT_FAILED = 'LOAD_CLOUD_STORAGE_CONTENT_FAILED',
LOAD_CLOUD_STORAGE_CONTENT_SUCCESS = 'LOAD_CLOUD_STORAGE_CONTENT_SUCCESS',
}
const cloudStoragesActions = {
updateCloudStoragesGettingQuery: (query: Partial<CloudStoragesQuery>) => (
createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGES_GETTING_QUERY, { query })
),
getCloudStorages: () => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGES),
getCloudStoragesSuccess: (
array: any[],
count: number,
query: Partial<CloudStoragesQuery>,
) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS, {
array,
count,
query,
}),
getCloudStoragesFailed: (error: any, query: Partial<CloudStoragesQuery>) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_FAILED, { error, query })
),
deleteCloudStorage: (cloudStorageID: number) => (
createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE, { cloudStorageID })
),
deleteCloudStorageSuccess: (cloudStorageID: number) => (
createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE_SUCCESS, { cloudStorageID })
),
deleteCloudStorageFailed: (error: any, cloudStorageID: number) => (
createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE_FAILED, { error, cloudStorageID })
),
createCloudStorage: () => createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE),
createCloudStorageSuccess: (cloudStorageID: number) => (
createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE_SUCCESS, { cloudStorageID })
),
createCloudStorageFailed: (error: any) => (
createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE_FAILED, { error })
),
updateCloudStorage: () => createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE, {}),
updateCloudStorageSuccess: (cloudStorage: CloudStorage) => (
createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_SUCCESS, { cloudStorage })
),
updateCloudStorageFailed: (cloudStorage: CloudStorage, error: any) => (
createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_FAILED, { cloudStorage, error })
),
loadCloudStorageContent: () => createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT),
loadCloudStorageContentSuccess: (cloudStorageID: number, content: any) => (
createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_SUCCESS, { cloudStorageID, content })
),
loadCloudStorageContentFailed: (cloudStorageID: number, error: any) => (
createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_FAILED, { cloudStorageID, error })
),
getCloudStorageStatus: (id: number) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS, { id }),
getCloudStorageStatusSuccess: (cloudStorageID: number, status: string) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS, { cloudStorageID, status })
),
getCloudStorageStatusFailed: (cloudStorageID: number, error: any) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED, { cloudStorageID, error })
),
getCloudStoragePreiew: (cloudStorageID: number) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW, { cloudStorageID })
),
getCloudStoragePreiewSuccess: (cloudStorageID: number, preview: string) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_SUCCESS, { cloudStorageID, preview })
),
getCloudStoragePreiewFailed: (cloudStorageID: number, error: any) => (
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_FAILED, { cloudStorageID, error })
),
};
export type CloudStorageActions = ActionUnion<typeof cloudStoragesActions>;
export function getCloudStoragesAsync(query: Partial<CloudStoragesQuery>): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(cloudStoragesActions.getCloudStorages());
dispatch(cloudStoragesActions.updateCloudStoragesGettingQuery(query));
const filteredQuery = { ...query };
for (const key in filteredQuery) {
if (filteredQuery[key] === null) {
delete filteredQuery[key];
}
}
let result = null;
try {
result = await cvat.cloudStorages.get(filteredQuery);
} catch (error) {
dispatch(cloudStoragesActions.getCloudStoragesFailed(error, query));
return;
}
const array = Array.from(result);
dispatch(cloudStoragesActions.getCloudStoragesSuccess(
array,
result.count,
query,
));
};
}
export function deleteCloudStorageAsync(cloudStorageInstance: any): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(cloudStoragesActions.deleteCloudStorage(cloudStorageInstance.id));
await cloudStorageInstance.delete();
} catch (error) {
dispatch(cloudStoragesActions.deleteCloudStorageFailed(error, cloudStorageInstance.id));
return;
}
dispatch(cloudStoragesActions.deleteCloudStorageSuccess(cloudStorageInstance.id));
};
}
export function createCloudStorageAsync(data: any): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const cloudStorageInstance = new cvat.classes.CloudStorage(data);
dispatch(cloudStoragesActions.createCloudStorage());
try {
const savedCloudStorage = await cloudStorageInstance.save();
dispatch(cloudStoragesActions.createCloudStorageSuccess(savedCloudStorage.id));
} catch (error) {
dispatch(cloudStoragesActions.createCloudStorageFailed(error));
}
};
}
export function updateCloudStorageAsync(data: any): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const cloudStorageInstance = new cvat.classes.CloudStorage(data);
dispatch(cloudStoragesActions.updateCloudStorage());
try {
const savedCloudStorage = await cloudStorageInstance.save();
dispatch(cloudStoragesActions.updateCloudStorageSuccess(savedCloudStorage));
} catch (error) {
dispatch(cloudStoragesActions.updateCloudStorageFailed(data, error));
}
};
}
export function loadCloudStorageContentAsync(cloudStorage: CloudStorage): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(cloudStoragesActions.loadCloudStorageContent());
try {
const result = await cloudStorage.getContent();
dispatch(cloudStoragesActions.loadCloudStorageContentSuccess(cloudStorage.id, result));
} catch (error) {
dispatch(cloudStoragesActions.loadCloudStorageContentFailed(cloudStorage.id, error));
}
};
}
export function getCloudStorageStatusAsync(cloudStorage: CloudStorage): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(cloudStoragesActions.getCloudStorageStatus(cloudStorage.id));
try {
const result = await cloudStorage.getStatus();
dispatch(cloudStoragesActions.getCloudStorageStatusSuccess(cloudStorage.id, result));
} catch (error) {
dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error));
}
};
}
export function getCloudStoragePreviewAsync(cloudStorage: CloudStorage): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(cloudStoragesActions.getCloudStoragePreiew(cloudStorage.id));
try {
const result = await cloudStorage.getPreview();
dispatch(cloudStoragesActions.getCloudStoragePreiewSuccess(cloudStorage.id, result));
} catch (error) {
dispatch(cloudStoragesActions.getCloudStoragePreiewFailed(cloudStorage.id, error));
}
};
}

@ -15,16 +15,19 @@ export enum ExportActionTypes {
export const exportActions = {
openExportModal: (instance: any) => createAction(ExportActionTypes.OPEN_EXPORT_MODAL, { instance }),
closeExportModal: () => createAction(ExportActionTypes.CLOSE_EXPORT_MODAL),
exportDataset: (instance: any, format: string) =>
createAction(ExportActionTypes.EXPORT_DATASET, { instance, format }),
exportDatasetSuccess: (instance: any, format: string) =>
createAction(ExportActionTypes.EXPORT_DATASET_SUCCESS, { instance, format }),
exportDatasetFailed: (instance: any, format: string, error: any) =>
exportDataset: (instance: any, format: string) => (
createAction(ExportActionTypes.EXPORT_DATASET, { instance, format })
),
exportDatasetSuccess: (instance: any, format: string) => (
createAction(ExportActionTypes.EXPORT_DATASET_SUCCESS, { instance, format })
),
exportDatasetFailed: (instance: any, format: string, error: any) => (
createAction(ExportActionTypes.EXPORT_DATASET_FAILED, {
instance,
format,
error,
}),
})
),
};
export const exportDatasetAsync = (

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -15,10 +15,11 @@ export enum FormatsActionTypes {
const formatsActions = {
getFormats: () => createAction(FormatsActionTypes.GET_FORMATS),
getFormatsSuccess: (annotationFormats: any) =>
getFormatsSuccess: (annotationFormats: any) => (
createAction(FormatsActionTypes.GET_FORMATS_SUCCESS, {
annotationFormats,
}),
})
),
getFormatsFailed: (error: any) => createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error }),
};

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -22,44 +22,48 @@ export enum ModelsActionTypes {
export const modelsActions = {
getModels: () => createAction(ModelsActionTypes.GET_MODELS),
getModelsSuccess: (models: Model[]) =>
createAction(ModelsActionTypes.GET_MODELS_SUCCESS, {
models,
}),
getModelsFailed: (error: any) =>
createAction(ModelsActionTypes.GET_MODELS_FAILED, {
error,
}),
getModelsSuccess: (models: Model[]) => createAction(ModelsActionTypes.GET_MODELS_SUCCESS, {
models,
}),
getModelsFailed: (error: any) => createAction(ModelsActionTypes.GET_MODELS_FAILED, {
error,
}),
fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }),
getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) =>
getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => (
createAction(ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, {
taskID,
activeInference,
}),
getInferenceStatusFailed: (taskID: number, error: any) =>
})
),
getInferenceStatusFailed: (taskID: number, error: any) => (
createAction(ModelsActionTypes.GET_INFERENCE_STATUS_FAILED, {
taskID,
error,
}),
startInferenceFailed: (taskID: number, error: any) =>
})
),
startInferenceFailed: (taskID: number, error: any) => (
createAction(ModelsActionTypes.START_INFERENCE_FAILED, {
taskID,
error,
}),
cancelInferenceSuccess: (taskID: number) =>
})
),
cancelInferenceSuccess: (taskID: number) => (
createAction(ModelsActionTypes.CANCEL_INFERENCE_SUCCESS, {
taskID,
}),
cancelInferenceFailed: (taskID: number, error: any) =>
})
),
cancelInferenceFailed: (taskID: number, error: any) => (
createAction(ModelsActionTypes.CANCEL_INFERENCE_FAILED, {
taskID,
error,
}),
})
),
closeRunModelDialog: () => createAction(ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG),
showRunModelDialog: (taskInstance: any) =>
showRunModelDialog: (taskInstance: any) => (
createAction(ModelsActionTypes.SHOW_RUN_MODEL_DIALOG, {
taskInstance,
}),
})
),
};
export type ModelsActions = ActionUnion<typeof modelsActions>;

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -32,14 +32,17 @@ export enum ReviewActionTypes {
}
export const reviewActions = {
initializeReviewSuccess: (reviewInstance: any, frame: number) =>
createAction(ReviewActionTypes.INITIALIZE_REVIEW_SUCCESS, { reviewInstance, frame }),
initializeReviewSuccess: (reviewInstance: any, frame: number) => (
createAction(ReviewActionTypes.INITIALIZE_REVIEW_SUCCESS, { reviewInstance, frame })
),
initializeReviewFailed: (error: any) => createAction(ReviewActionTypes.INITIALIZE_REVIEW_FAILED, { error }),
createIssue: () => createAction(ReviewActionTypes.CREATE_ISSUE, {}),
startIssue: (position: number[]) =>
createAction(ReviewActionTypes.START_ISSUE, { position: cvat.classes.Issue.hull(position) }),
finishIssueSuccess: (frame: number, issue: any) =>
createAction(ReviewActionTypes.FINISH_ISSUE_SUCCESS, { frame, issue }),
startIssue: (position: number[]) => (
createAction(ReviewActionTypes.START_ISSUE, { position: cvat.classes.Issue.hull(position) })
),
finishIssueSuccess: (frame: number, issue: any) => (
createAction(ReviewActionTypes.FINISH_ISSUE_SUCCESS, { frame, issue })
),
finishIssueFailed: (error: any) => createAction(ReviewActionTypes.FINISH_ISSUE_FAILED, { error }),
cancelIssue: () => createAction(ReviewActionTypes.CANCEL_ISSUE),
commentIssue: (issueId: number) => createAction(ReviewActionTypes.COMMENT_ISSUE, { issueId }),

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -17,11 +17,12 @@ export enum ShareActionTypes {
const shareActions = {
loadShareData: () => createAction(ShareActionTypes.LOAD_SHARE_DATA),
loadShareDataSuccess: (values: ShareFileInfo[], directory: string) =>
loadShareDataSuccess: (values: ShareFileInfo[], directory: string) => (
createAction(ShareActionTypes.LOAD_SHARE_DATA_SUCCESS, {
values,
directory,
}),
})
),
loadShareDataFailed: (error: any) => createAction(ShareActionTypes.LOAD_SHARE_DATA_FAILED, { error }),
};

@ -386,10 +386,13 @@ export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, A
if (data.subset) {
description.subset = data.subset;
}
if (data.cloudStorageId) {
description.cloud_storage_id = data.cloudStorageId;
}
const taskInstance = new cvat.classes.Task(description);
taskInstance.clientFiles = data.files.local;
taskInstance.serverFiles = data.files.share;
taskInstance.serverFiles = data.files.share.concat(data.files.cloudStorage);
taskInstance.remoteFiles = data.files.remote;
if (data.advanced.repository) {
@ -401,6 +404,7 @@ export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, A
};
gitPlugin.data.task = taskInstance;
gitPlugin.data.repos = data.advanced.repository;
gitPlugin.data.format = data.advanced.format;
gitPlugin.data.lfs = data.advanced.lfs;
}
}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -16,10 +16,12 @@ export enum UserAgreementsActionTypes {
const userAgreementsActions = {
getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS),
getUserAgreementsSuccess: (userAgreements: UserAgreement[]) =>
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements),
getUserAgreementsFailed: (error: any) =>
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }),
getUserAgreementsSuccess: (userAgreements: UserAgreement[]) => (
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements)
),
getUserAgreementsFailed: (error: any) => (
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error })
),
};
export type UserAgreementsActions = ActionUnion<typeof userAgreementsActions>;

@ -0,0 +1,21 @@
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M1.289 2.771L0 3.30333V12.6463L1.289 13.1755L1.29675 13.1678V2.77833L1.289 2.771Z" fill="#8C3123"/>
<path d="M8.18756 11.8195L1.28906 13.1755V2.771L8.18756 4.0975V11.8195Z" fill="#E05243"/>
<path d="M5.07349 9.69626L7.99961 10.0039L8.01798 9.96888L8.03442 6.00655L7.99961 5.97559L5.07349 6.27876V9.69626Z" fill="#8C3123"/>
<path d="M7.99976 11.8347L14.7104 13.1784L14.721 13.1645L14.7208 2.78029L14.7102 2.771L7.99976 4.11273V11.8347Z" fill="#8C3123"/>
<path d="M10.9267 9.69626L7.99976 10.0039V5.97559L10.9267 6.27876V9.69626Z" fill="#E05243"/>
<path d="M10.9265 4.62622L7.99961 5.06674L5.07349 4.62622L7.99592 3.99365L10.9265 4.62622Z" fill="#5E1F18"/>
<path d="M10.9265 11.3448L7.99961 10.9014L5.07349 11.3448L7.99605 12.0185L10.9265 11.3448Z" fill="#F2B0A9"/>
<path d="M5.07349 4.62612L7.99961 4.02813L8.0233 4.02209V0.0161548L7.99961 0L5.07349 1.20841V4.62612Z" fill="#8C3123"/>
<path d="M10.9267 4.62612L7.99976 4.02813V0L10.9267 1.20841V4.62612Z" fill="#E05243"/>
<path d="M7.99968 15.9704L5.07324 14.7624V11.3447L7.99968 11.9425L8.04274 11.9829L8.03106 15.9006L7.99968 15.9704Z" fill="#8C3123"/>
<path d="M7.99976 15.9704L10.9264 14.7624V11.3447L7.99976 11.9425V15.9704Z" fill="#E05243"/>
<path d="M14.7104 2.771L16 3.30333V12.6463L14.7104 13.1784V2.771Z" fill="#E05243"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -3,9 +3,9 @@
SPDX-License-Identifier: MIT
-->
<svg height="1em" width="1em" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<svg stroke="currentColor" height="1em" width="1em" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g>
<rect rx="12" height="166" width="167" y="25" x="25" stroke-width="15" stroke="#000" fill="none" stroke-dasharray="35" />
<rect rx="12" height="166" width="167" y="70" x="70" stroke-width="15" stroke="#000" fill="none" />
<rect rx="12" height="166" width="167" y="25" x="25" stroke-width="15" fill="none" stroke-dasharray="35" />
<rect rx="12" height="166" width="167" y="70" x="70" stroke-width="15" fill="none" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 428 B

@ -3,9 +3,9 @@
SPDX-License-Identifier: MIT
-->
<svg height="1em" width="1em" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<svg stroke="currentColor" height="1em" width="1em" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g>
<rect rx="12" height="166" width="167" y="25" x="25" stroke-width="15" stroke="#000" fill="none" />
<rect rx="12" height="166" width="167" y="70" x="70" stroke-width="15" stroke="#000" fill="none" stroke-dasharray="35" />
<rect rx="12" height="166" width="167" y="25" x="25" stroke-width="15" fill="none" />
<rect rx="12" height="166" width="167" y="70" x="70" stroke-width="15" fill="none" stroke-dasharray="35" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 428 B

@ -0,0 +1,4 @@
<svg width="1em" height="1em" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.98 25.9939C18.55 25.2679 21.498 24.6669 21.532 24.6589L21.594 24.6439L18.224 20.0289C16.37 17.4909 14.854 15.4039 14.854 15.3919C14.854 15.3799 18.334 4.3359 18.354 4.2969C18.361 4.2839 20.729 8.9909 24.095 15.7079L29.869 27.2289L29.913 27.3169H8.49097L14.98 25.9939Z" fill="#0089D6"/>
<path d="M2.125 24.586C2.125 24.58 3.713 21.406 5.654 17.533L9.183 10.492L13.3 6.52002C15.562 4.33502 17.419 2.54402 17.426 2.54102C17.4112 2.60727 17.3891 2.67167 17.36 2.73302L12.89 13.759L8.5 24.589H5.311C4.24902 24.5999 3.18696 24.5989 2.125 24.586V24.586Z" fill="#0089D6"/>
</svg>

After

Width:  |  Height:  |  Size: 683 B

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -6,7 +6,7 @@ $grid-unit-size: 8px;
$header-height: $grid-unit-size * 7;
$layout-sm-grid-size: $grid-unit-size / 2;
$layout-sm-grid-size: $grid-unit-size * 0.5;
$layout-lg-grid-size: $grid-unit-size * 2;
$layout-sm-grid-color: rgba(0, 0, 0, 0.15);
$layout-lg-grid-color: rgba(0, 0, 0, 0.15);
@ -24,6 +24,8 @@ $border-color-3: #242424;
$border-color-hover: #40a9ff;
$background-color-1: white;
$background-color-2: #f1f1f1;
$notification-background-color-1: #d9ecff;
$notification-border-color-1: #1890ff;
$transparent-color: rgba(0, 0, 0, 0);
$player-slider-color: #979797;
$player-buttons-color: #242424;

@ -9,6 +9,7 @@ import Modal from 'antd/lib/modal';
import { LoadingOutlined } from '@ant-design/icons';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import LoadSubmenu from './load-submenu';
import { DimensionType } from '../../reducers/interfaces';
@ -21,7 +22,8 @@ interface Props {
loadActivity: string | null;
inferenceIsActive: boolean;
taskDimension: DimensionType;
onClickMenu: (params: MenuInfo, file?: File) => void;
onClickMenu: (params: MenuInfo) => void;
onUploadAnnotations: (format: string, file: File) => void;
exportIsActive: boolean;
}
@ -35,54 +37,31 @@ export enum Actions {
EXPORT_TASK = 'export_task',
}
export default function ActionsMenuComponent(props: Props): JSX.Element {
function ActionsMenuComponent(props: Props): JSX.Element {
const {
taskID,
bugTracker,
inferenceIsActive,
loaders,
onClickMenu,
onUploadAnnotations,
loadActivity,
taskDimension,
exportIsActive,
} = props;
let latestParams: MenuInfo | null = null;
function onClickMenuWrapper(params: MenuInfo | null, file?: File): void {
const copyParams = params || latestParams;
if (!copyParams) {
function onClickMenuWrapper(params: MenuInfo): void {
if (!params) {
return;
}
latestParams = copyParams;
if (copyParams.keyPath.length === 2) {
const [, action] = copyParams.keyPath;
if (action === Actions.LOAD_TASK_ANNO) {
if (file) {
Modal.confirm({
title: 'Current annotation will be lost',
content: 'You are going to upload new annotations to this task. Continue?',
className: 'cvat-modal-content-load-task-annotation',
onOk: () => {
onClickMenu(copyParams, file);
},
okButtonProps: {
type: 'primary',
danger: true,
},
okText: 'Update',
});
}
} else {
onClickMenu(copyParams);
}
} else if (copyParams.key === Actions.DELETE_TASK) {
if (params.key === Actions.DELETE_TASK) {
Modal.confirm({
title: `The task ${taskID} will be deleted`,
content: 'All related data (images, annotations) will be lost. Continue?',
className: 'cvat-modal-confirm-delete-task',
onOk: () => {
onClickMenu(copyParams);
onClickMenu(params);
},
okButtonProps: {
type: 'primary',
@ -91,7 +70,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
okText: 'Delete',
});
} else {
onClickMenu(copyParams);
onClickMenu(params);
}
}
@ -100,8 +79,22 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
{LoadSubmenu({
loaders,
loadActivity,
onFileUpload: (file: File): void => {
onClickMenuWrapper(null, file);
onFileUpload: (format: string, file: File): void => {
if (file) {
Modal.confirm({
title: 'Current annotation will be lost',
content: 'You are going to upload new annotations to this task. Continue?',
className: 'cvat-modal-content-load-task-annotation',
onOk: () => {
onUploadAnnotations(format, file);
},
okButtonProps: {
type: 'primary',
danger: true,
},
okText: 'Update',
});
}
},
menuKey: Actions.LOAD_TASK_ANNO,
taskDimension,
@ -115,9 +108,11 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
{exportIsActive && <LoadingOutlined id='cvat-export-task-loading' />}
Export task
</Menu.Item>
<hr />
<Menu.Divider />
<Menu.Item key={Actions.MOVE_TASK_TO_PROJECT}>Move to project</Menu.Item>
<Menu.Item key={Actions.DELETE_TASK}>Delete</Menu.Item>
</Menu>
);
}
export default React.memo(ActionsMenuComponent);

@ -14,7 +14,7 @@ interface Props {
menuKey: string;
loaders: any[];
loadActivity: string | null;
onFileUpload(file: File): void;
onFileUpload(format: string, file: File): void;
taskDimension: DimensionType;
}
@ -36,14 +36,15 @@ export default function LoadSubmenu(props: Props): JSX.Element {
.join(', '); // add '.' to each extension in a list
const pending = loadActivity === loader.name;
const disabled = !loader.enabled || !!loadActivity;
const format = loader.name;
return (
<Menu.Item key={loader.name} disabled={disabled} className='cvat-menu-load-submenu-item'>
<Menu.Item key={format} disabled={disabled} className='cvat-menu-load-submenu-item'>
<Upload
accept={accept}
multiple={false}
showUploadList={false}
beforeUpload={(file: File): boolean => {
onFileUpload(file);
onFileUpload(format, file);
return false;
}}
>

@ -17,6 +17,7 @@ import {
changeFrameAsync,
updateAnnotationsAsync,
} from 'actions/annotation-actions';
import isAbleToChangeFrame from 'utils/is-able-to-change-frame';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { ThunkDispatch } from 'utils/redux';
import AppearanceBlock from 'components/annotation-page/appearance-block';
@ -266,7 +267,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame =
typeof activeObjectState.keyframes.next === 'number' ? activeObjectState.keyframes.next : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
if (frame !== null && isAbleToChangeFrame()) {
changeFrame(frame);
}
}
@ -276,7 +277,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame =
typeof activeObjectState.keyframes.prev === 'number' ? activeObjectState.keyframes.prev : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
if (frame !== null && isAbleToChangeFrame()) {
changeFrame(frame);
}
}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -61,7 +61,10 @@ function ReviewContextMenu({
>
{latestComments.map(
(comment: string, id: number): JSX.Element => (
<Menu.Item className='cvat-context-menu-item' key={`${id}`}>
<Menu.Item
className='cvat-context-menu-item cvat-quick-issue-from-latest-item'
key={`${id}`}
>
{comment}
</Menu.Item>
),

@ -31,7 +31,6 @@ interface Props {
jobInstance: any;
activatedStateID: number | null;
activatedAttributeID: number | null;
selectedStatesID: number[];
annotations: any[];
frameIssues: any[] | null;
frameData: any;
@ -82,7 +81,6 @@ interface Props {
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onSplitAnnotations(sessionInstance: any, frame: number, state: any): void;
onActivateObject(activatedStateID: number | null): void;
onSelectObjects(selectedStatesID: number[]): void;
onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void;
onAddZLayer(): void;
onSwitchZLayer(cur: number): void;

@ -7,44 +7,33 @@ import Popover, { PopoverProps } from 'antd/lib/popover';
export default function withVisibilityHandling(WrappedComponent: typeof Popover, popoverType: string) {
return (props: PopoverProps): JSX.Element => {
const [initialized, setInitialized] = useState<boolean>(false);
const [visible, setVisible] = useState<boolean>(false);
const { overlayClassName, ...rest } = props;
const overlayClassNames = typeof overlayClassName === 'string' ? overlayClassName.split(/\s+/) : [];
const popoverClassName = `cvat-${popoverType}-popover`;
overlayClassNames.push(popoverClassName);
if (visible) {
const visiblePopoverClassName = `cvat-${popoverType}-popover-visible`;
overlayClassNames.push(visiblePopoverClassName);
}
const callback = (event: Event): void => {
if ((event as AnimationEvent).animationName === 'antZoomBigIn') {
setVisible(true);
}
};
const { overlayStyle } = props;
return (
<WrappedComponent
{...rest}
overlayStyle={{
...(typeof overlayStyle === 'object' ? overlayStyle : {}),
animationDuration: '0s',
animationDelay: '0s',
}}
trigger={visible ? 'click' : 'hover'}
overlayClassName={overlayClassNames.join(' ').trim()}
onVisibleChange={(_visible: boolean) => {
if (!_visible) {
setVisible(false);
} else {
// Hide other popovers
const element = window.document.getElementsByClassName(`${popoverClassName}`)[0];
if (_visible) {
const [element] = window.document.getElementsByClassName(popoverClassName);
if (element) {
element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
(element as HTMLElement).style.pointerEvents = '';
(element as HTMLElement).style.opacity = '';
}
}
if (!initialized) {
const self = window.document.getElementsByClassName(`cvat-${popoverType}-popover`)[0];
self?.addEventListener('animationend', callback);
setInitialized(true);
}
setVisible(_visible);
}}
/>
);

@ -2,9 +2,15 @@
//
// SPDX-License-Identifier: MIT
import React, { MutableRefObject } from 'react';
import React, { ReactPortal } from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import Icon, { LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import Icon, {
EnvironmentFilled,
EnvironmentOutlined,
LoadingOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
import Popover from 'antd/lib/popover';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
@ -14,14 +20,11 @@ import Tabs from 'antd/lib/tabs';
import { Row, Col } from 'antd/lib/grid';
import notification from 'antd/lib/notification';
import message from 'antd/lib/message';
import Progress from 'antd/lib/progress';
import InputNumber from 'antd/lib/input-number';
import Dropdown from 'antd/lib/dropdown';
import lodash from 'lodash';
import { AIToolsIcon } from 'icons';
import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper';
import range from 'utils/range';
import getCore from 'cvat-core-wrapper';
import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper';
import {
@ -29,12 +32,15 @@ import {
} from 'reducers/interfaces';
import {
interactWithCanvas,
switchNavigationBlocked as switchNavigationBlockedAction,
fetchAnnotationsAsync,
updateAnnotationsAsync,
createAnnotationsAsync,
} from 'actions/annotation-actions';
import DetectorRunner from 'components/model-runner-modal/detector-runner';
import LabelSelector from 'components/label-selector/label-selector';
import CVATTooltip from 'components/common/cvat-tooltip';
import ApproximationAccuracy, {
thresholdFromAccuracy,
} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy';
@ -54,7 +60,6 @@ interface StateToProps {
detectors: Model[];
trackers: Model[];
curZOrder: number;
aiToolsRef: MutableRefObject<any>;
defaultApproxPolyAccuracy: number;
toolsBlockerState: ToolsBlockerState;
}
@ -64,7 +69,8 @@ interface DispatchToProps {
updateAnnotations(statesToUpdate: any[]): void;
createAnnotations(sessionInstance: any, frame: number, statesToCreate: any[]): void;
fetchAnnotations(): void;
onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState):void;
onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState): void;
switchNavigationBlocked(navigationBlocked: boolean): void;
}
const core = getCore();
@ -92,7 +98,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
jobInstance,
frame,
curZOrder: annotation.annotations.zLayer.cur,
aiToolsRef: annotation.aiToolsRef,
defaultApproxPolyAccuracy: settings.workspace.defaultApproxPolyAccuracy,
toolsBlockerState,
};
@ -104,21 +109,81 @@ const mapDispatchToProps = {
createAnnotations: createAnnotationsAsync,
fetchAnnotations: fetchAnnotationsAsync,
onSwitchToolsBlockerState: switchToolsBlockerState,
switchNavigationBlocked: switchNavigationBlockedAction,
};
type Props = StateToProps & DispatchToProps;
interface TrackedShape {
clientID: number;
serverlessState: any;
shapePoints: number[];
trackerModel: Model;
}
interface State {
activeInteractor: Model | null;
activeLabelID: number;
activeTracker: Model | null;
trackingProgress: number | null;
trackingFrames: number;
trackedShapes: TrackedShape[];
fetching: boolean;
pointsRecieved: boolean;
approxPolyAccuracy: number;
mode: 'detection' | 'interaction' | 'tracking';
portals: React.ReactPortal[];
}
function trackedRectangleMapper(shape: number[]): number[] {
return shape.reduce(
(acc: number[], value: number, index: number): number[] => {
if (index % 2) {
// y
acc[1] = Math.min(acc[1], value);
acc[3] = Math.max(acc[3], value);
} else {
// x
acc[0] = Math.min(acc[0], value);
acc[2] = Math.max(acc[2], value);
}
return acc;
},
[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER],
);
}
function registerPlugin(): (callback: null | (() => void)) => void {
let onTrigger: null | (() => void) = null;
const listener = {
name: 'Remove annotations listener',
description: 'Tracker needs to know when annotations is reset in the job',
cvat: {
classes: {
Job: {
prototype: {
annotations: {
clear: {
leave(self: any, result: any) {
if (typeof onTrigger === 'function') {
onTrigger();
}
return result;
},
},
},
},
},
},
},
};
core.plugins.register(listener);
return (callback: null | (() => void)) => {
onTrigger = callback;
};
}
const onRemoveAnnotations = registerPlugin();
export class ToolsControlComponent extends React.PureComponent<Props, State> {
private interaction: {
id: string | null;
@ -143,11 +208,11 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
activeTracker: props.trackers.length ? props.trackers[0] : null,
activeLabelID: props.labels.length ? props.labels[0].id : null,
approxPolyAccuracy: props.defaultApproxPolyAccuracy,
trackingProgress: null,
trackingFrames: 10,
trackedShapes: [],
fetching: false,
pointsRecieved: false,
mode: 'interaction',
portals: [],
};
this.interaction = {
@ -161,15 +226,29 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
public componentDidMount(): void {
const { canvasInstance, aiToolsRef } = this.props;
aiToolsRef.current = this;
const { canvasInstance } = this.props;
onRemoveAnnotations(() => {
this.setState({ trackedShapes: [] });
});
this.setState({
portals: this.collectTrackerPortals(),
});
canvasInstance.html().addEventListener('canvas.interacted', this.interactionListener);
canvasInstance.html().addEventListener('canvas.canceled', this.cancelListener);
}
public componentDidUpdate(prevProps: Props, prevState: State): void {
const { isActivated, defaultApproxPolyAccuracy, canvasInstance } = this.props;
const { approxPolyAccuracy, mode } = this.state;
const {
isActivated, defaultApproxPolyAccuracy, canvasInstance, states,
} = this.props;
const { approxPolyAccuracy, mode, activeTracker } = this.state;
if (prevProps.states !== states || prevState.activeTracker !== activeTracker) {
this.setState({
portals: this.collectTrackerPortals(),
});
}
if (prevProps.isActivated && !isActivated) {
window.removeEventListener('contextmenu', this.contextmenuDisabler);
@ -211,11 +290,13 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
});
}
}
this.checkTrackedStates(prevProps);
}
public componentWillUnmount(): void {
const { canvasInstance, aiToolsRef } = this.props;
aiToolsRef.current = undefined;
const { canvasInstance } = this.props;
onRemoveAnnotations(null);
canvasInstance.html().removeEventListener('canvas.interacted', this.interactionListener);
canvasInstance.html().removeEventListener('canvas.canceled', this.cancelListener);
}
@ -339,6 +420,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
};
private onTracking = async (e: Event): Promise<void> => {
const { trackedShapes, activeTracker } = this.state;
const {
isActivated, jobInstance, frame, curZOrder, fetchAnnotations,
} = this.props;
@ -365,18 +447,25 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
points,
frame,
occluded: false,
source: 'auto',
attributes: {},
descriptions: [`Trackable (${activeTracker?.name})`],
});
const [clientID] = await jobInstance.annotations.put([state]);
this.setState({
trackedShapes: [
...trackedShapes,
{
clientID,
serverlessState: null,
shapePoints: points,
trackerModel: activeTracker as Model,
},
],
});
// update annotations on a canvas
fetchAnnotations();
const states = await jobInstance.annotations.get(frame);
const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID);
await this.trackState(objectState);
} catch (err) {
notification.error({
description: err.toString(),
@ -411,7 +500,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
});
};
private onChangeToolsBlockerState = (event:string):void => {
private onChangeToolsBlockerState = (event: string): void => {
const { isActivated, onSwitchToolsBlockerState } = this.props;
if (isActivated && event === 'keydown') {
onSwitchToolsBlockerState({ algorithmsLocked: true });
@ -420,6 +509,275 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
};
private collectTrackerPortals(): React.ReactPortal[] {
const { states, fetchAnnotations } = this.props;
const { trackedShapes, activeTracker } = this.state;
const trackedClientIDs = trackedShapes.map((trackedShape: TrackedShape) => trackedShape.clientID);
const portals = !activeTracker ?
[] :
states
.filter((objectState) => objectState.objectType === 'track' && objectState.shapeType === 'rectangle')
.map((objectState: any): React.ReactPortal | null => {
const { clientID } = objectState;
const selectorID = `#cvat-objects-sidebar-state-item-${clientID}`;
let targetElement = window.document.querySelector(
`${selectorID} .cvat-object-item-button-prev-keyframe`,
) as HTMLElement;
const isTracked = trackedClientIDs.includes(clientID);
if (targetElement) {
targetElement = targetElement.parentElement?.parentElement as HTMLElement;
return ReactDOM.createPortal(
<Col>
{isTracked ? (
<CVATTooltip overlay='Disable tracking'>
<EnvironmentFilled
onClick={() => {
const filteredStates = trackedShapes.filter(
(trackedShape: TrackedShape) =>
trackedShape.clientID !== clientID,
);
/* eslint no-param-reassign: ["error", { "props": false }] */
objectState.descriptions = [];
objectState.save().then(() => {
this.setState({
trackedShapes: filteredStates,
});
});
fetchAnnotations();
}}
/>
</CVATTooltip>
) : (
<CVATTooltip overlay={`Enable tracking using ${activeTracker.name}`}>
<EnvironmentOutlined
onClick={() => {
objectState.descriptions = [`Trackable (${activeTracker.name})`];
objectState.save().then(() => {
this.setState({
trackedShapes: [
...trackedShapes,
{
clientID,
serverlessState: null,
shapePoints: objectState.points,
trackerModel: activeTracker,
},
],
});
});
fetchAnnotations();
}}
/>
</CVATTooltip>
)}
</Col>,
targetElement,
);
}
return null;
})
.filter((portal: ReactPortal | null) => portal !== null);
return portals as React.ReactPortal[];
}
private async checkTrackedStates(prevProps: Props): Promise<void> {
const {
frame,
jobInstance,
states: objectStates,
trackers,
fetchAnnotations,
switchNavigationBlocked,
} = this.props;
const { trackedShapes } = this.state;
let withServerRequest = false;
type AccumulatorType = {
statefull: {
[index: string]: {
// tracker id
clientIDs: number[];
states: any[];
shapes: number[][];
};
};
stateless: {
[index: string]: {
// tracker id
clientIDs: number[];
shapes: number[][];
};
};
};
if (prevProps.frame !== frame && trackedShapes.length) {
// 1. find all trackable objects on the current frame
// 2. devide them into two groups: with relevant state, without relevant state
const trackingData = trackedShapes.reduce<AccumulatorType>(
(acc: AccumulatorType, trackedShape: TrackedShape): AccumulatorType => {
const {
serverlessState, shapePoints, clientID, trackerModel,
} = trackedShape;
const [clientState] = objectStates.filter((_state: any): boolean => _state.clientID === clientID);
if (
!clientState ||
clientState.keyframes.prev !== frame - 1 ||
clientState.keyframes.last >= frame
) {
return acc;
}
if (clientState && !clientState.outside) {
const { points } = clientState;
withServerRequest = true;
const stateIsRelevant =
serverlessState !== null &&
points.length === shapePoints.length &&
points.every((coord: number, i: number) => coord === shapePoints[i]);
if (stateIsRelevant) {
const container = acc.statefull[trackerModel.id] || {
clientIDs: [],
shapes: [],
states: [],
};
container.clientIDs.push(clientID);
container.shapes.push(points);
container.states.push(serverlessState);
acc.statefull[trackerModel.id] = container;
} else {
const container = acc.stateless[trackerModel.id] || {
clientIDs: [],
shapes: [],
};
container.clientIDs.push(clientID);
container.shapes.push(points);
acc.stateless[trackerModel.id] = container;
}
}
return acc;
},
{
statefull: {},
stateless: {},
},
);
try {
if (withServerRequest) {
switchNavigationBlocked(true);
}
// 3. get relevant state for the second group
for (const trackerID of Object.keys(trackingData.stateless)) {
let hideMessage = null;
try {
const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID);
if (!tracker) {
throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`);
}
const trackableObjects = trackingData.stateless[trackerID];
const numOfObjects = trackableObjects.clientIDs.length;
hideMessage = message.loading(
`${tracker.name}: states are being initialized for ${numOfObjects} ${
numOfObjects > 1 ? 'objects' : 'object'
} ..`,
0,
);
// eslint-disable-next-line no-await-in-loop
const response = await core.lambda.call(jobInstance.task, tracker, {
task: jobInstance.task,
frame: frame - 1,
shapes: trackableObjects.shapes,
});
const { states: serverlessStates } = response;
const statefullContainer = trackingData.statefull[trackerID] || {
clientIDs: [],
shapes: [],
states: [],
};
Array.prototype.push.apply(statefullContainer.clientIDs, trackableObjects.clientIDs);
Array.prototype.push.apply(statefullContainer.shapes, trackableObjects.shapes);
Array.prototype.push.apply(statefullContainer.states, serverlessStates);
trackingData.statefull[trackerID] = statefullContainer;
delete trackingData.stateless[trackerID];
} catch (error) {
notification.error({
message: 'Tracker initialization error',
description: error.toString(),
});
} finally {
if (hideMessage) hideMessage();
}
}
for (const trackerID of Object.keys(trackingData.statefull)) {
// 4. run tracking for all the objects
let hideMessage = null;
try {
const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID);
if (!tracker) {
throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`);
}
const trackableObjects = trackingData.statefull[trackerID];
const numOfObjects = trackableObjects.clientIDs.length;
hideMessage = message.loading(
`${tracker.name}: ${numOfObjects} ${
numOfObjects > 1 ? 'objects are' : 'object is'
} being tracked..`,
0,
);
// eslint-disable-next-line no-await-in-loop
const response = await core.lambda.call(jobInstance.task, tracker, {
task: jobInstance.task,
frame: frame - 1,
shapes: trackableObjects.shapes,
states: trackableObjects.states,
});
response.shapes = response.shapes.map(trackedRectangleMapper);
for (let i = 0; i < trackableObjects.clientIDs.length; i++) {
const clientID = trackableObjects.clientIDs[i];
const shape = response.shapes[i];
const state = response.states[i];
const [objectState] = objectStates.filter(
(_state: any): boolean => _state.clientID === clientID,
);
const [trackedShape] = trackedShapes.filter(
(_trackedShape: TrackedShape) => _trackedShape.clientID === clientID,
);
objectState.points = shape;
objectState.save().then(() => {
trackedShape.serverlessState = state;
trackedShape.shapePoints = shape;
});
}
} catch (error) {
notification.error({
message: 'Tracking error',
description: error.toString(),
});
} finally {
if (hideMessage) hideMessage();
fetchAnnotations();
}
}
} finally {
if (withServerRequest) {
switchNavigationBlocked(false);
}
}
}
}
private constructFromPoints(points: number[][]): void {
const {
frame, labels, curZOrder, jobInstance, activeLabelID, createAnnotations,
@ -457,70 +815,6 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
return points;
}
public async trackState(state: any): Promise<void> {
const { jobInstance, frame, fetchAnnotations } = this.props;
const { activeTracker, trackingFrames } = this.state;
const { clientID, points } = state;
const tracker = activeTracker as Model;
try {
this.setState({ trackingProgress: 0, fetching: true });
let response = await core.lambda.call(jobInstance.task, tracker, {
task: jobInstance.task,
frame,
shape: points,
});
for (const offset of range(1, trackingFrames + 1)) {
/* eslint-disable no-await-in-loop */
const states = await jobInstance.annotations.get(frame + offset);
const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID);
response = await core.lambda.call(jobInstance.task, tracker, {
task: jobInstance.task,
frame: frame + offset,
shape: response.points,
state: response.state,
});
const reduced = response.shape.reduce(
(acc: number[], value: number, index: number): number[] => {
if (index % 2) {
// y
acc[1] = Math.min(acc[1], value);
acc[3] = Math.max(acc[3], value);
} else {
// x
acc[0] = Math.min(acc[0], value);
acc[2] = Math.max(acc[2], value);
}
return acc;
},
[
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER,
],
);
objectState.points = reduced;
await objectState.save();
this.setState({ trackingProgress: offset / trackingFrames });
}
} finally {
this.setState({ trackingProgress: null, fetching: false });
fetchAnnotations();
}
}
public trackingAvailable(): boolean {
const { activeTracker, trackingFrames } = this.state;
const { trackers } = this.props;
return !!trackingFrames && !!trackers.length && activeTracker !== null;
}
private renderLabelBlock(): JSX.Element {
const { labels } = this.props;
const { activeLabelID } = this.state;
@ -549,9 +843,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
const {
trackers, canvasInstance, jobInstance, frame, onInteractionStart,
} = this.props;
const {
activeTracker, activeLabelID, fetching, trackingFrames,
} = this.state;
const { activeTracker, activeLabelID, fetching } = this.state;
if (!trackers.length) {
return (
@ -589,27 +881,6 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</Select>
</Col>
</Row>
<Row align='middle' justify='start' style={{ marginTop: '5px' }}>
<Col>
<Text>Tracking frames</Text>
</Col>
<Col offset={2}>
<InputNumber
value={trackingFrames}
step={1}
min={1}
precision={0}
max={jobInstance.stopFrame - frame}
onChange={(value: number | undefined | string | null): void => {
if (typeof value !== 'undefined' && value !== null) {
this.setState({
trackingFrames: +value,
});
}
}}
/>
</Col>
</Row>
<Row align='middle' justify='end'>
<Col>
<Button
@ -797,10 +1068,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</Text>
</Col>
</Row>
<Tabs
type='card'
tabBarGutter={8}
>
<Tabs type='card' tabBarGutter={8}>
<Tabs.TabPane key='interactors' tab='Interactors'>
{this.renderLabelBlock()}
{this.renderInteractorBlock()}
@ -822,7 +1090,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
interactors, detectors, trackers, isActivated, canvasInstance, labels,
} = this.props;
const {
fetching, trackingProgress, approxPolyAccuracy, pointsRecieved, mode,
fetching, approxPolyAccuracy, pointsRecieved, mode, portals,
} = this.state;
if (![...interactors, ...detectors, ...trackers].length) return null;
@ -849,8 +1117,6 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
const showAnyContent = !!labels.length;
const showInteractionContent = isActivated && mode === 'interaction' && pointsRecieved;
const showDetectionContent = fetching && mode === 'detection';
const showTrackingContent = fetching && mode === 'tracking' && trackingProgress !== null;
const formattedTrackingProgress = showTrackingContent ? +((trackingProgress as number) * 100).toFixed(0) : null;
const interactionContent: JSX.Element | null = showInteractionContent ? (
<>
@ -863,23 +1129,19 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</>
) : null;
const trackOrDetectModal: JSX.Element | null =
showDetectionContent || showTrackingContent ? (
<Modal
title='Making a server request'
zIndex={Number.MAX_SAFE_INTEGER}
visible
destroyOnClose
closable={false}
footer={[]}
>
<Text>Waiting for a server response..</Text>
<LoadingOutlined style={{ marginLeft: '10px' }} />
{showTrackingContent ? (
<Progress percent={formattedTrackingProgress as number} status='active' />
) : null}
</Modal>
) : null;
const detectionContent: JSX.Element | null = showDetectionContent ? (
<Modal
title='Making a server request'
zIndex={Number.MAX_SAFE_INTEGER}
visible
destroyOnClose
closable={false}
footer={[]}
>
<Text>Waiting for a server response..</Text>
<LoadingOutlined style={{ marginLeft: '10px' }} />
</Modal>
) : null;
return showAnyContent ? (
<>
@ -887,7 +1149,8 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
<Icon {...dynamicIconProps} component={AIToolsIcon} />
</CustomPopover>
{interactionContent}
{trackOrDetectModal}
{detectionContent}
{portals}
</>
) : (
<Icon className=' cvat-tools-control cvat-disabled-canvas-control' component={AIToolsIcon} />

@ -43,7 +43,6 @@ interface Props {
toBackground(): void;
toForeground(): void;
resetCuboidPerspective(): void;
activateTracking(): void;
}
function ItemTopComponent(props: Props): JSX.Element {
@ -76,7 +75,6 @@ function ItemTopComponent(props: Props): JSX.Element {
toBackground,
toForeground,
resetCuboidPerspective,
activateTracking,
jobInstance,
} = props;
@ -152,7 +150,6 @@ function ItemTopComponent(props: Props): JSX.Element {
toForeground,
resetCuboidPerspective,
changeColorPickerVisible,
activateTracking,
})}
>
<MoreOutlined />

@ -7,12 +7,7 @@ import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Icon, {
LinkOutlined,
CopyOutlined,
BlockOutlined,
GatewayOutlined,
RetweetOutlined,
DeleteOutlined,
LinkOutlined, CopyOutlined, BlockOutlined, RetweetOutlined, DeleteOutlined,
} from '@ant-design/icons';
import {
@ -50,7 +45,6 @@ interface Props {
toForeground(): void;
resetCuboidPerspective(): void;
changeColorPickerVisible(visible: boolean): void;
activateTracking(): void;
jobInstance: any;
}
@ -98,20 +92,6 @@ function PropagateItem(props: ItemProps): JSX.Element {
);
}
function TrackingItem(props: ItemProps): JSX.Element {
const { toolProps, ...rest } = props;
const { activateTracking } = toolProps;
return (
<Menu.Item {...rest}>
<CVATTooltip title='Run tracking with the active tracker'>
<Button type='link' icon={<GatewayOutlined />} onClick={activateTracking}>
Track
</Button>
</CVATTooltip>
</Menu.Item>
);
}
function SwitchOrientationItem(props: ItemProps): JSX.Element {
const { toolProps, ...rest } = props;
const { switchOrientation } = toolProps;
@ -233,24 +213,41 @@ export default function ItemMenu(props: Props): JSX.Element {
readonly, shapeType, objectType, colorBy, jobInstance,
} = props;
enum MenuKeys {
CREATE_URL = 'create_url',
COPY = 'copy',
PROPAGATE = 'propagate',
SWITCH_ORIENTATION = 'switch_orientation',
RESET_PERSPECIVE = 'reset_perspective',
TO_BACKGROUND = 'to_background',
TO_FOREGROUND = 'to_foreground',
SWITCH_COLOR = 'switch_color',
REMOVE_ITEM = 'remove_item',
}
const is2D = jobInstance.task.dimension === DimensionType.DIM_2D;
return (
<Menu className='cvat-object-item-menu' selectable={false}>
<CreateURLItem toolProps={props} />
{!readonly && <MakeCopyItem toolProps={props} />}
{!readonly && <PropagateItem toolProps={props} />}
{is2D && !readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && (
<TrackingItem toolProps={props} />
)}
<CreateURLItem key={MenuKeys.CREATE_URL} toolProps={props} />
{!readonly && <MakeCopyItem key={MenuKeys.COPY} toolProps={props} />}
{!readonly && <PropagateItem key={MenuKeys.PROPAGATE} toolProps={props} />}
{is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
<SwitchOrientationItem toolProps={props} />
<SwitchOrientationItem key={MenuKeys.SWITCH_ORIENTATION} toolProps={props} />
)}
{is2D && !readonly && shapeType === ShapeType.CUBOID && (
<ResetPerspectiveItem key={MenuKeys.RESET_PERSPECIVE} toolProps={props} />
)}
{is2D && objectType !== ObjectType.TAG && (
<ToBackgroundItem key={MenuKeys.TO_BACKGROUND} toolProps={props} />
)}
{is2D && !readonly && objectType !== ObjectType.TAG && (
<ToForegroundItem key={MenuKeys.TO_FOREGROUND} toolProps={props} />
)}
{[ColorBy.INSTANCE, ColorBy.GROUP].includes(colorBy) && (
<SwitchColorItem key={MenuKeys.SWITCH_COLOR} toolProps={props} />
)}
{is2D && !readonly && shapeType === ShapeType.CUBOID && <ResetPerspectiveItem toolProps={props} />}
{is2D && objectType !== ObjectType.TAG && <ToBackgroundItem toolProps={props} />}
{is2D && !readonly && objectType !== ObjectType.TAG && <ToForegroundItem toolProps={props} />}
{[ColorBy.INSTANCE, ColorBy.GROUP].includes(colorBy) && <SwitchColorItem toolProps={props} />}
{!readonly && <RemoveItem toolProps={props} />}
{!readonly && <RemoveItem key={MenuKeys.REMOVE_ITEM} toolProps={props} />}
</Menu>
);
}

@ -39,7 +39,6 @@ interface Props {
changeColor(color: string): void;
collapse(): void;
resetCuboidPerspective(): void;
activateTracking(): void;
}
function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean {
@ -92,7 +91,6 @@ function ObjectItemComponent(props: Props): JSX.Element {
changeColor,
collapse,
resetCuboidPerspective,
activateTracking,
jobInstance,
} = props;
@ -144,7 +142,6 @@ function ObjectItemComponent(props: Props): JSX.Element {
toBackground={toBackground}
toForeground={toForeground}
resetCuboidPerspective={resetCuboidPerspective}
activateTracking={activateTracking}
/>
<ObjectButtonsContainer readonly={readonly} clientID={clientID} />
{!!attributes.length && (

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -318,7 +318,7 @@
background: rgb(25, 184, 14);
height: 80%;
width: 90%;
border-radius: $grid-unit-size / 2;
border-radius: $grid-unit-size * 0.5;
}
.cvat-label-item-setup-shortcut-button {
@ -415,7 +415,7 @@
border-radius: 6px;
border: 1px solid $border-color-3;
z-index: 100;
padding: $grid-unit-size / 2 $grid-unit-size * 2 $grid-unit-size / 2 $grid-unit-size / 2;
padding: $grid-unit-size * 0.5 $grid-unit-size * 2 $grid-unit-size * 0.5 $grid-unit-size * 0.5;
.ant-slider-mark {
position: static;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save