Reformatted (#2349)

main
Vitaliy Nishukov 5 years ago committed by GitHub
parent 534ad3940c
commit 7512fd6883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,9 @@
.*/
3rdparty/
node_modules/
dist/
data/
datumaro/
keys/
logs/
static/

@ -1,53 +1,23 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
"env": { env: {
"node": false, node: false,
"browser": true, browser: true,
"es6": true, es6: true,
"jquery": true, jquery: true,
"qunit": true, qunit: true,
'jest/globals': true,
'cypress/globals': true,
}, },
"parserOptions": { parserOptions: {
"sourceType": "script", sourceType: 'script',
}, },
"plugins": [ plugins: ['requirejs', 'jest', 'cypress', 'eslint-plugin-header'],
"security", extends: ['eslint:recommended', 'plugin:requirejs/recommended', 'prettier'],
"no-unsanitized", rules: {
"no-unsafe-innerhtml", 'header/header': [2, '.header.js'],
],
"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-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],
// recently added to airbnb
"max-classes-per-file": [0],
// it was opposite before and our code has been written according to previous rule
"arrow-parens": [0],
// object spread is a modern ECMA standard. Let's do not use it without babel
"prefer-object-spread": [0],
}, },
}; };

@ -0,0 +1,9 @@
.*/
3rdparty/
node_modules/
dist/
data/
datumaro/
keys/
logs/
static/

@ -15,5 +15,13 @@
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "all", "trailingComma": "all",
"useTabs": false, "useTabs": false,
"vueIndentScriptAndStyle": false "vueIndentScriptAndStyle": false,
"overrides": [
{
"files": ["*.json", "*.yml", "*.yaml", "*.md"],
"options": {
"tabWidth": 2
}
}
]
} }

@ -1,4 +1,4 @@
exports.settings = {bullet: '*', paddedTable: false} exports.settings = { bullet: '*', paddedTable: false };
exports.plugins = [ exports.plugins = [
'remark-preset-lint-recommended', 'remark-preset-lint-recommended',
@ -7,10 +7,10 @@ exports.plugins = [
['remark-lint-no-dead-urls', { skipOffline: true }], ['remark-lint-no-dead-urls', { skipOffline: true }],
['remark-lint-maximum-line-length', 120], ['remark-lint-maximum-line-length', 120],
['remark-lint-maximum-heading-length', 120], ['remark-lint-maximum-heading-length', 120],
['remark-lint-strong-marker', "*"], ['remark-lint-strong-marker', '*'],
['remark-lint-emphasis-marker', "_"], ['remark-lint-emphasis-marker', '_'],
['remark-lint-unordered-list-marker-style', "-"], ['remark-lint-unordered-list-marker-style', '-'],
['remark-lint-ordered-list-marker-style', "."], ['remark-lint-ordered-list-marker-style', '.'],
['remark-lint-no-file-name-irregular-characters', false], ['remark-lint-no-file-name-irregular-characters', false],
['remark-lint-list-item-spacing', false], ['remark-lint-list-item-spacing', false],
] ];

@ -5,16 +5,18 @@
"value-keyword-case": null, "value-keyword-case": null,
"selector-combinator-space-after": null, "selector-combinator-space-after": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"at-rule-no-unknown": [true, { "at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["extend"] "ignoreAtRules": ["extend"]
}], }
"selector-type-no-unknown": [true, { ],
"selector-type-no-unknown": [
true,
{
"ignoreTypes": ["first-child"] "ignoreTypes": ["first-child"]
}] }
},
"ignoreFiles": [
"**/*.js",
"**/*.ts",
"**/*.py"
] ]
},
"ignoreFiles": ["**/*.js", "**/*.ts", "**/*.py"]
} }

@ -1,7 +1,7 @@
language: python language: python
python: python:
- "3.5" - '3.5'
cache: cache:
npm: true npm: true
@ -9,7 +9,7 @@ cache:
- ~/.cache - ~/.cache
addons: addons:
firefox: "latest" firefox: 'latest'
chrome: stable chrome: stable
apt: apt:
packages: packages:

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.2.0] - Unreleased ## [1.2.0] - Unreleased
### Added ### Added
- Removed Z-Order flag from task creation process - Removed Z-Order flag from task creation process
- Ability to login into CVAT-UI with token from api/v1/auth/login (<https://github.com/openvinotoolkit/cvat/pull/2234>) - Ability to login into CVAT-UI with token from api/v1/auth/login (<https://github.com/openvinotoolkit/cvat/pull/2234>)
- Added layout grids toggling ('ctrl + alt + Enter') - Added layout grids toggling ('ctrl + alt + Enter')
@ -30,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>) - MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>)
### Changed ### Changed
- UI models (like DEXTR) were redesigned to be more interactive (<https://github.com/opencv/cvat/pull/2054>) - UI models (like DEXTR) were redesigned to be more interactive (<https://github.com/opencv/cvat/pull/2054>)
- Used Ubuntu:20.04 as a base image for CVAT Dockerfile (<https://github.com/opencv/cvat/pull/2101>) - Used Ubuntu:20.04 as a base image for CVAT Dockerfile (<https://github.com/opencv/cvat/pull/2101>)
- Right colors of label tags in label mapping when a user runs automatic detection (<https://github.com/openvinotoolkit/cvat/pull/2162>) - Right colors of label tags in label mapping when a user runs automatic detection (<https://github.com/openvinotoolkit/cvat/pull/2162>)
@ -48,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- -
### Fixed ### Fixed
- Fixed multiple errors which arises when polygon is of length 5 or less (<https://github.com/opencv/cvat/pull/2100>) - Fixed multiple errors which arises when polygon is of length 5 or less (<https://github.com/opencv/cvat/pull/2100>)
- Fixed task creation from PDF (<https://github.com/opencv/cvat/pull/2141>) - Fixed task creation from PDF (<https://github.com/opencv/cvat/pull/2141>)
- Fixed CVAT format import for frame stepped tasks (<https://github.com/openvinotoolkit/cvat/pull/2151>) - Fixed CVAT format import for frame stepped tasks (<https://github.com/openvinotoolkit/cvat/pull/2151>)

@ -9,12 +9,15 @@ they should reciprocate that respect in addressing your issue or assessing
patches and features. patches and features.
## Development environment ## Development environment
- Install necessary dependencies: - Install necessary dependencies:
Ubuntu 18.04 Ubuntu 18.04
```sh ```sh
sudo apt-get update && sudo apt-get --no-install-recommends install -y build-essential curl redis-server python3-dev python3-pip python3-venv python3-tk libldap2-dev libsasl2-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev sudo apt-get update && sudo apt-get --no-install-recommends install -y build-essential curl redis-server python3-dev python3-pip python3-venv python3-tk libldap2-dev libsasl2-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev
``` ```
```sh ```sh
# Node and npm (you can use default versions of these packages from apt (8.*, 3.*), but we would recommend to use newer versions) # Node and npm (you can use default versions of these packages from apt (8.*, 3.*), but we would recommend to use newer versions)
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
@ -22,16 +25,18 @@ patches and features.
``` ```
MacOS 10.15 MacOS 10.15
```sh ```sh
brew install git python pyenv redis curl openssl node brew install git python pyenv redis curl openssl node
``` ```
- Install FFmpeg libraries (libav*) version 4.0 or higher. - Install FFmpeg libraries (libav\*) version 4.0 or higher.
- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) - Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions)
for development for development
- Install CVAT on your local host: - Install CVAT on your local host:
```sh ```sh
git clone https://github.com/opencv/cvat git clone https://github.com/opencv/cvat
cd cvat && mkdir logs keys cd cvat && mkdir logs keys
@ -42,13 +47,15 @@ for development
python manage.py migrate python manage.py migrate
python manage.py collectstatic python manage.py collectstatic
``` ```
> Note for Mac users > Note for Mac users
> >
> If you have any problems with installing dependencies from > If you have any problems with installing dependencies from
> ```cvat/requirements/*.txt```, you may need to reinstall your system python > `cvat/requirements/*.txt`, you may need to reinstall your system python
> In some cases after system update it can be configured incorrectly and cannot compile some native modules > In some cases after system update it can be configured incorrectly and cannot compile some native modules
- Create a super user for CVAT: - Create a super user for CVAT:
```sh ```sh
$ python manage.py createsuperuser $ python manage.py createsuperuser
Username (leave blank to use 'django'): *** Username (leave blank to use 'django'): ***
@ -58,25 +65,29 @@ for development
``` ```
- Install npm packages for UI and start UI debug server (run the following command from CVAT root directory): - Install npm packages for UI and start UI debug server (run the following command from CVAT root directory):
```sh ```sh
npm install && \ npm install && \
cd cvat-core && npm install && \ cd cvat-core && npm install && \
cd ../cvat-ui && npm install && npm start cd ../cvat-ui && npm install && npm start
``` ```
> Note for Mac users > Note for Mac users
> >
> If you faced with error > If you faced with error
> >
> ```Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (57)``` > `Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (57)`
> >
> Read this article [Node Sass does not yet support your current environment](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) > Read this article [Node Sass does not yet support your current environment](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- Open new terminal (Ctrl + Shift + T), run Visual Studio Code from the virtual environment - Open new terminal (Ctrl + Shift + T), run Visual Studio Code from the virtual environment
```sh ```sh
cd .. && source .env/bin/activate && code cd .. && source .env/bin/activate && code
``` ```
- Install following VS Code extensions: - Install following VS Code extensions:
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) - [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
@ -100,19 +111,21 @@ You develop CVAT under WSL (Windows subsystem for Linux) following next steps.
- Following this guide install Ubuntu 18.04 Linux distribution for WSL. - Following this guide install Ubuntu 18.04 Linux distribution for WSL.
- Run Ubuntu using start menu link or execute next command - Run Ubuntu using start menu link or execute next command
```powershell ```powershell
wsl -d Ubuntu-18.04 wsl -d Ubuntu-18.04
``` ```
- Run all commands from this isntallation guide in WSL Ubuntu shell. - Run all commands from this isntallation guide in WSL Ubuntu shell.
## Setup additional components in development environment ## Setup additional components in development environment
### DL models as serverless functions ### DL models as serverless functions
Install [nuclio platform](https://github.com/nuclio/nuclio): Install [nuclio platform](https://github.com/nuclio/nuclio):
- You have to install `nuctl` command line tool to build and deploy serverless - You have to install `nuctl` command line tool to build and deploy serverless
functions. Download [the latest release]( functions. Download [the latest release](https://github.com/nuclio/nuclio/blob/development/docs/reference/nuctl/nuctl.md#download).
https://github.com/nuclio/nuclio/blob/development/docs/reference/nuctl/nuctl.md#download).
- The simplest way to explore Nuclio is to run its graphical user interface (GUI) - The simplest way to explore Nuclio is to run its graphical user interface (GUI)
of the Nuclio dashboard. All you need in order to run the dashboard is Docker. See of the Nuclio dashboard. All you need in order to run the dashboard is Docker. See
[nuclio documentation](https://github.com/nuclio/nuclio#quick-start-steps) [nuclio documentation](https://github.com/nuclio/nuclio#quick-start-steps)
@ -238,6 +251,7 @@ Server = nuclio
} }
] ]
``` ```
</details> </details>
### Run Cypress tests ### Run Cypress tests
- Install Сypress as described in the [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress.html). - Install Сypress as described in the [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress.html).
@ -278,6 +292,7 @@ requests](#pull-requests), but please respect the following restrictions:
respect the opinions of others. respect the opinions of others.
<a name="bugs"></a> <a name="bugs"></a>
## Bug reports ## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository. A bug is a _demonstrable problem_ that is caused by the code in the repository.
@ -316,6 +331,7 @@ Example:
> merits). > merits).
<a name="features"></a> <a name="features"></a>
## Feature requests ## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea Feature requests are welcome. But take a moment to find out whether your idea
@ -324,6 +340,7 @@ case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible. provide as much detail and context as possible.
<a name="pull-requests"></a> <a name="pull-requests"></a>
## Pull requests ## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic Good pull requests - patches, improvements, new features - are a fantastic

@ -47,7 +47,7 @@ framework allows additional dataset transformations
via its command line tool and Python library. via its command line tool and Python library.
| Annotation format | Import | Export | | Annotation format | Import | Export |
| ------------------------------------------------------------------------------------------ | ------ | ------ | | ----------------------------------------------------------------------------- | ------ | ------ |
| [CVAT for images](cvat/apps/documentation/xml_format.md#annotation) | X | X | | [CVAT for images](cvat/apps/documentation/xml_format.md#annotation) | X | X |
| [CVAT for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X | | [CVAT for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X |
| [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X | | [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X |
@ -79,19 +79,21 @@ Try it online without local installation. Only own or assigned tasks
are visible to users. are visible to users.
Disabled features: Disabled features:
- [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md)
Limitations: Limitations:
- No more than 10 tasks per user - No more than 10 tasks per user
- Uploaded data is limited to 500Mb - Uploaded data is limited to 500Mb
## REST API ## REST API
Automatically generated Swagger documentation for Django REST API is Automatically generated Swagger documentation for Django REST API is
available on ``<cvat_origin>/api/swagger`` available on `<cvat_origin>/api/swagger`
(default: ``localhost:8080/api/swagger``). (default: `localhost:8080/api/swagger`).
Swagger documentation is visiable on allowed hostes, Update environement variable in docker-compose.yml file with cvat hosted machine IP or domain name. Example - ``ALLOWED_HOSTS: 'localhost, 127.0.0.1'``) Swagger documentation is visiable on allowed hostes, Update environement variable in docker-compose.yml file with cvat hosted machine IP or domain name. Example - `ALLOWED_HOSTS: 'localhost, 127.0.0.1'`)
## LICENSE ## LICENSE
@ -105,16 +107,18 @@ contributors and other users.
However, if you have a feature request or a bug report that can reproduced, However, if you have a feature request or a bug report that can reproduced,
feel free to open an issue (with steps to reproduce the bug if it's a bug feel free to open an issue (with steps to reproduce the bug if it's a bug
report) on [GitHub* issues](https://github.com/opencv/cvat/issues). report) on [GitHub\* issues](https://github.com/opencv/cvat/issues).
If you are not sure or just want to browse other users common questions, If you are not sure or just want to browse other users common questions,
[Gitter chat](https://gitter.im/opencv-cvat) is the way to go. [Gitter chat](https://gitter.im/opencv-cvat) is the way to go.
Other ways to ask questions and get our support: Other ways to ask questions and get our support:
* [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow*
* [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision) - [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow\*
- [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision)
## Links ## Links
- [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) - [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat)
- [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation) - [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation)
- [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/) - [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/)

@ -5,12 +5,14 @@
It is possible to proxy annotation logs from client to ELK. To do that run the following command below: It is possible to proxy annotation logs from client to ELK. To do that run the following command below:
### Build docker image ### Build docker image
```bash ```bash
# From project root directory # From project root directory
docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml build docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml build
``` ```
### Run docker container ### Run docker container
```bash ```bash
# From project root directory # From project root directory
docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml up -d docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml up -d
@ -19,6 +21,7 @@ docker-compose -f docker-compose.yml -f components/analytics/docker-compose.anal
At the moment it is not possible to save advanced settings. Below values should be specified manually. At the moment it is not possible to save advanced settings. Below values should be specified manually.
## Time picker default ## Time picker default
{ {
"from": "now/d", "from": "now/d",
"to": "now/d", "to": "now/d",

@ -35,9 +35,24 @@ services:
volumes: ['./components/analytics/kibana:/home/django/kibana:ro'] volumes: ['./components/analytics/kibana:/home/django/kibana:ro']
depends_on: ['cvat'] depends_on: ['cvat']
working_dir: '/home/django' working_dir: '/home/django'
entrypoint: ['bash', 'wait-for-it.sh', 'elasticsearch:9200', '-t', '0', '--', entrypoint:
'/bin/bash', 'wait-for-it.sh', 'kibana:5601', '-t', '0', '--', [
'/usr/bin/python3', 'kibana/setup.py', 'kibana/export.json'] 'bash',
'wait-for-it.sh',
'elasticsearch:9200',
'-t',
'0',
'--',
'/bin/bash',
'wait-for-it.sh',
'kibana:5601',
'-t',
'0',
'--',
'/usr/bin/python3',
'kibana/setup.py',
'kibana/export.json',
]
environment: environment:
no_proxy: elasticsearch,kibana,${no_proxy} no_proxy: elasticsearch,kibana,${no_proxy}

@ -1,3 +1,3 @@
http.host: 0.0.0.0 http.host: 0.0.0.0
script.painless.regex.enabled: true script.painless.regex.enabled: true
path.repo: ["/usr/share/elasticsearch/data/backup"] path.repo: ['/usr/share/elasticsearch/data/backup']

@ -40,19 +40,11 @@
"_type": "search", "_type": "search",
"_source": { "_source": {
"hits": 0, "hits": 0,
"sort": [ "sort": ["@timestamp", "desc"],
"@timestamp",
"desc"
],
"kibanaSavedObjectMeta": { "kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"index\":\"ec510550-c238-11e8-8e1b-758ef07f6de8\",\"highlightAll\":true,\"version\":true,\"query\":{\"language\":\"lucene\",\"query\":\"event:\\\"Send exception\\\"\"},\"filter\":[]}" "searchSourceJSON": "{\"index\":\"ec510550-c238-11e8-8e1b-758ef07f6de8\",\"highlightAll\":true,\"version\":true,\"query\":{\"language\":\"lucene\",\"query\":\"event:\\\"Send exception\\\"\"},\"filter\":[]}"
}, },
"columns": [ "columns": ["task", "type", "userid", "stack"],
"task",
"type",
"userid",
"stack"
],
"description": "", "description": "",
"title": "Table with exceptions", "title": "Table with exceptions",
"version": 1 "version": 1

@ -1,5 +1,5 @@
server.host: 0.0.0.0 server.host: 0.0.0.0
elasticsearch.url: http://elasticsearch:9200 elasticsearch.url: http://elasticsearch:9200
elasticsearch.requestHeadersWhitelist: [ "cookie", "authorization", "x-forwarded-user" ] elasticsearch.requestHeadersWhitelist: ['cookie', 'authorization', 'x-forwarded-user']
kibana.defaultAppId: "discover" kibana.defaultAppId: 'discover'
server.basePath: /analytics server.basePath: /analytics

@ -1,6 +1,7 @@
## Serverless for Computer Vision Annotation Tool (CVAT) ## Serverless for Computer Vision Annotation Tool (CVAT)
### Run docker container ### Run docker container
```bash ```bash
# From project root directory # From project root directory
docker-compose -f docker-compose.yml -f components/serverless/docker-compose.serverless.yml up -d docker-compose -f docker-compose.yml -f components/serverless/docker-compose.serverless.yml up -d

@ -15,9 +15,9 @@ services:
http_proxy: http_proxy:
https_proxy: https_proxy:
no_proxy: 172.28.0.1,${no_proxy} no_proxy: 172.28.0.1,${no_proxy}
NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: "true" NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: 'true'
ports: ports:
- "8070:8070" - '8070:8070'
cvat: cvat:
environment: environment:

@ -1,39 +1,36 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
'env': { env: {
'node': true, node: true,
'browser': true, browser: true,
'es6': true, es6: true,
}, },
'parserOptions': { parserOptions: {
'parser': '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
'ecmaVersion': 6, ecmaVersion: 6,
}, },
'plugins': [ plugins: ['@typescript-eslint', 'import'],
'@typescript-eslint', extends: [
'import',
],
'extends': [
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'airbnb-typescript/base', 'airbnb-typescript/base',
'plugin:import/errors', 'plugin:import/errors',
'plugin:import/warnings', 'plugin:import/warnings',
'plugin:import/typescript', 'plugin:import/typescript',
], ],
'rules': { rules: {
'@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['warn', 4], '@typescript-eslint/indent': ['warn', 4],
'no-plusplus': 0, 'no-plusplus': 0,
'no-restricted-syntax': [ 'no-restricted-syntax': [
0, 0,
{ {
'selector': 'ForOfStatement' selector: 'ForOfStatement',
} },
], ],
'max-len': ['error', { code: 120 }],
'no-continue': 0, 'no-continue': 0,
'func-names': 0, 'func-names': 0,
'no-console': 0, // this rule deprecates console.log, console.warn etc. because 'it is not good in production code' 'no-console': 0, // this rule deprecates console.log, console.warn etc. because 'it is not good in production code'
@ -41,10 +38,10 @@ module.exports = {
'import/prefer-default-export': 0, // works incorrect with interfaces 'import/prefer-default-export': 0, // works incorrect with interfaces
'newline-per-chained-call': 0, // makes code uglier 'newline-per-chained-call': 0, // makes code uglier
}, },
'settings': { settings: {
'import/resolver': { 'import/resolver': {
'node': { node: {
'extensions': ['.ts', '.js', '.json'], extensions: ['.ts', '.js', '.json'],
}, },
}, },
}, },

@ -1,18 +1,21 @@
# Module CVAT-CANVAS # Module CVAT-CANVAS
## Description ## Description
The CVAT module written in TypeScript language. The CVAT module written in TypeScript language.
It presents a canvas to viewing, drawing and editing of annotations. It presents a canvas to viewing, drawing and editing of annotations.
## Versioning ## Versioning
If you make changes in this package, please do following: If you make changes in this package, please do following:
- After not important changes (typos, backward compatible bug fixes, refactoring) do: ``npm version patch`` - After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch`
- After changing API (backward compatible new features) do: ``npm version minor`` - After changing API (backward compatible new features) do: `npm version minor`
- After changing API (changes that break backward compatibility) do: ``npm version major`` - After changing API (changes that break backward compatibility) do: `npm version major`
## Commands ## Commands
- Building of the module from sources in the ```dist``` directory:
- Building of the module from sources in the `dist` directory:
```bash ```bash
npm run build npm run build
@ -22,6 +25,7 @@ npm run build -- --mode=development # without a minification
## Using ## Using
Canvas itself handles: Canvas itself handles:
- Shape context menu (PKM) - Shape context menu (PKM)
- Image moving (mousedrag) - Image moving (mousedrag)
- Image resizing (mousewheel) - Image resizing (mousewheel)
@ -136,22 +140,23 @@ Canvas itself handles:
### API CSS ### API CSS
- All drawn objects (shapes, tracks) have an id ```cvat_canvas_shape_{objectState.clientID}``` - All drawn objects (shapes, tracks) have an id `cvat_canvas_shape_{objectState.clientID}`
- Drawn shapes and tracks have classes ```cvat_canvas_shape```, - Drawn shapes and tracks have classes `cvat_canvas_shape`,
```cvat_canvas_shape_activated```, `cvat_canvas_shape_activated`,
```cvat_canvas_shape_grouping```, `cvat_canvas_shape_grouping`,
```cvat_canvas_shape_merging```, `cvat_canvas_shape_merging`,
```cvat_canvas_shape_drawing```, `cvat_canvas_shape_drawing`,
```cvat_canvas_shape_occluded``` `cvat_canvas_shape_occluded`
- Drawn texts have the class ```cvat_canvas_text``` - Drawn texts have the class `cvat_canvas_text`
- Tags have the class ```cvat_canvas_tag``` - Tags have the class `cvat_canvas_tag`
- Canvas image has ID ```cvat_canvas_image``` - Canvas image has ID `cvat_canvas_image`
- Grid on the canvas has ID ```cvat_canvas_grid``` and ```cvat_canvas_grid_pattern``` - Grid on the canvas has ID `cvat_canvas_grid` and `cvat_canvas_grid_pattern`
- Crosshair during a draw has class ```cvat_canvas_crosshair``` - Crosshair during a draw has class `cvat_canvas_crosshair`
### Events ### Events
Standard JS events are used. Standard JS events are used.
```js ```js
- canvas.setup - canvas.setup
- canvas.activated => {state: ObjectState} - canvas.activated => {state: ObjectState}
@ -178,6 +183,7 @@ Standard JS events are used.
``` ```
### WEB ### WEB
```js ```js
// Create an instance of a canvas // Create an instance of a canvas
const canvas = new window.canvas.Canvas(); const canvas = new window.canvas.Canvas();
@ -202,7 +208,7 @@ Standard JS events are used.
## API Reaction ## API Reaction
| | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS | INTERACT | | | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS | INTERACT |
|--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------|----------| | ------------ | ---- | ----- | ----- | ---- | ----- | ---- | ---- | ------ | ----------- | ----------- | -------- |
| setup() | + | + | + | +/- | + | +/- | +/- | +/- | + | + | + | | setup() | + | + | + | +/- | + | +/- | +/- | +/- | + | + | + |
| activate() | + | - | - | - | - | - | - | - | - | - | - | | activate() | + | - | - | - | - | - | - | - | - | - | - |
| rotate() | + | + | + | + | + | + | + | + | + | + | + | | rotate() | + | + | + | + | + | + | + | + | + | + | + |

@ -1,8 +1,7 @@
// Copyright (C) 2019-2020 Intel Corporation // Copyright (C) 2020 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/* eslint-disable */
module.exports = { module.exports = {
parser: false, parser: false,
plugins: { plugins: {

@ -41,7 +41,7 @@ polyline.cvat_shape_drawing_opacity {
font-size: 1.2em; font-size: 1.2em;
fill: white; fill: white;
cursor: default; cursor: default;
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; font-family: Calibri, Candara, Segoe, 'Segoe UI', Optima, Arial, sans-serif;
text-shadow: 0 0 4px black; text-shadow: 0 0 4px black;
user-select: none; user-select: none;
pointer-events: none; pointer-events: none;

@ -27,10 +27,16 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
private groups: SVGGElement[]; private groups: SVGGElement[];
private auxiliaryGroupID: number | null; private auxiliaryGroupID: number | null;
private auxiliaryClicks: number[]; private auxiliaryClicks: number[];
private listeners: Record<number, Record<number, { private listeners: Record<
number,
Record<
number,
{
click: (event: MouseEvent) => void; click: (event: MouseEvent) => void;
dblclick: (event: MouseEvent) => void; dblclick: (event: MouseEvent) => void;
}>>; }
>
>;
public constructor(frameContent: SVGSVGElement) { public constructor(frameContent: SVGSVGElement) {
this.frameContent = frameContent; this.frameContent = frameContent;
@ -47,8 +53,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
private removeMarkers(): void { private removeMarkers(): void {
this.groups.forEach((group: SVGGElement): void => { this.groups.forEach((group: SVGGElement): void => {
const groupID = group.dataset.groupId; const groupID = group.dataset.groupId;
Array.from(group.children) Array.from(group.children).forEach((circle: SVGCircleElement, pointID: number): void => {
.forEach((circle: SVGCircleElement, pointID: number): void => {
circle.removeEventListener('click', this.listeners[+groupID][pointID].click); circle.removeEventListener('click', this.listeners[+groupID][pointID].click);
circle.removeEventListener('dblclick', this.listeners[+groupID][pointID].click); circle.removeEventListener('dblclick', this.listeners[+groupID][pointID].click);
circle.remove(); circle.remove();
@ -89,8 +94,9 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
if (this.auxiliaryGroupID !== null) { if (this.auxiliaryGroupID !== null) {
while (this.auxiliaryClicks.length > 0) { while (this.auxiliaryClicks.length > 0) {
const resetID = this.auxiliaryClicks.pop(); const resetID = this.auxiliaryClicks.pop();
this.groups[this.auxiliaryGroupID] this.groups[this.auxiliaryGroupID].children[resetID].classList.remove(
.children[resetID].classList.remove('cvat_canvas_autoborder_point_direction'); 'cvat_canvas_autoborder_point_direction',
);
} }
} }
@ -103,15 +109,14 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
private drawMarkers(transformedShapes: TransformedShape[]): void { private drawMarkers(transformedShapes: TransformedShape[]): void {
const svgNamespace = 'http://www.w3.org/2000/svg'; const svgNamespace = 'http://www.w3.org/2000/svg';
this.groups = transformedShapes this.groups = transformedShapes.map(
.map((shape: TransformedShape, groupID: number): SVGGElement => { (shape: TransformedShape, groupID: number): SVGGElement => {
const group = document.createElementNS(svgNamespace, 'g'); const group = document.createElementNS(svgNamespace, 'g');
group.setAttribute('data-group-id', `${groupID}`); group.setAttribute('data-group-id', `${groupID}`);
this.listeners[groupID] = this.listeners[groupID] || {}; this.listeners[groupID] = this.listeners[groupID] || {};
const circles = shape.points.split(/\s/).map(( const circles = shape.points.split(/\s/).map(
point: string, pointID: number, points: string[], (point: string, pointID: number, points: string[]): SVGCircleElement => {
): SVGCircleElement => {
const [x, y] = point.split(','); const [x, y] = point.split(',');
const circle = document.createElementNS(svgNamespace, 'circle'); const circle = document.createElementNS(svgNamespace, 'circle');
@ -127,9 +132,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
event.stopPropagation(); event.stopPropagation();
// another shape was clicked // another shape was clicked
if (this.auxiliaryGroupID !== null if (this.auxiliaryGroupID !== null && this.auxiliaryGroupID !== groupID) {
&& this.auxiliaryGroupID !== groupID
) {
this.resetAuxiliaryShape(); this.resetAuxiliaryShape();
} }
@ -169,9 +172,10 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
} else { } else {
// sign defines bypass direction // sign defines bypass direction
const landmarks = this.auxiliaryClicks; const landmarks = this.auxiliaryClicks;
const sign = Math.sign(landmarks[2] - landmarks[0]) const sign =
* Math.sign(landmarks[1] - landmarks[0]) Math.sign(landmarks[2] - landmarks[0]) *
* Math.sign(landmarks[2] - landmarks[1]); Math.sign(landmarks[1] - landmarks[0]) *
Math.sign(landmarks[2] - landmarks[1]);
// go via a polygon and get vertexes // go via a polygon and get vertexes
// the first vertex has been already drawn // the first vertex has been already drawn
@ -195,7 +199,8 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
// remove the latest cursor position from drawing array // remove the latest cursor position from drawing array
for (const wayPoint of way) { for (const wayPoint of way) {
const [_x, _y] = wayPoint.split(',') const [_x, _y] = wayPoint
.split(',')
.map((coordinate: string): number => +coordinate); .map((coordinate: string): number => +coordinate);
this.addPointToCurrentShape(_x, _y); this.addPointToCurrentShape(_x, _y);
} }
@ -204,7 +209,6 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
} }
}; };
const dblclick = (event: MouseEvent): void => { const dblclick = (event: MouseEvent): void => {
event.stopPropagation(); event.stopPropagation();
}; };
@ -217,11 +221,13 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
circle.addEventListener('mousedown', this.listeners[groupID][pointID].click); circle.addEventListener('mousedown', this.listeners[groupID][pointID].click);
circle.addEventListener('dblclick', this.listeners[groupID][pointID].click); circle.addEventListener('dblclick', this.listeners[groupID][pointID].click);
return circle; return circle;
}); },
);
group.append(...circles); group.append(...circles);
return group; return group;
}); },
);
this.frameContent.append(...this.groups); this.frameContent.append(...this.groups);
} }
@ -231,9 +237,11 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.removeMarkers(); this.removeMarkers();
const currentClientID = this.currentShape.node.dataset.originClientId; const currentClientID = this.currentShape.node.dataset.originClientId;
const shapes = Array.from(this.frameContent.getElementsByClassName('cvat_canvas_shape')) const shapes = Array.from(this.frameContent.getElementsByClassName('cvat_canvas_shape')).filter(
.filter((shape: HTMLElement): boolean => +shape.getAttribute('clientID') !== this.currentID); (shape: HTMLElement): boolean => +shape.getAttribute('clientID') !== this.currentID,
const transformedShapes = shapes.map((shape: HTMLElement): TransformedShape | null => { );
const transformedShapes = shapes
.map((shape: HTMLElement): TransformedShape | null => {
const color = shape.getAttribute('fill'); const color = shape.getAttribute('fill');
const clientID = shape.getAttribute('clientID'); const clientID = shape.getAttribute('clientID');
@ -270,16 +278,13 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
color, color,
points: points.trim(), points: points.trim(),
}; };
}).filter((state: TransformedShape | null): boolean => state !== null); })
.filter((state: TransformedShape | null): boolean => state !== null);
this.drawMarkers(transformedShapes); this.drawMarkers(transformedShapes);
} }
public autoborder( public autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void {
enabled: boolean,
currentShape?: SVG.Shape,
currentID?: number,
): void {
if (enabled && !this.enabled && currentShape) { if (enabled && !this.enabled && currentShape) {
this.enabled = true; this.enabled = true;
this.currentShape = currentShape; this.currentShape = currentShape;

@ -72,10 +72,7 @@ class CanvasImpl implements Canvas {
} }
public fitCanvas(): void { public fitCanvas(): void {
this.model.fitCanvas( this.model.fitCanvas(this.view.html().clientWidth, this.view.html().clientHeight);
this.view.html().clientWidth,
this.view.html().clientHeight,
);
} }
public bitmap(enable: boolean): void { public bitmap(enable: boolean): void {

@ -43,7 +43,7 @@ export interface ActiveElement {
export enum RectDrawingMethod { export enum RectDrawingMethod {
CLASSIC = 'By 2 points', CLASSIC = 'By 2 points',
EXTREME_POINTS = 'By 4 points' EXTREME_POINTS = 'By 4 points',
} }
export enum CuboidDrawingMethod { export enum CuboidDrawingMethod {
@ -280,23 +280,23 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
public zoom(x: number, y: number, direction: number): void { public zoom(x: number, y: number, direction: number): void {
const oldScale: number = this.data.scale; const oldScale: number = this.data.scale;
const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6; const newScale: number = direction > 0 ? (oldScale * 6) / 5 : (oldScale * 5) / 6;
this.data.scale = Math.min(Math.max(newScale, FrameZoom.MIN), FrameZoom.MAX); this.data.scale = Math.min(Math.max(newScale, FrameZoom.MIN), FrameZoom.MAX);
const { angle } = this.data; const { angle } = this.data;
const mutiplier = Math.sin(angle * Math.PI / 180) + Math.cos(angle * Math.PI / 180); const mutiplier = Math.sin((angle * Math.PI) / 180) + Math.cos((angle * Math.PI) / 180);
if ((angle / 90) % 2) { if ((angle / 90) % 2) {
// 90, 270, .. // 90, 270, ..
this.data.top += mutiplier * ((x - this.data.imageSize.width / 2) this.data.top +=
* (oldScale / this.data.scale - 1)) * this.data.scale; mutiplier * ((x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1)) * this.data.scale;
this.data.left -= mutiplier * ((y - this.data.imageSize.height / 2) this.data.left -=
* (oldScale / this.data.scale - 1)) * this.data.scale; mutiplier * ((y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1)) * this.data.scale;
} else { } else {
this.data.left += mutiplier * ((x - this.data.imageSize.width / 2) this.data.left +=
* (oldScale / this.data.scale - 1)) * this.data.scale; mutiplier * ((x - this.data.imageSize.width / 2) * (oldScale / this.data.scale - 1)) * this.data.scale;
this.data.top += mutiplier * ((y - this.data.imageSize.height / 2) this.data.top +=
* (oldScale / this.data.scale - 1)) * this.data.scale; mutiplier * ((y - this.data.imageSize.height / 2) * (oldScale / this.data.scale - 1)) * this.data.scale;
} }
this.notify(UpdateReasons.IMAGE_ZOOMED); this.notify(UpdateReasons.IMAGE_ZOOMED);
@ -312,10 +312,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.canvasSize.height = height; this.data.canvasSize.height = height;
this.data.canvasSize.width = width; this.data.canvasSize.width = width;
this.data.imageOffset = Math.floor(Math.max( this.data.imageOffset = Math.floor(
this.data.canvasSize.height / FrameZoom.MIN, Math.max(this.data.canvasSize.height / FrameZoom.MIN, this.data.canvasSize.width / FrameZoom.MIN),
this.data.canvasSize.width / FrameZoom.MIN, );
));
this.notify(UpdateReasons.FITTED_CANVAS); this.notify(UpdateReasons.FITTED_CANVAS);
this.notify(UpdateReasons.OBJECTS_UPDATED); this.notify(UpdateReasons.OBJECTS_UPDATED);
@ -367,20 +366,20 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
this.data.imageID = frameData.number; this.data.imageID = frameData.number;
frameData.data( frameData
(): void => { .data((): void => {
this.data.image = null; this.data.image = null;
this.notify(UpdateReasons.IMAGE_CHANGED); this.notify(UpdateReasons.IMAGE_CHANGED);
}, })
).then((data: Image): void => { .then((data: Image): void => {
if (frameData.number !== this.data.imageID) { if (frameData.number !== this.data.imageID) {
// already another image // already another image
return; return;
} }
this.data.imageSize = { this.data.imageSize = {
height: (frameData.height as number), height: frameData.height as number,
width: (frameData.width as number), width: frameData.width as number,
}; };
this.data.image = data; this.data.image = data;
@ -388,15 +387,14 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.zLayer = zLayer; this.data.zLayer = zLayer;
this.data.objects = objectStates; this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED); this.notify(UpdateReasons.OBJECTS_UPDATED);
}).catch((exception: any): void => { })
.catch((exception: any): void => {
throw exception; throw exception;
}); });
} }
public activate(clientID: number | null, attributeID: number | null): void { public activate(clientID: number | null, attributeID: number | null): void {
if (this.data.activeElement.clientID === clientID if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) {
&& this.data.activeElement.attributeID === attributeID
) {
return; return;
} }
@ -404,9 +402,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
throw Error(`Canvas is busy. Action: ${this.data.mode}`); throw Error(`Canvas is busy. Action: ${this.data.mode}`);
} }
if (typeof (clientID) === 'number') { if (typeof clientID === 'number') {
const [state] = this.objects const [state] = this.objects.filter((_state: any): boolean => _state.clientID === clientID);
.filter((_state: any): boolean => _state.clientID === clientID);
if (!state || state.objectType === 'tag') { if (!state || state.objectType === 'tag') {
return; return;
} }
@ -422,7 +419,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
public rotate(rotationAngle: number): void { public rotate(rotationAngle: number): void {
if (this.data.angle !== rotationAngle) { if (this.data.angle !== rotationAngle) {
this.data.angle = (360 + Math.floor((rotationAngle) / 90) * 90) % 360; this.data.angle = (360 + Math.floor(rotationAngle / 90) * 90) % 360;
this.fit(); this.fit();
} }
} }
@ -452,13 +449,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
); );
} }
this.data.scale = Math.min( this.data.scale = Math.min(Math.max(this.data.scale, FrameZoom.MIN), FrameZoom.MAX);
Math.max(this.data.scale, FrameZoom.MIN),
FrameZoom.MAX,
);
this.data.top = (this.data.canvasSize.height / 2 - this.data.imageSize.height / 2); this.data.top = this.data.canvasSize.height / 2 - this.data.imageSize.height / 2;
this.data.left = (this.data.canvasSize.width / 2 - this.data.imageSize.width / 2); this.data.left = this.data.canvasSize.width / 2 - this.data.imageSize.width / 2;
this.notify(UpdateReasons.IMAGE_FITTED); this.notify(UpdateReasons.IMAGE_FITTED);
} }
@ -482,7 +476,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
throw new Error('Drawing has been already started'); throw new Error('Drawing has been already started');
} else if (!drawData.shapeType && !drawData.initialState) { } else if (!drawData.shapeType && !drawData.initialState) {
throw new Error('A shape type is not specified'); throw new Error('A shape type is not specified');
} else if (typeof (drawData.numberOfPoints) !== 'undefined') { } else if (typeof drawData.numberOfPoints !== 'undefined') {
if (drawData.shapeType === 'polygon' && drawData.numberOfPoints < 3) { if (drawData.shapeType === 'polygon' && drawData.numberOfPoints < 3) {
throw new Error('A polygon consists of at least 3 points'); throw new Error('A polygon consists of at least 3 points');
} else if (drawData.shapeType === 'polyline' && drawData.numberOfPoints < 2) { } else if (drawData.shapeType === 'polyline' && drawData.numberOfPoints < 2) {
@ -491,10 +485,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
} }
if (typeof (drawData.redraw) === 'number') { if (typeof drawData.redraw === 'number') {
const clientID = drawData.redraw; const clientID = drawData.redraw;
const [state] = this.data.objects const [state] = this.data.objects.filter((_state: any): boolean => _state.clientID === clientID);
.filter((_state: any): boolean => _state.clientID === clientID);
if (state) { if (state) {
this.data.drawData = { ...drawData }; this.data.drawData = { ...drawData };
@ -526,7 +519,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
this.data.interactionData = interactionData; this.data.interactionData = interactionData;
if (typeof (this.data.interactionData.crosshair) !== 'boolean') { if (typeof this.data.interactionData.crosshair !== 'boolean') {
this.data.interactionData.crosshair = true; this.data.interactionData.crosshair = true;
} }
@ -591,18 +584,18 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
public configure(configuration: Configuration): void { public configure(configuration: Configuration): void {
if (typeof (configuration.displayAllText) !== 'undefined') { if (typeof configuration.displayAllText !== 'undefined') {
this.data.configuration.displayAllText = configuration.displayAllText; this.data.configuration.displayAllText = configuration.displayAllText;
} }
if (typeof (configuration.showProjections) !== 'undefined') { if (typeof configuration.showProjections !== 'undefined') {
this.data.configuration.showProjections = configuration.showProjections; this.data.configuration.showProjections = configuration.showProjections;
} }
if (typeof (configuration.autoborders) !== 'undefined') { if (typeof configuration.autoborders !== 'undefined') {
this.data.configuration.autoborders = configuration.autoborders; this.data.configuration.autoborders = configuration.autoborders;
} }
if (typeof (configuration.undefinedAttrValue) !== 'undefined') { if (typeof configuration.undefinedAttrValue !== 'undefined') {
this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue;
} }
@ -610,8 +603,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
public isAbleToChangeFrame(): boolean { public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode) const isUnable =
|| (this.data.mode === Mode.DRAW && typeof (this.data.drawData.redraw) === 'number'); [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; return !isUnable;
} }
@ -647,10 +641,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.imageOffset = geometry.offset; this.data.imageOffset = geometry.offset;
this.data.scale = geometry.scale; this.data.scale = geometry.scale;
this.data.imageOffset = Math.floor(Math.max( this.data.imageOffset = Math.floor(
this.data.canvasSize.height / FrameZoom.MIN, Math.max(this.data.canvasSize.height / FrameZoom.MIN, this.data.canvasSize.width / FrameZoom.MIN),
this.data.canvasSize.width / FrameZoom.MIN, );
));
} }
public get zLayer(): number | null { public get zLayer(): number | null {
@ -667,8 +660,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
public get objects(): any[] { public get objects(): any[] {
if (this.data.zLayer !== null) { if (this.data.zLayer !== null) {
return this.data.objects return this.data.objects.filter((object: any): boolean => object.zOrder <= this.data.zLayer);
.filter((object: any): boolean => object.zOrder <= this.data.zLayer);
} }
return this.data.objects; return this.data.objects;

File diff suppressed because it is too large Load Diff

@ -14,8 +14,9 @@ const MIN_EDGE_LENGTH = 3;
const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5; const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5;
const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75; const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75;
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__'; const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' const ARROW_PATH =
+ '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'; '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';
export default { export default {
BASE_STROKE_WIDTH, BASE_STROKE_WIDTH,

@ -25,13 +25,19 @@ export default class Crosshair {
} }
this.canvas = canvas; this.canvas = canvas;
this.x = this.canvas.line(0, y, this.canvas.node.clientWidth, y).attr({ this.x = this.canvas
.line(0, y, this.canvas.node.clientWidth, y)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale), 'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
}).addClass('cvat_canvas_crosshair'); })
.addClass('cvat_canvas_crosshair');
this.y = this.canvas.line(x, 0, x, this.canvas.node.clientHeight).attr({ this.y = this.canvas
.line(x, 0, x, this.canvas.node.clientHeight)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale), 'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
}).addClass('cvat_canvas_crosshair'); })
.addClass('cvat_canvas_crosshair');
} }
public hide(): void { public hide(): void {

@ -1,12 +1,3 @@
/* eslint-disable func-names */
/* eslint-disable no-underscore-dangle */
/* eslint-disable curly */
/*
* Copyright (C) 2020 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
import consts from './consts'; import consts from './consts';
export interface Point { export interface Point {
@ -26,9 +17,7 @@ function line(p1: Point, p2: Point): number[] {
return [a, b, c]; return [a, b, c];
} }
function intersection( function intersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null {
p1: Point, p2: Point, p3: Point, p4: Point,
): Point | null {
const L1 = line(p1, p2); const L1 = line(p1, p2);
const L2 = line(p3, p4); const L2 = line(p3, p4);
@ -246,8 +235,20 @@ export class CuboidModel {
this.rb = new Edge([3, 5], this.points); this.rb = new Edge([3, 5], this.points);
this.db = new Edge([7, 5], this.points); this.db = new Edge([7, 5], this.points);
this.edgeList = [this.fl, this.fr, this.dl, this.dr, this.ft, this.lt, this.edgeList = [
this.rt, this.dt, this.fb, this.lb, this.rb, this.db]; this.fl,
this.fr,
this.dl,
this.dr,
this.ft,
this.lt,
this.rt,
this.dt,
this.fb,
this.lb,
this.rb,
this.db,
];
} }
private initFaces(): void { private initFaces(): void {
@ -347,8 +348,8 @@ function setupCuboidPoints(points: Point[]): any[] {
let p3; let p3;
let p4; let p4;
const height = Math.abs(points[0].x - points[1].x) const height =
< Math.abs(points[1].x - points[2].x) 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[0].y)
: Math.abs(points[1].y - points[2].y); : Math.abs(points[1].y - points[2].y);
@ -460,7 +461,6 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y }; plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y };
plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y }; plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y };
let cuboidPoints; let cuboidPoints;
// right // right
if (Math.abs(angle) < Math.PI / 2 - 0.1) { if (Math.abs(angle) < Math.PI / 2 - 0.1) {
@ -470,18 +470,12 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
cuboidPoints = setupCuboidPoints(points); cuboidPoints = setupCuboidPoints(points);
// down // down
} else if (angle > 0) { } else if (angle > 0) {
cuboidPoints = [ cuboidPoints = [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3, plane1.p4, plane2.p4];
plane1.p1, plane2.p1, plane1.p2, plane2.p2,
plane1.p3, plane2.p3, plane1.p4, plane2.p4,
];
cuboidPoints[0].y += 0.1; cuboidPoints[0].y += 0.1;
cuboidPoints[4].y += 0.1; cuboidPoints[4].y += 0.1;
// up // up
} else { } else {
cuboidPoints = [ cuboidPoints = [plane2.p1, plane1.p1, plane2.p2, plane1.p2, plane2.p3, plane1.p3, plane2.p4, plane1.p4];
plane2.p1, plane1.p1, plane2.p2, plane1.p2,
plane2.p3, plane1.p3, plane2.p4, plane1.p4,
];
cuboidPoints[0].y += 0.1; cuboidPoints[0].y += 0.1;
cuboidPoints[4].y += 0.1; cuboidPoints[4].y += 0.1;
} }

@ -18,13 +18,7 @@ import {
} from './shared'; } from './shared';
import Crosshair from './crosshair'; import Crosshair from './crosshair';
import consts from './consts'; import consts from './consts';
import { import { DrawData, Geometry, RectDrawingMethod, Configuration, CuboidDrawingMethod } from './canvasModel';
DrawData,
Geometry,
RectDrawingMethod,
Configuration,
CuboidDrawingMethod,
} from './canvasModel';
import { cuboidFrom4Points } from './cuboid'; import { cuboidFrom4Points } from './cuboid';
@ -64,8 +58,9 @@ export class DrawHandlerImpl implements DrawHandler {
const frameHeight = this.geometry.image.height; const frameHeight = this.geometry.image.height;
const { offset } = this.geometry; const { offset } = this.geometry;
let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height] let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height].map(
.map((coord: number): number => coord - offset); (coord: number): number => coord - offset,
);
xtl = Math.min(Math.max(xtl, 0), frameWidth); xtl = Math.min(Math.max(xtl, 0), frameWidth);
xbr = Math.min(Math.max(xbr, 0), frameWidth); xbr = Math.min(Math.max(xbr, 0), frameWidth);
@ -75,7 +70,9 @@ export class DrawHandlerImpl implements DrawHandler {
return [xtl, ytl, xbr, ybr]; return [xtl, ytl, xbr, ybr];
} }
private getFinalPolyshapeCoordinates(targetPoints: number[]): { private getFinalPolyshapeCoordinates(
targetPoints: number[],
): {
points: number[]; points: number[];
box: Box; box: Box;
} { } {
@ -106,7 +103,9 @@ export class DrawHandlerImpl implements DrawHandler {
}; };
} }
private getFinalCuboidCoordinates(targetPoints: number[]): { private getFinalCuboidCoordinates(
targetPoints: number[],
): {
points: number[]; points: number[];
box: Box; box: Box;
} { } {
@ -133,8 +132,7 @@ export class DrawHandlerImpl implements DrawHandler {
for (let i = 0; i < points.length - 1; i += 2) { for (let i = 0; i < points.length - 1; i += 2) {
const [x, y] = points.slice(i); const [x, y] = points.slice(i);
if (x >= offset && x <= offset + frameWidth if (x >= offset && x <= offset + frameWidth && y >= offset && y <= offset + frameHeight) continue;
&& y >= offset && y <= offset + frameHeight) continue;
let xOffset = 0; let xOffset = 0;
let yOffset = 0; let yOffset = 0;
@ -156,9 +154,8 @@ export class DrawHandlerImpl implements DrawHandler {
if (cuboidOffsets.length === points.length / 2) { if (cuboidOffsets.length === points.length / 2) {
cuboidOffsets.forEach((offsetCoords: number[]): void => { cuboidOffsets.forEach((offsetCoords: number[]): void => {
if (Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)) if (Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2) < minCuboidOffset.d) {
< minCuboidOffset.d) { minCuboidOffset.d = Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2);
minCuboidOffset.d = Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2));
[minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords; [minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords;
} }
}); });
@ -215,8 +212,10 @@ export class DrawHandlerImpl implements DrawHandler {
// Or when no drawn points, but we call cancel() drawing // Or when no drawn points, but we call cancel() drawing
// We check if it is activated with remember function // We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) { if (this.drawInstance.remember('_paintHandler')) {
if (this.drawData.shapeType !== 'rectangle' if (
&& this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC) { this.drawData.shapeType !== 'rectangle' &&
this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC
) {
// Check for unsaved drawn shapes // Check for unsaved drawn shapes
this.drawInstance.draw('done'); this.drawInstance.draw('done');
} }
@ -248,7 +247,8 @@ export class DrawHandlerImpl implements DrawHandler {
private drawBox(): void { private drawBox(): void {
this.drawInstance = this.canvas.rect(); this.drawInstance = this.canvas.rect();
this.drawInstance.on('drawstop', (e: Event): void => { this.drawInstance
.on('drawstop', (e: Event): void => {
const bbox = (e.target as SVGRectElement).getBBox(); const bbox = (e.target as SVGRectElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType, redraw: clientID } = this.drawData; const { shapeType, redraw: clientID } = this.drawData;
@ -256,29 +256,39 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.canceled) return; if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({ this.onDrawDone(
{
clientID, clientID,
shapeType, shapeType,
points: [xtl, ytl, xbr, ybr], points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
} }
}).on('drawupdate', (): void => { })
.on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance); this.shapeSizeElement.update(this.drawInstance);
}).addClass('cvat_canvas_shape_drawing').attr({ })
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
} }
private drawBoxBy4Points(): void { private drawBoxBy4Points(): void {
let numberOfPoints = 0; let numberOfPoints = 0;
this.drawInstance = (this.canvas as any).polygon() this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polygon()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': 0, 'stroke-width': 0,
opacity: 0, opacity: 0,
}).on('drawstart', (): void => { })
.on('drawstart', (): void => {
// init numberOfPoints as one on drawstart // init numberOfPoints as one on drawstart
numberOfPoints = 1; numberOfPoints = 1;
}).on('drawpoint', (e: CustomEvent): void => { })
.on('drawpoint', (e: CustomEvent): void => {
// increase numberOfPoints by one on drawpoint // increase numberOfPoints by one on drawpoint
numberOfPoints += 1; numberOfPoints += 1;
@ -290,14 +300,18 @@ export class DrawHandlerImpl implements DrawHandler {
this.cancel(); this.cancel();
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({ this.onDrawDone(
{
shapeType, shapeType,
clientID, clientID,
points: [xtl, ytl, xbr, ybr], points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
} }
} }
}).on('undopoint', (): void => { })
.on('undopoint', (): void => {
if (numberOfPoints > 0) { if (numberOfPoints > 0) {
numberOfPoints -= 1; numberOfPoints -= 1;
} }
@ -351,10 +365,7 @@ export class DrawHandlerImpl implements DrawHandler {
} else { } else {
this.drawInstance.draw('update', e); this.drawInstance.draw('update', e);
const deltaTreshold = 15; const deltaTreshold = 15;
const delta = Math.sqrt( const delta = Math.sqrt((e.clientX - lastDrawnPoint.x) ** 2 + (e.clientY - lastDrawnPoint.y) ** 2);
((e.clientX - lastDrawnPoint.x) ** 2)
+ ((e.clientY - lastDrawnPoint.y) ** 2),
);
if (delta > deltaTreshold) { if (delta > deltaTreshold) {
this.drawInstance.draw('point', e); this.drawInstance.draw('point', e);
} }
@ -375,50 +386,67 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawInstance.on('drawdone', (e: CustomEvent): void => { this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points')); const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points'));
const { shapeType, redraw: clientID } = this.drawData; const { shapeType, redraw: clientID } = this.drawData;
const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) const { points, box } =
shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints); : this.getFinalPolyshapeCoordinates(targetPoints);
this.release(); this.release();
if (this.canceled) return; if (this.canceled) return;
if (shapeType === 'polygon' if (
&& ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD) shapeType === 'polygon' &&
&& points.length >= 3 * 2) { (box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD &&
this.onDrawDone({ points.length >= 3 * 2
) {
this.onDrawDone(
{
clientID, clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); },
} else if (shapeType === 'polyline' Date.now() - this.startTimestamp,
&& ((box.xbr - box.xtl) >= consts.SIZE_THRESHOLD );
|| (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) } else if (
&& points.length >= 2 * 2) { shapeType === 'polyline' &&
this.onDrawDone({ (box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) &&
points.length >= 2 * 2
) {
this.onDrawDone(
{
clientID, clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); },
} else if (shapeType === 'points' Date.now() - this.startTimestamp,
&& (e.target as any).getAttribute('points') !== '0,0') { );
this.onDrawDone({ } else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') {
this.onDrawDone(
{
clientID, clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
// TODO: think about correct constraign for cuboids // TODO: think about correct constraign for cuboids
} else if (shapeType === 'cuboid' } else if (shapeType === 'cuboid' && points.length === 4 * 2) {
&& points.length === 4 * 2) { this.onDrawDone(
this.onDrawDone({ {
clientID, clientID,
shapeType, shapeType,
points: cuboidFrom4Points(points), points: cuboidFrom4Points(points),
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
} }
}); });
} }
private drawPolygon(): void { private drawPolygon(): void {
this.drawInstance = (this.canvas as any).polygon() this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polygon()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
@ -429,8 +457,10 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private drawPolyline(): void { private drawPolyline(): void {
this.drawInstance = (this.canvas as any).polyline() this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polyline()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': 0, 'fill-opacity': 0,
}); });
@ -442,8 +472,7 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private drawPoints(): void { private drawPoints(): void {
this.drawInstance = (this.canvas as any).polygon() this.drawInstance = (this.canvas as any).polygon().addClass('cvat_canvas_shape_drawing').attr({
.addClass('cvat_canvas_shape_drawing').attr({
'stroke-width': 0, 'stroke-width': 0,
opacity: 0, opacity: 0,
}); });
@ -452,8 +481,10 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private drawCuboidBy4Points(): void { private drawCuboidBy4Points(): void {
this.drawInstance = (this.canvas as any).polyline() this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polyline()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
this.drawPolyshape(); this.drawPolyshape();
@ -461,7 +492,8 @@ export class DrawHandlerImpl implements DrawHandler {
private drawCuboid(): void { private drawCuboid(): void {
this.drawInstance = this.canvas.rect(); this.drawInstance = this.canvas.rect();
this.drawInstance.on('drawstop', (e: Event): void => { this.drawInstance
.on('drawstop', (e: Event): void => {
const bbox = (e.target as SVGRectElement).getBBox(); const bbox = (e.target as SVGRectElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType } = this.drawData; const { shapeType } = this.drawData;
@ -470,14 +502,20 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.canceled) return; if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
const d = { x: (xbr - xtl) * 0.1, y: (ybr - ytl) * 0.1 }; const d = { x: (xbr - xtl) * 0.1, y: (ybr - ytl) * 0.1 };
this.onDrawDone({ this.onDrawDone(
{
shapeType, shapeType,
points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]), points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]),
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
} }
}).on('drawupdate', (): void => { })
.on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance); this.shapeSizeElement.update(this.drawInstance);
}).addClass('cvat_canvas_shape_drawing').attr({ })
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
} }
@ -489,14 +527,17 @@ export class DrawHandlerImpl implements DrawHandler {
.split(/[,\s]/g) .split(/[,\s]/g)
.map((coord: string): number => +coord); .map((coord: string): number => +coord);
const { points } = this.drawData.initialState.shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) const { points } =
this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints); : this.getFinalPolyshapeCoordinates(targetPoints);
if (!e.detail.originalEvent.ctrlKey) { if (!e.detail.originalEvent.ctrlKey) {
this.release(); this.release();
} }
this.onDrawDone({ this.onDrawDone(
{
shapeType: this.drawData.initialState.shapeType, shapeType: this.drawData.initialState.shapeType,
objectType: this.drawData.initialState.objectType, objectType: this.drawData.initialState.objectType,
points, points,
@ -504,7 +545,10 @@ export class DrawHandlerImpl implements DrawHandler {
attributes: { ...this.drawData.initialState.attributes }, attributes: { ...this.drawData.initialState.attributes },
label: this.drawData.initialState.label, label: this.drawData.initialState.label,
color: this.drawData.initialState.color, color: this.drawData.initialState.color,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); },
Date.now() - this.startTimestamp,
e.detail.originalEvent.ctrlKey,
);
}); });
} }
@ -525,9 +569,11 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private pasteBox(box: BBox): void { private pasteBox(box: BBox): void {
this.drawInstance = (this.canvas as any).rect(box.width, box.height) this.drawInstance = (this.canvas as any)
.rect(box.width, box.height)
.move(box.x, box.y) .move(box.x, box.y)
.addClass('cvat_canvas_shape_drawing').attr({ .addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
this.pasteShape(); this.pasteShape();
@ -539,7 +585,8 @@ export class DrawHandlerImpl implements DrawHandler {
this.release(); this.release();
} }
this.onDrawDone({ this.onDrawDone(
{
shapeType: this.drawData.initialState.shapeType, shapeType: this.drawData.initialState.shapeType,
objectType: this.drawData.initialState.objectType, objectType: this.drawData.initialState.objectType,
points: [xtl, ytl, xbr, ybr], points: [xtl, ytl, xbr, ybr],
@ -547,14 +594,18 @@ export class DrawHandlerImpl implements DrawHandler {
attributes: { ...this.drawData.initialState.attributes }, attributes: { ...this.drawData.initialState.attributes },
label: this.drawData.initialState.label, label: this.drawData.initialState.label,
color: this.drawData.initialState.color, color: this.drawData.initialState.color,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); },
Date.now() - this.startTimestamp,
e.detail.originalEvent.ctrlKey,
);
}); });
} }
private pastePolygon(points: string): void { private pastePolygon(points: string): void {
this.drawInstance = (this.canvas as any).polygon(points) this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polygon(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
this.pasteShape(); this.pasteShape();
@ -562,8 +613,10 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private pastePolyline(points: string): void { private pastePolyline(points: string): void {
this.drawInstance = (this.canvas as any).polyline(points) this.drawInstance = (this.canvas as any)
.addClass('cvat_canvas_shape_drawing').attr({ .polyline(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
this.pasteShape(); this.pasteShape();
@ -571,7 +624,10 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private pasteCuboid(points: string): void { private pasteCuboid(points: string): void {
this.drawInstance = (this.canvas as any).cube(points).addClass('cvat_canvas_shape_drawing').attr({ this.drawInstance = (this.canvas as any)
.cube(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'face-stroke': 'black', 'face-stroke': 'black',
}); });
@ -580,13 +636,7 @@ export class DrawHandlerImpl implements DrawHandler {
} }
private pastePoints(initialPoints: string): void { private pastePoints(initialPoints: string): void {
function moveShape( function moveShape(shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void {
shape: SVG.PolyLine,
group: SVG.G,
x: number,
y: number,
scale: number,
): void {
const bbox = shape.bbox(); const bbox = shape.bbox();
shape.move(x - bbox.width / 2, y - bbox.height / 2); shape.move(x - bbox.width / 2, y - bbox.height / 2);
@ -601,8 +651,7 @@ export class DrawHandlerImpl implements DrawHandler {
const { x: initialX, y: initialY } = this.cursorPosition; const { x: initialX, y: initialY } = this.cursorPosition;
this.pointsGroup = this.canvas.group(); this.pointsGroup = this.canvas.group();
this.drawInstance = (this.canvas as any).polyline(initialPoints) this.drawInstance = (this.canvas as any).polyline(initialPoints).addClass('cvat_canvas_shape_drawing').style({
.addClass('cvat_canvas_shape_drawing').style({
'stroke-width': 0, 'stroke-width': 0,
}); });
@ -617,15 +666,11 @@ export class DrawHandlerImpl implements DrawHandler {
}); });
} }
moveShape( moveShape(this.drawInstance, this.pointsGroup, initialX, initialY, this.geometry.scale);
this.drawInstance, this.pointsGroup, initialX, initialY, this.geometry.scale,
);
this.canvas.on('mousemove.draw', (): void => { this.canvas.on('mousemove.draw', (): void => {
const { x, y } = this.cursorPosition; // was computer in another callback const { x, y } = this.cursorPosition; // was computer in another callback
moveShape( moveShape(this.drawInstance, this.pointsGroup, x, y, this.geometry.scale);
this.drawInstance, this.pointsGroup, x, y, this.geometry.scale,
);
}); });
this.pastePolyshape(); this.pastePolyshape();
@ -659,8 +704,9 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.drawData.initialState) { if (this.drawData.initialState) {
const { offset } = this.geometry; const { offset } = this.geometry;
if (this.drawData.shapeType === 'rectangle') { if (this.drawData.shapeType === 'rectangle') {
const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points.map(
.map((coord: number): number => coord + offset); (coord: number): number => coord + offset,
);
this.pasteBox({ this.pasteBox({
x: xtl, x: xtl,
@ -669,8 +715,7 @@ export class DrawHandlerImpl implements DrawHandler {
height: ybr - ytl, height: ybr - ytl,
}); });
} else { } else {
const points = this.drawData.initialState.points const points = this.drawData.initialState.points.map((coord: number): number => coord + offset);
.map((coord: number): number => coord + offset);
const stringifiedPoints = stringifyPoints(points); const stringifiedPoints = stringifyPoints(points);
if (this.drawData.shapeType === 'polygon') { if (this.drawData.shapeType === 'polygon') {
@ -741,10 +786,7 @@ export class DrawHandlerImpl implements DrawHandler {
}; };
this.canvas.on('mousemove.crosshair', (e: MouseEvent): void => { this.canvas.on('mousemove.crosshair', (e: MouseEvent): void => {
const [x, y] = translateToSVG( const [x, y] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]);
this.canvas.node as any as SVGSVGElement,
[e.clientX, e.clientY],
);
this.cursorPosition = { x, y }; this.cursorPosition = { x, y };
if (this.crosshair) { if (this.crosshair) {
this.crosshair.move(x, y); this.crosshair.move(x, y);
@ -753,15 +795,11 @@ export class DrawHandlerImpl implements DrawHandler {
} }
public configurate(configuration: Configuration): void { public configurate(configuration: Configuration): void {
if (typeof (configuration.autoborders) === 'boolean') { if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders; this.autobordersEnabled = configuration.autoborders;
if (this.drawInstance) { if (this.drawInstance) {
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder( this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
true,
this.drawInstance,
this.drawData.redraw,
);
} else { } else {
this.autoborderHandler.autoborder(false); this.autoborderHandler.autoborder(false);
} }
@ -798,14 +836,8 @@ export class DrawHandlerImpl implements DrawHandler {
const paintHandler = this.drawInstance.remember('_paintHandler'); const paintHandler = this.drawInstance.remember('_paintHandler');
for (const point of (paintHandler as any).set.members) { for (const point of (paintHandler as any).set.members) {
point.attr( point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
'stroke-width', point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`);
`${consts.POINTS_STROKE_WIDTH / geometry.scale}`,
);
point.attr(
'r',
`${consts.BASE_POINT_SIZE / geometry.scale}`,
);
} }
} }
} }

@ -47,7 +47,8 @@ export class EditHandlerImpl implements EditHandler {
if (e.button !== 0) return; if (e.button !== 0) return;
const { offset } = this.geometry; const { offset } = this.geometry;
const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`; const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`;
const points = pointsToNumberArray(stringifiedPoints).slice(0, -2) const points = pointsToNumberArray(stringifiedPoints)
.slice(0, -2)
.map((coord: number): number => coord - offset); .map((coord: number): number => coord - offset);
if (points.length >= minimumPoints * 2) { if (points.length >= minimumPoints * 2) {
@ -63,7 +64,7 @@ export class EditHandlerImpl implements EditHandler {
private startEdit(): void { private startEdit(): void {
// get started coordinates // get started coordinates
const [clientX, clientY] = translateFromSVG( const [clientX, clientY] = translateFromSVG(
this.canvas.node as any as SVGSVGElement, (this.canvas.node as any) as SVGSVGElement,
this.editedShape.attr('points').split(' ')[this.editData.pointID].split(','), this.editedShape.attr('points').split(' ')[this.editData.pointID].split(','),
); );
@ -92,10 +93,7 @@ export class EditHandlerImpl implements EditHandler {
(this.editLine as any).draw('point', e); (this.editLine as any).draw('point', e);
} else { } else {
const deltaTreshold = 15; const deltaTreshold = 15;
const delta = Math.sqrt( const delta = Math.sqrt((e.clientX - lastDrawnPoint.x) ** 2 + (e.clientY - lastDrawnPoint.y) ** 2);
((e.clientX - lastDrawnPoint.x) ** 2)
+ ((e.clientY - lastDrawnPoint.y) ** 2),
);
if (delta > deltaTreshold) { if (delta > deltaTreshold) {
(this.editLine as any).draw('point', e); (this.editLine as any).draw('point', e);
} }
@ -112,17 +110,22 @@ export class EditHandlerImpl implements EditHandler {
} }
const strokeColor = this.editedShape.attr('stroke'); const strokeColor = this.editedShape.attr('stroke');
(this.editLine as any).addClass('cvat_canvas_shape_drawing').style({ (this.editLine as any)
.addClass('cvat_canvas_shape_drawing')
.style({
'pointer-events': 'none', 'pointer-events': 'none',
'fill-opacity': 0, 'fill-opacity': 0,
stroke: strokeColor, stroke: strokeColor,
}).attr({ })
.attr({
'data-origin-client-id': this.editData.state.clientID, 'data-origin-client-id': this.editData.state.clientID,
}).on('drawstart drawpoint', (e: CustomEvent): void => { })
.on('drawstart drawpoint', (e: CustomEvent): void => {
this.transform(this.geometry); this.transform(this.geometry);
lastDrawnPoint.x = e.detail.event.clientX; lastDrawnPoint.x = e.detail.event.clientX;
lastDrawnPoint.y = e.detail.event.clientY; lastDrawnPoint.y = e.detail.event.clientY;
}).on('drawupdate', (): void => this.transform(this.geometry)) })
.on('drawupdate', (): void => this.transform(this.geometry))
.draw(dummyEvent, { snapToGrid: 0.1 }); .draw(dummyEvent, { snapToGrid: 0.1 });
if (this.editData.state.shapeType === 'points') { if (this.editData.state.shapeType === 'points') {
@ -141,9 +144,7 @@ export class EditHandlerImpl implements EditHandler {
if (e.button === 0 && !e.altKey) { if (e.button === 0 && !e.altKey) {
(this.editLine as any).draw('point', e); (this.editLine as any).draw('point', e);
} else if (e.button === 2 && this.editLine) { } else if (e.button === 2 && this.editLine) {
if (this.editData.state.shapeType === 'points' if (this.editData.state.shapeType === 'points' || this.editLine.attr('points').split(' ').length > 2) {
|| this.editLine.attr('points').split(' ').length > 2
) {
(this.editLine as any).draw('undo'); (this.editLine as any).draw('undo');
} }
} }
@ -152,8 +153,7 @@ export class EditHandlerImpl implements EditHandler {
private selectPolygon(shape: SVG.Polygon): void { private selectPolygon(shape: SVG.Polygon): void {
const { offset } = this.geometry; const { offset } = this.geometry;
const points = pointsToNumberArray(shape.attr('points')) const points = pointsToNumberArray(shape.attr('points')).map((coord: number): number => coord - offset);
.map((coord: number): number => coord - offset);
const { state } = this.editData; const { state } = this.editData;
this.edit({ this.edit({
@ -168,8 +168,7 @@ export class EditHandlerImpl implements EditHandler {
} }
// Get stop point and all points // Get stop point and all points
const stopPointID = Array.prototype.indexOf const stopPointID = Array.prototype.indexOf.call((e.target as HTMLElement).parentElement.children, e.target);
.call((e.target as HTMLElement).parentElement.children, e.target);
const oldPoints = this.editedShape.attr('points').trim().split(' '); const oldPoints = this.editedShape.attr('points').trim().split(' ');
const linePoints = this.editLine.attr('points').trim().split(' '); const linePoints = this.editLine.attr('points').trim().split(' ');
@ -179,8 +178,7 @@ export class EditHandlerImpl implements EditHandler {
} }
// Compute new point array // Compute new point array
const [start, stop] = [this.editData.pointID, stopPointID] const [start, stop] = [this.editData.pointID, stopPointID].sort((a, b): number => +a - +b);
.sort((a, b): number => +a - +b);
if (this.editData.state.shapeType !== 'polygon') { if (this.editData.state.shapeType !== 'polygon') {
let points = null; let points = null;
@ -190,15 +188,15 @@ export class EditHandlerImpl implements EditHandler {
if (start !== this.editData.pointID) { if (start !== this.editData.pointID) {
linePoints.reverse(); linePoints.reverse();
} }
points = oldPoints.slice(0, start) points = oldPoints
.slice(0, start)
.concat(linePoints) .concat(linePoints)
.concat(oldPoints.slice(stop + 1)); .concat(oldPoints.slice(stop + 1));
} else { } else {
points = oldPoints.concat(linePoints.slice(0, -1)); points = oldPoints.concat(linePoints.slice(0, -1));
} }
points = pointsToNumberArray(points.join(' ')) points = pointsToNumberArray(points.join(' ')).map((coord: number): number => coord - offset);
.map((coord: number): number => coord - offset);
const { state } = this.editData; const { state } = this.editData;
this.edit({ this.edit({
@ -209,21 +207,23 @@ export class EditHandlerImpl implements EditHandler {
return; return;
} }
const cutIndexes1 = oldPoints.reduce((acc: string[], _: string, i: number) => const cutIndexes1 = oldPoints.reduce(
i >= stop || i <= start ? [...acc, i] : acc, []); (acc: string[], _: string, i: number) => (i >= stop || i <= start ? [...acc, i] : acc),
const cutIndexes2 = oldPoints.reduce((acc: string[], _: string, i: number) => [],
i <= stop && i >= start ? [...acc, i] : acc, []); );
const cutIndexes2 = oldPoints.reduce(
(acc: string[], _: string, i: number) => (i <= stop && i >= start ? [...acc, i] : acc),
[],
);
const curveLength = (indexes: number[]): number => { const curveLength = (indexes: number[]): number => {
const points = indexes.map((index: number): string => oldPoints[index]) const points = indexes
.map((index: number): string => oldPoints[index])
.map((point: string): string[] => point.split(',')) .map((point: string): string[] => point.split(','))
.map((point: string[]): number[] => [+point[0], +point[1]]); .map((point: string[]): number[] => [+point[0], +point[1]]);
let length = 0; let length = 0;
for (let i = 1; i < points.length; i++) { for (let i = 1; i < points.length; i++) {
length += Math.sqrt( length += Math.sqrt((points[i][0] - points[i - 1][0]) ** 2 + (points[i][1] - points[i - 1][1]) ** 2);
((points[i][0] - points[i - 1][0]) ** 2)
+ ((points[i][1] - points[i - 1][1]) ** 2),
);
} }
return length; return length;
@ -236,11 +236,11 @@ export class EditHandlerImpl implements EditHandler {
linePoints.reverse(); linePoints.reverse();
} }
const firstPart = oldPoints.slice(0, start) const firstPart = oldPoints
.slice(0, start)
.concat(linePoints) .concat(linePoints)
.concat(oldPoints.slice(stop + 1)); .concat(oldPoints.slice(stop + 1));
const secondPart = oldPoints.slice(start, stop) const secondPart = oldPoints.slice(start, stop).concat(linePoints.slice(1).reverse());
.concat(linePoints.slice(1).reverse());
if (firstPart.length < 3 || secondPart.length < 3) { if (firstPart.length < 3 || secondPart.length < 3) {
this.cancel(); this.cancel();
@ -264,17 +264,22 @@ export class EditHandlerImpl implements EditHandler {
this.selectPolygon(this.clones[0]); this.selectPolygon(this.clones[0]);
} else { } else {
for (const points of [firstPart, secondPart]) { for (const points of [firstPart, secondPart]) {
this.clones.push(this.canvas.polygon(points.join(' ')) this.clones.push(
this.canvas
.polygon(points.join(' '))
.attr('fill', this.editedShape.attr('fill')) .attr('fill', this.editedShape.attr('fill'))
.attr('fill-opacity', '0.5') .attr('fill-opacity', '0.5')
.addClass('cvat_canvas_shape')); .addClass('cvat_canvas_shape'),
);
} }
for (const clone of this.clones) { for (const clone of this.clones) {
clone.on('click', (): void => this.selectPolygon(clone)); clone.on('click', (): void => this.selectPolygon(clone));
clone.on('mouseenter', (): void => { clone
.on('mouseenter', (): void => {
clone.addClass('cvat_canvas_shape_splitting'); clone.addClass('cvat_canvas_shape_splitting');
}).on('mouseleave', (): void => { })
.on('mouseleave', (): void => {
clone.removeClass('cvat_canvas_shape_splitting'); clone.removeClass('cvat_canvas_shape_splitting');
}); });
} }
@ -288,7 +293,7 @@ export class EditHandlerImpl implements EditHandler {
if (enabled) { if (enabled) {
(this.editedShape as any).selectize(true, { (this.editedShape as any).selectize(true, {
deepSelect: true, deepSelect: true,
pointSize: 2 * consts.BASE_POINT_SIZE / self.geometry.scale, pointSize: (2 * consts.BASE_POINT_SIZE) / self.geometry.scale,
rotationPoint: false, rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle { pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested const circle: SVG.Circle = this.nested
@ -354,9 +359,7 @@ export class EditHandlerImpl implements EditHandler {
} }
private initEditing(): void { private initEditing(): void {
this.editedShape = this.canvas this.editedShape = this.canvas.select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first().clone();
.select(`#cvat_canvas_shape_${this.editData.state.clientID}`)
.first().clone();
this.setupPoints(true); this.setupPoints(true);
this.startEdit(); this.startEdit();
// draw points for this with selected and start editing till another point is clicked // draw points for this with selected and start editing till another point is clicked
@ -406,15 +409,11 @@ export class EditHandlerImpl implements EditHandler {
} }
public configurate(configuration: Configuration): void { public configurate(configuration: Configuration): void {
if (typeof (configuration.autoborders) === 'boolean') { if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders; this.autobordersEnabled = configuration.autoborders;
if (this.editLine) { if (this.editLine) {
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder( this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID);
true,
this.editLine,
this.editData.state.clientID,
);
} else { } else {
this.autoborderHandler.autoborder(false); this.autoborderHandler.autoborder(false);
} }
@ -442,14 +441,8 @@ export class EditHandlerImpl implements EditHandler {
const paintHandler = this.editLine.remember('_paintHandler'); const paintHandler = this.editLine.remember('_paintHandler');
for (const point of (paintHandler as any).set.members) { for (const point of (paintHandler as any).set.members) {
point.attr( point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
'stroke-width', point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`);
`${consts.POINTS_STROKE_WIDTH / geometry.scale}`,
);
point.attr(
'r',
`${consts.BASE_POINT_SIZE / geometry.scale}`,
);
} }
} }
} }

@ -5,10 +5,7 @@
import * as SVG from 'svg.js'; import * as SVG from 'svg.js';
import { GroupData } from './canvasModel'; import { GroupData } from './canvasModel';
import { import { translateToSVG } from './shared';
translateToSVG,
} from './shared';
export interface GroupHandler { export interface GroupHandler {
group(groupData: GroupData): void; group(groupData: GroupData): void;
@ -35,16 +32,15 @@ export class GroupHandlerImpl implements GroupHandler {
private statesToBeGroupped: any[]; private statesToBeGroupped: any[];
private highlightedShapes: Record<number, SVG.Shape>; private highlightedShapes: Record<number, SVG.Shape>;
private getSelectionBox(event: MouseEvent): { private getSelectionBox(
event: MouseEvent,
): {
xtl: number; xtl: number;
ytl: number; ytl: number;
xbr: number; xbr: number;
ybr: number; ybr: number;
} { } {
const point = translateToSVG( const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
(this.canvas.node as any as SVGSVGElement),
[event.clientX, event.clientY],
);
const stopSelectionPoint = { const stopSelectionPoint = {
x: point[0], x: point[0],
y: point[1], y: point[1],
@ -60,10 +56,7 @@ export class GroupHandlerImpl implements GroupHandler {
private onSelectStart(event: MouseEvent): void { private onSelectStart(event: MouseEvent): void {
if (!this.selectionRect) { if (!this.selectionRect) {
const point = translateToSVG( const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
this.canvas.node as any as SVGSVGElement,
[event.clientX, event.clientY],
);
this.startSelectionPoint = { this.startSelectionPoint = {
x: point[0], x: point[0],
y: point[1], y: point[1],
@ -102,12 +95,16 @@ export class GroupHandlerImpl implements GroupHandler {
// TODO: Doesn't work properly for groups // TODO: Doesn't work properly for groups
const bbox = shape.bbox(); const bbox = shape.bbox();
const clientID = shape.attr('clientID'); const clientID = shape.attr('clientID');
if (bbox.x > box.xtl && bbox.y > box.ytl if (
&& bbox.x + bbox.width < box.xbr bbox.x > box.xtl &&
&& bbox.y + bbox.height < box.ybr bbox.y > box.ytl &&
&& !(clientID in this.highlightedShapes)) { bbox.x + bbox.width < box.xbr &&
const objectState = this.getStates() bbox.y + bbox.height < box.ybr &&
.filter((state: any): boolean => state.clientID === clientID)[0]; !(clientID in this.highlightedShapes)
) {
const objectState = this.getStates().filter(
(state: any): boolean => state.clientID === clientID,
)[0];
if (objectState) { if (objectState) {
this.statesToBeGroupped.push(objectState); this.statesToBeGroupped.push(objectState);

@ -15,11 +15,7 @@ export interface InteractionHandler {
} }
export class InteractionHandlerImpl implements InteractionHandler { export class InteractionHandlerImpl implements InteractionHandler {
private onInteraction: ( private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void;
shapes: InteractionResult[] | null,
shapesUpdated?: boolean,
isDone?: boolean,
) => void;
private geometry: Geometry; private geometry: Geometry;
private canvas: SVG.Container; private canvas: SVG.Container;
private interactionData: InteractionData; private interactionData: InteractionData;
@ -30,7 +26,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
private crosshair: Crosshair; private crosshair: Crosshair;
private prepareResult(): InteractionResult[] { private prepareResult(): InteractionResult[] {
return this.interactionShapes.map((shape: SVG.Shape): InteractionResult => { return this.interactionShapes.map(
(shape: SVG.Shape): InteractionResult => {
if (shape.type === 'circle') { if (shape.type === 'circle') {
const points = [(shape as SVG.Circle).cx(), (shape as SVG.Circle).cy()]; const points = [(shape as SVG.Circle).cx(), (shape as SVG.Circle).cy()];
return { return {
@ -40,32 +37,35 @@ export class InteractionHandlerImpl implements InteractionHandler {
}; };
} }
const bbox = (shape.node as any as SVGRectElement).getBBox(); const bbox = ((shape.node as any) as SVGRectElement).getBBox();
const points = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height]; const points = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height];
return { return {
points: points.map((coord: number): number => coord - this.geometry.offset), points: points.map((coord: number): number => coord - this.geometry.offset),
shapeType: 'rectangle', shapeType: 'rectangle',
button: 0, button: 0,
}; };
}); },
);
} }
private shouldRaiseEvent(ctrlKey: boolean): boolean { private shouldRaiseEvent(ctrlKey: boolean): boolean {
const { interactionData, interactionShapes, shapesWereUpdated } = this; const { interactionData, interactionShapes, shapesWereUpdated } = this;
const { minPosVertices, minNegVertices, enabled } = interactionData; const { minPosVertices, minNegVertices, enabled } = interactionData;
const positiveShapes = interactionShapes const positiveShapes = interactionShapes.filter(
.filter((shape: SVG.Shape): boolean => (shape as any).attr('stroke') === 'green'); (shape: SVG.Shape): boolean => (shape as any).attr('stroke') === 'green',
const negativeShapes = interactionShapes );
.filter((shape: SVG.Shape): boolean => (shape as any).attr('stroke') !== 'green'); const negativeShapes = interactionShapes.filter(
(shape: SVG.Shape): boolean => (shape as any).attr('stroke') !== 'green',
);
if (interactionData.shapeType === 'rectangle') { if (interactionData.shapeType === 'rectangle') {
return enabled && !ctrlKey && !!interactionShapes.length; return enabled && !ctrlKey && !!interactionShapes.length;
} }
const minimumVerticesAchieved = (typeof (minPosVertices) === 'undefined' const minimumVerticesAchieved =
|| minPosVertices <= positiveShapes.length) && (typeof (minNegVertices) === 'undefined' (typeof minPosVertices === 'undefined' || minPosVertices <= positiveShapes.length) &&
|| minPosVertices <= negativeShapes.length); (typeof minNegVertices === 'undefined' || minPosVertices <= negativeShapes.length);
return enabled && !ctrlKey && minimumVerticesAchieved && shapesWereUpdated; return enabled && !ctrlKey && minimumVerticesAchieved && shapesWereUpdated;
} }
@ -82,12 +82,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
const eventListener = (e: MouseEvent): void => { const eventListener = (e: MouseEvent): void => {
if ((e.button === 0 || e.button === 2) && !e.altKey) { if ((e.button === 0 || e.button === 2) && !e.altKey) {
e.preventDefault(); e.preventDefault();
const [cx, cy] = translateToSVG( const [cx, cy] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]);
this.canvas.node as any as SVGSVGElement,
[e.clientX, e.clientY],
);
this.currentInteractionShape = this.canvas this.currentInteractionShape = this.canvas
.circle(consts.BASE_POINT_SIZE * 2 / this.geometry.scale).center(cx, cy) .circle((consts.BASE_POINT_SIZE * 2) / this.geometry.scale)
.center(cx, cy)
.fill('white') .fill('white')
.stroke(e.button === 0 ? 'green' : 'red') .stroke(e.button === 0 ? 'green' : 'red')
.addClass('cvat_interaction_point') .addClass('cvat_interaction_point')
@ -150,13 +148,16 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.currentInteractionShape = this.canvas.rect(); this.currentInteractionShape = this.canvas.rect();
this.canvas.on('mousedown.interaction', eventListener); this.canvas.on('mousedown.interaction', eventListener);
this.currentInteractionShape.on('drawstop', (): void => { this.currentInteractionShape
.on('drawstop', (): void => {
this.interactionShapes.push(this.currentInteractionShape); this.interactionShapes.push(this.currentInteractionShape);
this.shapesWereUpdated = true; this.shapesWereUpdated = true;
this.canvas.off('mousedown.interaction', eventListener); this.canvas.off('mousedown.interaction', eventListener);
this.interact({ enabled: false }); this.interact({ enabled: false });
}).addClass('cvat_canvas_shape_drawing').attr({ })
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
}); });
} }
@ -192,19 +193,11 @@ export class InteractionHandlerImpl implements InteractionHandler {
} }
public constructor( public constructor(
onInteraction: ( onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void,
shapes: InteractionResult[] | null,
shapesUpdated?: boolean,
isDone?: boolean,
) => void,
canvas: SVG.Container, canvas: SVG.Container,
geometry: Geometry, geometry: Geometry,
) { ) {
this.onInteraction = ( this.onInteraction = (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean): void => {
shapes: InteractionResult[] | null,
shapesUpdated?: boolean,
isDone?: boolean,
): void => {
this.shapesWereUpdated = false; this.shapesWereUpdated = false;
onInteraction(shapes, shapesUpdated, isDone); onInteraction(shapes, shapesUpdated, isDone);
}; };
@ -221,10 +214,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
}; };
this.canvas.on('mousemove.interaction', (e: MouseEvent): void => { this.canvas.on('mousemove.interaction', (e: MouseEvent): void => {
const [x, y] = translateToSVG( const [x, y] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]);
this.canvas.node as any as SVGSVGElement,
[e.clientX, e.clientY],
);
this.cursorPosition = { x, y }; this.cursorPosition = { x, y };
if (this.crosshair) { if (this.crosshair) {
this.crosshair.move(x, y); this.crosshair.move(x, y);
@ -232,7 +222,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
}); });
document.body.addEventListener('keyup', (e: KeyboardEvent): void => { document.body.addEventListener('keyup', (e: KeyboardEvent): void => {
if (e.keyCode === 17 && this.shouldRaiseEvent(false)) { // 17 is ctrl if (e.keyCode === 17 && this.shouldRaiseEvent(false)) {
// 17 is ctrl
this.onInteraction(this.prepareResult(), true, false); this.onInteraction(this.prepareResult(), true, false);
} }
}); });

@ -12,7 +12,6 @@ export interface MergeHandler {
repeatSelection(): void; repeatSelection(): void;
} }
export class MergeHandlerImpl implements MergeHandler { export class MergeHandlerImpl implements MergeHandler {
// callback is used to notify about merging end // callback is used to notify about merging end
private onMergeDone: (objects: any[] | null, duration?: number) => void; private onMergeDone: (objects: any[] | null, duration?: number) => void;
@ -40,8 +39,10 @@ export class MergeHandlerImpl implements MergeHandler {
} }
private checkConstraints(state: any): boolean { private checkConstraints(state: any): boolean {
return !this.constraints || (state.label.id === this.constraints.labelID return (
&& state.shapeType === this.constraints.shapeType); !this.constraints ||
(state.label.id === this.constraints.labelID && state.shapeType === this.constraints.shapeType)
);
} }
private release(): void { private release(): void {
@ -118,8 +119,7 @@ export class MergeHandlerImpl implements MergeHandler {
} }
} else { } else {
const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first(); const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first();
if (shape && this.checkConstraints(objectState) if (shape && this.checkConstraints(objectState) && !stateFrames.includes(objectState.frame)) {
&& !stateFrames.includes(objectState.frame)) {
this.statesToBeMerged.push(objectState); this.statesToBeMerged.push(objectState);
this.highlightedShapes[objectState.clientID] = shape; this.highlightedShapes[objectState.clientID] = shape;
shape.addClass('cvat_canvas_shape_merging'); shape.addClass('cvat_canvas_shape_merging');

@ -83,22 +83,25 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
return output; return output;
} }
export function displayShapeSize( export function displayShapeSize(shapesContainer: SVG.Container, textContainer: SVG.Container): ShapeSizeElement {
shapesContainer: SVG.Container,
textContainer: SVG.Container,
): ShapeSizeElement {
const shapeSize: ShapeSizeElement = { const shapeSize: ShapeSizeElement = {
sizeElement: textContainer.text('').font({ sizeElement: textContainer
.text('')
.font({
weight: 'bolder', weight: 'bolder',
}).fill('white').addClass('cvat_canvas_text'), })
.fill('white')
.addClass('cvat_canvas_text'),
update(shape: SVG.Shape): void { update(shape: SVG.Shape): void {
const bbox = shape.bbox(); const bbox = shape.bbox();
const text = `${bbox.width.toFixed(1)}x${bbox.height.toFixed(1)}`; const text = `${bbox.width.toFixed(1)}x${bbox.height.toFixed(1)}`;
const [x, y]: number[] = translateToSVG( const [x, y]: number[] = translateToSVG(
textContainer.node as any as SVGSVGElement, (textContainer.node as any) as SVGSVGElement,
translateFromSVG((shapesContainer.node as any as SVGSVGElement), [bbox.x, bbox.y]), translateFromSVG((shapesContainer.node as any) as SVGSVGElement, [bbox.x, bbox.y]),
); );
this.sizeElement.clear().plain(text) this.sizeElement
.clear()
.plain(text)
.move(x + consts.TEXT_MARGIN, y + consts.TEXT_MARGIN); .move(x + consts.TEXT_MARGIN, y + consts.TEXT_MARGIN);
}, },
rm(): void { rm(): void {
@ -120,7 +123,9 @@ export function pointsToNumberArray(points: string | Point[]): number[] {
}, []); }, []);
} }
return points.trim().split(/[,\s]+/g) return points
.trim()
.split(/[,\s]+/g)
.map((coord: string): number => +coord); .map((coord: string): number => +coord);
} }
@ -138,14 +143,19 @@ export function parsePoints(source: string | number[]): Point[] {
}, []); }, []);
} }
return source.trim().split(/\s/).map((point: string): Point => { return source
.trim()
.split(/\s/)
.map(
(point: string): Point => {
const [x, y] = point.split(',').map((coord: string): number => +coord); const [x, y] = point.split(',').map((coord: string): number => +coord);
return { x, y }; return { x, y };
}); },
);
} }
export function stringifyPoints(points: (Point | number)[]): string { export function stringifyPoints(points: (Point | number)[]): string {
if (typeof (points[0]) === 'number') { if (typeof points[0] === 'number') {
return points.reduce((acc: string, val: number, idx: number): string => { return points.reduce((acc: string, val: number, idx: number): string => {
if (idx % 2) { if (idx % 2) {
return `${acc},${val}`; return `${acc},${val}`;
@ -166,5 +176,5 @@ export function scalarProduct(a: Vector2D, b: Vector2D): number {
} }
export function vectorLength(vector: Vector2D): number { export function vectorLength(vector: Vector2D): number {
return Math.sqrt((vector.i ** 2) + (vector.j ** 2)); return Math.sqrt(vector.i ** 2 + vector.j ** 2);
} }

@ -85,12 +85,16 @@ export class SplitHandlerImpl implements SplitHandler {
this.highlightedShape = shape; this.highlightedShape = shape;
this.highlightedShape.addClass('cvat_canvas_shape_splitting'); this.highlightedShape.addClass('cvat_canvas_shape_splitting');
this.canvas.node.append(this.highlightedShape.node); this.canvas.node.append(this.highlightedShape.node);
this.highlightedShape.on('click.split', (): void => { this.highlightedShape.on(
'click.split',
(): void => {
this.splitDone = true; this.splitDone = true;
this.onSplitDone(state); this.onSplitDone(state);
}, { },
{
once: true, once: true,
}); },
);
} }
} }
} }

@ -10,13 +10,7 @@ import 'svg.select.js';
import 'svg.draw.js'; import 'svg.draw.js';
import consts from './consts'; import consts from './consts';
import { import { Point, Equation, CuboidModel, Orientation, Edge } from './cuboid';
Point,
Equation,
CuboidModel,
Orientation,
Edge,
} from './cuboid';
import { parsePoints, clamp } from './shared'; import { parsePoints, clamp } from './shared';
// Update constructor // Update constructor
@ -51,20 +45,19 @@ function undo(): void {
} }
} }
SVG.Element.prototype.draw.extend('polyline', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polyline, 'polyline',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polyline, {
undo: undo, undo: undo,
}, }),
)); );
SVG.Element.prototype.draw.extend('polygon', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polygon, 'polygon',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polygon, {
undo: undo, undo: undo,
}, }),
)); );
// Create transform for rect, polyline and polygon // Create transform for rect, polyline and polygon
function transform(): void { function transform(): void {
@ -72,26 +65,26 @@ function transform(): void {
this.offset = { x: window.pageXOffset, y: window.pageYOffset }; this.offset = { x: window.pageXOffset, y: window.pageYOffset };
} }
SVG.Element.prototype.draw.extend('rect', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.rect, 'rect',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.rect, {
transform: transform, transform: transform,
}, }),
)); );
SVG.Element.prototype.draw.extend('polyline', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polyline, 'polyline',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polyline, {
transform: transform, transform: transform,
}, }),
)); );
SVG.Element.prototype.draw.extend('polygon', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polygon, 'polygon',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polygon, {
transform: transform, transform: transform,
}, }),
)); );
// Fix method drawCircles // Fix method drawCircles
function drawCircles(): void { function drawCircles(): void {
@ -108,9 +101,7 @@ function drawCircles(): void {
[, this.p.y] = array[i]; [, this.p.y] = array[i];
const p = this.p.matrixTransform( const p = this.p.matrixTransform(
this.parent.node.getScreenCTM() this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()),
.inverse()
.multiply(this.el.node.getScreenCTM()),
); );
this.set.add( this.set.add(
@ -118,32 +109,33 @@ function drawCircles(): void {
.circle(5) .circle(5)
.stroke({ .stroke({
width: 1, width: 1,
}).fill('#ccc') })
.fill('#ccc')
.center(p.x, p.y), .center(p.x, p.y),
); );
} }
} }
SVG.Element.prototype.draw.extend('line', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.line, 'line',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.line, {
drawCircles: drawCircles, drawCircles: drawCircles,
} }),
)); );
SVG.Element.prototype.draw.extend('polyline', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polyline, 'polyline',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polyline, {
drawCircles: drawCircles, drawCircles: drawCircles,
} }),
)); );
SVG.Element.prototype.draw.extend('polygon', Object.assign({}, SVG.Element.prototype.draw.extend(
SVG.Element.prototype.draw.plugins.polygon, 'polygon',
{ Object.assign({}, SVG.Element.prototype.draw.plugins.polygon, {
drawCircles: drawCircles, drawCircles: drawCircles,
} }),
)); );
// Fix method drag // Fix method drag
const originalDraggable = SVG.Element.prototype.draggable; const originalDraggable = SVG.Element.prototype.draggable;
@ -155,7 +147,7 @@ SVG.Element.prototype.draggable = function constructor(...args: any): any {
handler.drag = function (e: any) { handler.drag = function (e: any) {
this.m = this.el.node.getScreenCTM().inverse(); this.m = this.el.node.getScreenCTM().inverse();
return handler.constructor.prototype.drag.call(this, e); return handler.constructor.prototype.drag.call(this, e);
} };
} else { } else {
originalDraggable.call(this, ...args); originalDraggable.call(this, ...args);
} }
@ -178,11 +170,11 @@ SVG.Element.prototype.resize = function constructor(...args: any): any {
if (event.button === 0 && !event.shiftKey && !event.altKey) { if (event.button === 0 && !event.shiftKey && !event.altKey) {
return handler.constructor.prototype.resize.call(this, e); return handler.constructor.prototype.resize.call(this, e);
} }
} };
handler.update = function (e: any) { handler.update = function (e: any) {
this.m = this.el.node.getScreenCTM().inverse(); this.m = this.el.node.getScreenCTM().inverse();
return handler.constructor.prototype.update.call(this, e); return handler.constructor.prototype.update.call(this, e);
} };
} else { } else {
originalResize.call(this, ...args); originalResize.call(this, ...args);
} }
@ -193,7 +185,6 @@ for (const key of Object.keys(originalResize)) {
SVG.Element.prototype.resize[key] = originalResize[key]; SVG.Element.prototype.resize[key] = originalResize[key];
} }
enum EdgeIndex { enum EdgeIndex {
FL = 1, FL = 1,
FR = 2, FR = 2,
@ -255,14 +246,34 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
}, },
setupProjections() { setupProjections() {
this.ftProj = this.line(this.updateProjectionLine(this.cuboidModel.ft.getEquation(), this.ftProj = this.line(
this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); this.updateProjectionLine(
this.fbProj = this.line(this.updateProjectionLine(this.cuboidModel.fb.getEquation(), this.cuboidModel.ft.getEquation(),
this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); this.cuboidModel.ft.points[0],
this.rtProj = this.line(this.updateProjectionLine(this.cuboidModel.rt.getEquation(), this.cuboidModel.vpl,
this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); ),
this.rbProj = this.line(this.updateProjectionLine(this.cuboidModel.rb.getEquation(), );
this.cuboidModel.rb.points[1], this.cuboidModel.vpr)); this.fbProj = this.line(
this.updateProjectionLine(
this.cuboidModel.fb.getEquation(),
this.cuboidModel.ft.points[0],
this.cuboidModel.vpl,
),
);
this.rtProj = this.line(
this.updateProjectionLine(
this.cuboidModel.rt.getEquation(),
this.cuboidModel.rt.points[1],
this.cuboidModel.vpr,
),
);
this.rbProj = this.line(
this.updateProjectionLine(
this.cuboidModel.rb.getEquation(),
this.cuboidModel.rb.points[1],
this.cuboidModel.vpr,
),
);
this.ftProj.stroke({ color: '#C0C0C0' }).addClass('cvat_canvas_cuboid_projections'); this.ftProj.stroke({ color: '#C0C0C0' }).addClass('cvat_canvas_cuboid_projections');
this.fbProj.stroke({ color: '#C0C0C0' }).addClass('cvat_canvas_cuboid_projections'); this.fbProj.stroke({ color: '#C0C0C0' }).addClass('cvat_canvas_cuboid_projections');
@ -308,8 +319,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
} else { } else {
this.drCenter.hide(); this.drCenter.hide();
} }
}, },
showProjections() { showProjections() {
@ -358,7 +367,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
const x2 = direction.x; const x2 = direction.x;
const y2 = equation.getY(x2); const y2 = equation.getY(x2);
return [[x1, y1], [x2, y2]]; return [
[x1, y1],
[x2, y2],
];
}, },
selectize(value: boolean, options: object) { selectize(value: boolean, options: object) {
@ -373,40 +385,46 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
} }
if (value === false) { if (value === false) {
this.getGrabPoints().forEach((point: SVG.Element) => {point && point.remove()}); this.getGrabPoints().forEach((point: SVG.Element) => {
point && point.remove();
});
} else { } else {
this.setupGrabPoints(this.face.remember('_selectHandler').drawPoint.bind( this.setupGrabPoints(
{nested: this, options: this.face.remember('_selectHandler').options} this.face
)); .remember('_selectHandler')
.drawPoint.bind({ nested: this, options: this.face.remember('_selectHandler').options }),
);
// setup proper classes for selection points for proper cursor // setup proper classes for selection points for proper cursor
Array.from(this.face.remember('_selectHandler').nested.node.children) Array.from(this.face.remember('_selectHandler').nested.node.children).forEach(
.forEach((point: SVG.LinkedHTMLElement, i: number) => { (point: SVG.LinkedHTMLElement, i: number) => {
point.classList.add(`svg_select_points_${['lt', 'lb', 'rb', 'rt'][i]}`) point.classList.add(`svg_select_points_${['lt', 'lb', 'rb', 'rt'][i]}`);
}); },
);
if (this.cuboidModel.orientation === Orientation.LEFT) { if (this.cuboidModel.orientation === Orientation.LEFT) {
Array.from(this.dorsalRightEdge.remember('_selectHandler').nested.node.children) Array.from(this.dorsalRightEdge.remember('_selectHandler').nested.node.children).forEach(
.forEach((point: SVG.LinkedHTMLElement, i: number) => { (point: SVG.LinkedHTMLElement, i: number) => {
point.classList.add(`svg_select_points_${['t', 'b'][i]}`); point.classList.add(`svg_select_points_${['t', 'b'][i]}`);
point.ondblclick = (e: MouseEvent) => { point.ondblclick = (e: MouseEvent) => {
if (e.shiftKey) { if (e.shiftKey) {
this.resetPerspective() this.resetPerspective();
} }
}; };
}); },
);
} else { } else {
Array.from(this.dorsalLeftEdge.remember('_selectHandler').nested.node.children) Array.from(this.dorsalLeftEdge.remember('_selectHandler').nested.node.children).forEach(
.forEach((point: SVG.LinkedHTMLElement, i: number) => { (point: SVG.LinkedHTMLElement, i: number) => {
point.classList.add(`svg_select_points_${['t', 'b'][i]}`); point.classList.add(`svg_select_points_${['t', 'b'][i]}`);
point.ondblclick = (e: MouseEvent) => { point.ondblclick = (e: MouseEvent) => {
if (e.shiftKey) { if (e.shiftKey) {
this.resetPerspective() this.resetPerspective();
} }
}; };
}); },
);
} }
} }
return this; return this;
@ -428,7 +446,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
point.off('dragmove'); point.off('dragmove');
point.off('dragend'); point.off('dragend');
} }
}) });
return; return;
} }
@ -436,9 +454,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
function getResizedPointIndex(event: CustomEvent): number { function getResizedPointIndex(event: CustomEvent): number {
const { target } = event.detail.event.detail.event; const { target } = event.detail.event.detail.event;
const { parentElement } = target; const { parentElement } = target;
return Array return Array.from(parentElement.children).indexOf(target);
.from(parentElement.children)
.indexOf(target);
} }
let resizedCubePoint: null | number = null; let resizedCubePoint: null | number = null;
@ -447,14 +463,15 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
y: 0, y: 0,
}; };
this.face.on('resizestart', (event: CustomEvent) => { this.face
.on('resizestart', (event: CustomEvent) => {
accumulatedOffset.x = 0; accumulatedOffset.x = 0;
accumulatedOffset.y = 0; accumulatedOffset.y = 0;
const resizedFacePoint = getResizedPointIndex(event); const resizedFacePoint = getResizedPointIndex(event);
resizedCubePoint = [0, 1].includes(resizedFacePoint) ? resizedFacePoint resizedCubePoint = [0, 1].includes(resizedFacePoint) ? resizedFacePoint : 5 - resizedFacePoint; // 2,3 -> 3,2
: 5 - resizedFacePoint; // 2,3 -> 3,2
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
}).on('resizing', (event: CustomEvent) => { })
.on('resizing', (event: CustomEvent) => {
let { dx, dy } = event.detail; let { dx, dy } = event.detail;
let dxPortion = dx - accumulatedOffset.x; let dxPortion = dx - accumulatedOffset.x;
let dyPortion = dy - accumulatedOffset.y; let dyPortion = dy - accumulatedOffset.y;
@ -467,13 +484,15 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
let cuboidPoints = this.cuboidModel.getPoints(); let cuboidPoints = this.cuboidModel.getPoints();
let x1 = cuboidPoints[edgeTopIndex].x + dxPortion; let x1 = cuboidPoints[edgeTopIndex].x + dxPortion;
let x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; let x2 = cuboidPoints[edgeBottomIndex].x + dxPortion;
if (edge === EdgeIndex.FL if (
&& (cuboidPoints[2].x - (cuboidPoints[0].x + dxPortion) < consts.MIN_EDGE_LENGTH) edge === EdgeIndex.FL &&
cuboidPoints[2].x - (cuboidPoints[0].x + dxPortion) < consts.MIN_EDGE_LENGTH
) { ) {
x1 = cuboidPoints[edgeTopIndex].x; x1 = cuboidPoints[edgeTopIndex].x;
x2 = cuboidPoints[edgeBottomIndex].x; x2 = cuboidPoints[edgeBottomIndex].x;
} else if (edge === EdgeIndex.FR } else if (
&& (cuboidPoints[2].x + dxPortion - cuboidPoints[0].x < consts.MIN_EDGE_LENGTH) edge === EdgeIndex.FR &&
cuboidPoints[2].x + dxPortion - cuboidPoints[0].x < consts.MIN_EDGE_LENGTH
) { ) {
x1 = cuboidPoints[edgeTopIndex].x; x1 = cuboidPoints[edgeTopIndex].x;
x2 = cuboidPoints[edgeBottomIndex].x; x2 = cuboidPoints[edgeBottomIndex].x;
@ -503,7 +522,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.face.plot(this.cuboidModel.front.points); this.face.plot(this.cuboidModel.front.points);
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('resizedone', (event: CustomEvent) => { })
.on('resizedone', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
@ -544,7 +564,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
accumulatedOffset.y = 0; accumulatedOffset.y = 0;
resizedCubePoint = getResizedPointIndex(event) + (orientation === Orientation.LEFT ? 4 : 6); resizedCubePoint = getResizedPointIndex(event) + (orientation === Orientation.LEFT ? 4 : 6);
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
}).on('resizing', (event: CustomEvent) => { })
.on('resizing', (event: CustomEvent) => {
let { dy } = event.detail; let { dy } = event.detail;
let dyPortion = dy - accumulatedOffset.y; let dyPortion = dy - accumulatedOffset.y;
accumulatedOffset.y += dyPortion; accumulatedOffset.y += dyPortion;
@ -568,7 +589,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
const midPointUp = { ...cuboidPoints[edgeTopIndex] }; const midPointUp = { ...cuboidPoints[edgeTopIndex] };
const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; const midPointDown = { ...cuboidPoints[edgeBottomIndex] };
(edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion;
const dorselEdge = (orientation === Orientation.LEFT ? this.cuboidModel.dr : this.cuboidModel.dl); const dorselEdge =
orientation === Orientation.LEFT ? this.cuboidModel.dr : this.cuboidModel.dl;
const constraints = computeSideEdgeConstraints(dorselEdge, this.cuboidModel.fr); const constraints = computeSideEdgeConstraints(dorselEdge, this.cuboidModel.fr);
midPointUp.y = clamp(midPointUp.y, constraints.y1Range.min, constraints.y1Range.max); midPointUp.y = clamp(midPointUp.y, constraints.y1Range.min, constraints.y1Range.max);
midPointDown.y = clamp(midPointDown.y, constraints.y2Range.min, constraints.y2Range.max); midPointDown.y = clamp(midPointDown.y, constraints.y2Range.min, constraints.y2Range.max);
@ -576,11 +598,11 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.updateViewAndVM(edge === EdgeIndex.DL); this.updateViewAndVM(edge === EdgeIndex.DL);
} }
this.updateViewAndVM(false); this.updateViewAndVM(false);
this.face.plot(this.cuboidModel.front.points); this.face.plot(this.cuboidModel.front.points);
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('resizedone', (event: CustomEvent) => { })
.on('resizedone', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
} }
@ -608,20 +630,24 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
updatingFace.points = [leftPoints, { x: midX, y: midY }, rightPoints, null]; updatingFace.points = [leftPoints, { x: midX, y: midY }, rightPoints, null];
} }
this.drCenter
this.drCenter.draggable((x: number) => { .draggable((x: number) => {
let xStatus; let xStatus;
if (this.drCenter.cx() < this.cuboidModel.fr.points[0].x) { if (this.drCenter.cx() < this.cuboidModel.fr.points[0].x) {
xStatus = x < this.cuboidModel.fr.points[0].x - consts.MIN_EDGE_LENGTH xStatus =
&& x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH; x < this.cuboidModel.fr.points[0].x - consts.MIN_EDGE_LENGTH &&
x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH;
} else { } else {
xStatus = x > this.cuboidModel.fr.points[0].x + consts.MIN_EDGE_LENGTH xStatus =
&& x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH; x > this.cuboidModel.fr.points[0].x + consts.MIN_EDGE_LENGTH &&
x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH;
} }
return { x: xStatus, y: this.drCenter.attr('y1') }; return { x: xStatus, y: this.drCenter.attr('y1') };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.dorsalRightEdge.center(this.drCenter.cx(), this.drCenter.cy()); this.dorsalRightEdge.center(this.drCenter.cx(), this.drCenter.cy());
const x = this.dorsalRightEdge.attr('x1'); const x = this.dorsalRightEdge.attr('x1');
@ -633,23 +659,29 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.cuboidModel.dr.points = [topPoint, botPoint]; this.cuboidModel.dr.points = [topPoint, botPoint];
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
this.dlCenter.draggable((x: number) => { this.dlCenter
.draggable((x: number) => {
let xStatus; let xStatus;
if (this.dlCenter.cx() < this.cuboidModel.fl.points[0].x) { if (this.dlCenter.cx() < this.cuboidModel.fl.points[0].x) {
xStatus = x < this.cuboidModel.fl.points[0].x - consts.MIN_EDGE_LENGTH xStatus =
&& x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH; x < this.cuboidModel.fl.points[0].x - consts.MIN_EDGE_LENGTH &&
x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH;
} else { } else {
xStatus = x > this.cuboidModel.fl.points[0].x + consts.MIN_EDGE_LENGTH xStatus =
&& x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH; x > this.cuboidModel.fl.points[0].x + consts.MIN_EDGE_LENGTH &&
x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH;
} }
return { x: xStatus, y: this.dlCenter.attr('y1') }; return { x: xStatus, y: this.dlCenter.attr('y1') };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.dorsalLeftEdge.center(this.dlCenter.cx(), this.dlCenter.cy()); this.dorsalLeftEdge.center(this.dlCenter.cx(), this.dlCenter.cy());
const x = this.dorsalLeftEdge.attr('x1'); const x = this.dorsalLeftEdge.attr('x1');
@ -661,16 +693,20 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.cuboidModel.dl.points = [topPoint, botPoint]; this.cuboidModel.dl.points = [topPoint, botPoint];
this.updateViewAndVM(true); this.updateViewAndVM(true);
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
});; });
this.flCenter.draggable((x: number) => { this.flCenter
.draggable((x: number) => {
const vpX = this.flCenter.cx() - this.cuboidModel.vpl.x > 0 ? this.cuboidModel.vpl.x : 0; const vpX = this.flCenter.cx() - this.cuboidModel.vpl.x > 0 ? this.cuboidModel.vpl.x : 0;
return { x: x < this.cuboidModel.fr.points[0].x && x > vpX + consts.MIN_EDGE_LENGTH }; return { x: x < this.cuboidModel.fr.points[0].x && x > vpX + consts.MIN_EDGE_LENGTH };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.frontLeftEdge.center(this.flCenter.cx(), this.flCenter.cy()); this.frontLeftEdge.center(this.flCenter.cx(), this.flCenter.cy());
const x = this.frontLeftEdge.attr('x1'); const x = this.frontLeftEdge.attr('x1');
@ -682,15 +718,19 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.cuboidModel.fl.points = [topPoint, botPoint]; this.cuboidModel.fl.points = [topPoint, botPoint];
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
this.frCenter.draggable((x: number) => { this.frCenter
.draggable((x: number) => {
return { x: x > this.cuboidModel.fl.points[0].x, y: this.frCenter.attr('y1') }; return { x: x > this.cuboidModel.fl.points[0].x, y: this.frCenter.attr('y1') };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.frontRightEdge.center(this.frCenter.cx(), this.frCenter.cy()); this.frontRightEdge.center(this.frCenter.cx(), this.frCenter.cy());
const x = this.frontRightEdge.attr('x1'); const x = this.frontRightEdge.attr('x1');
@ -702,43 +742,61 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.cuboidModel.fr.points = [topPoint, botPoint]; this.cuboidModel.fr.points = [topPoint, botPoint];
this.updateViewAndVM(true); this.updateViewAndVM(true);
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
this.ftCenter.draggable((x: number, y: number) => { this.ftCenter
.draggable((x: number, y: number) => {
return { x: x === this.ftCenter.cx(), y: y < this.fbCenter.cy() - consts.MIN_EDGE_LENGTH }; return { x: x === this.ftCenter.cx(), y: y < this.fbCenter.cy() - consts.MIN_EDGE_LENGTH };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.frontTopEdge.center(this.ftCenter.cx(), this.ftCenter.cy()); this.frontTopEdge.center(this.ftCenter.cx(), this.ftCenter.cy());
horizontalEdgeControl.call(this, this.cuboidModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); horizontalEdgeControl.call(
this,
this.cuboidModel.top,
this.frontTopEdge.attr('x2'),
this.frontTopEdge.attr('y2'),
);
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
this.fbCenter.draggable((x: number, y: number) => { this.fbCenter
.draggable((x: number, y: number) => {
return { x: x === this.fbCenter.cx(), y: y > this.ftCenter.cy() + consts.MIN_EDGE_LENGTH }; return { x: x === this.fbCenter.cx(), y: y > this.ftCenter.cy() + consts.MIN_EDGE_LENGTH };
}).on('dragstart', ((event: CustomEvent) => { })
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('resizestart', event)); this.fire(new CustomEvent('resizestart', event));
})).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.frontBotEdge.center(this.fbCenter.cx(), this.fbCenter.cy()); this.frontBotEdge.center(this.fbCenter.cx(), this.fbCenter.cy());
horizontalEdgeControl.call(this, this.cuboidModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); horizontalEdgeControl.call(
this,
this.cuboidModel.bot,
this.frontBotEdge.attr('x2'),
this.frontBotEdge.attr('y2'),
);
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('resizing', event)); this.fire(new CustomEvent('resizing', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('resizedone', event)); this.fire(new CustomEvent('resizedone', event));
}); });
return this; return this;
}, },
draggable(value: any, constraint: any) { draggable(value: any, constraint: any) {
const { cuboidModel } = this; const { cuboidModel } = this;
const faces = [this.face, this.right, this.dorsal, this.left] const faces = [this.face, this.right, this.dorsal, this.left];
const accumulatedOffset: Point = { const accumulatedOffset: Point = {
x: 0, x: 0,
y: 0, y: 0,
@ -750,16 +808,19 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
face.off('dragstart'); face.off('dragstart');
face.off('dragmove'); face.off('dragmove');
face.off('dragend'); face.off('dragend');
}) });
return return;
} }
this.face.draggable().on('dragstart', (event: CustomEvent) => { this.face
.draggable()
.on('dragstart', (event: CustomEvent) => {
accumulatedOffset.x = 0; accumulatedOffset.x = 0;
accumulatedOffset.y = 0; accumulatedOffset.y = 0;
this.fire(new CustomEvent('dragstart', event)); this.fire(new CustomEvent('dragstart', event));
}).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
const dx = event.detail.p.x - event.detail.handler.startPoints.point.x; const dx = event.detail.p.x - event.detail.handler.startPoints.point.x;
const dy = event.detail.p.y - event.detail.handler.startPoints.point.y; const dy = event.detail.p.y - event.detail.handler.startPoints.point.y;
let dxPortion = dx - accumulatedOffset.x; let dxPortion = dx - accumulatedOffset.x;
@ -770,47 +831,59 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.dmove(dxPortion, dyPortion); this.dmove(dxPortion, dyPortion);
this.fire(new CustomEvent('dragmove', event)); this.fire(new CustomEvent('dragmove', event));
}).on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('dragend', event));
}) })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('dragend', event));
});
this.left.draggable((x: number, y: number) => ({ this.left
x: x < Math.min(cuboidModel.dr.points[0].x, .draggable((x: number, y: number) => ({
cuboidModel.fr.points[0].x) - consts.MIN_EDGE_LENGTH, y x: x < Math.min(cuboidModel.dr.points[0].x, cuboidModel.fr.points[0].x) - consts.MIN_EDGE_LENGTH,
})).on('dragstart', (event: CustomEvent) => { y,
}))
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('dragstart', event)); this.fire(new CustomEvent('dragstart', event));
}).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.cuboidModel.left.points = parsePoints(this.left.attr('points')); this.cuboidModel.left.points = parsePoints(this.left.attr('points'));
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('dragmove', event)); this.fire(new CustomEvent('dragmove', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('dragend', event)); this.fire(new CustomEvent('dragend', event));
}); });
this.dorsal.draggable().on('dragstart', (event: CustomEvent) => { this.dorsal
.draggable()
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('dragstart', event)); this.fire(new CustomEvent('dragstart', event));
}).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.cuboidModel.dorsal.points = parsePoints(this.dorsal.attr('points')); this.cuboidModel.dorsal.points = parsePoints(this.dorsal.attr('points'));
this.updateViewAndVM(); this.updateViewAndVM();
this.fire(new CustomEvent('dragmove', event)); this.fire(new CustomEvent('dragmove', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('dragend', event)); this.fire(new CustomEvent('dragend', event));
}); });
this.right.draggable((x: number, y: number) => ({ this.right
x: x > Math.min(cuboidModel.dl.points[0].x, .draggable((x: number, y: number) => ({
cuboidModel.fl.points[0].x) + consts.MIN_EDGE_LENGTH, y x: x > Math.min(cuboidModel.dl.points[0].x, cuboidModel.fl.points[0].x) + consts.MIN_EDGE_LENGTH,
})).on('dragstart', (event: CustomEvent) => { y,
}))
.on('dragstart', (event: CustomEvent) => {
this.fire(new CustomEvent('dragstart', event)); this.fire(new CustomEvent('dragstart', event));
}).on('dragmove', (event: CustomEvent) => { })
.on('dragmove', (event: CustomEvent) => {
this.cuboidModel.right.points = parsePoints(this.right.attr('points')); this.cuboidModel.right.points = parsePoints(this.right.attr('points'));
this.updateViewAndVM(true); this.updateViewAndVM(true);
this.fire(new CustomEvent('dragmove', event)); this.fire(new CustomEvent('dragmove', event));
}).on('dragend', (event: CustomEvent) => { })
.on('dragend', (event: CustomEvent) => {
this.fire(new CustomEvent('dragend', event)); this.fire(new CustomEvent('dragend', event));
}); });
@ -820,8 +893,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
_attr: SVG.Element.prototype.attr, _attr: SVG.Element.prototype.attr,
attr(a: any, v: any, n: any) { attr(a: any, v: any, n: any) {
if ((a === 'fill' || a === 'stroke' || a === 'face-stroke') if ((a === 'fill' || a === 'stroke' || a === 'face-stroke') && v !== undefined) {
&& v !== undefined) {
this._attr(a, v, n); this._attr(a, v, n);
this.paintOrientationLines(); this.paintOrientationLines();
} else if (a === 'points' && typeof v === 'string') { } else if (a === 'points' && typeof v === 'string') {
@ -841,13 +913,16 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.rtProj.hide(); this.rtProj.hide();
this.rbProj.hide(); this.rbProj.hide();
} }
} else if (a === 'stroke-width' && typeof v === "number") { } else if (a === 'stroke-width' && typeof v === 'number') {
this._attr(a, v, n); this._attr(a, v, n);
this.updateThickness(); this.updateThickness();
} else if (a === 'data-z-order' && typeof v !== 'undefined') { } else if (a === 'data-z-order' && typeof v !== 'undefined') {
this._attr(a, v, n); this._attr(a, v, n);
[this.face, this.left, this.dorsal, this.right, ...this.getEdges(), ...this.getGrabPoints()] [this.face, this.left, this.dorsal, this.right, ...this.getEdges(), ...this.getGrabPoints()].forEach(
.forEach((el) => {if (el) el.attr(a, v, n)}) (el) => {
if (el) el.attr(a, v, n);
},
);
} else { } else {
return this._attr(a, v, n); return this._attr(a, v, n);
} }
@ -856,7 +931,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
}, },
updateThickness() { updateThickness() {
const edges = [this.frontLeftEdge, this.frontRightEdge, this.frontTopEdge, this.frontBotEdge] const edges = [this.frontLeftEdge, this.frontRightEdge, this.frontTopEdge, this.frontBotEdge];
const width = this.attr('stroke-width'); const width = this.attr('stroke-width');
edges.forEach((edge: SVG.Element) => { edges.forEach((edge: SVG.Element) => {
edge.attr('stroke-width', width * (this.strokeOffset || consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH)); edge.attr('stroke-width', width * (this.strokeOffset || consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH));
@ -864,14 +939,15 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.on('mouseover', () => { this.on('mouseover', () => {
edges.forEach((edge: SVG.Element) => { edges.forEach((edge: SVG.Element) => {
this.strokeOffset = this.node.classList.contains('cvat_canvas_shape_activated') this.strokeOffset = this.node.classList.contains('cvat_canvas_shape_activated')
? consts.CUBOID_ACTIVE_EDGE_STROKE_WIDTH : consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH; ? consts.CUBOID_ACTIVE_EDGE_STROKE_WIDTH
: consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH;
edge.attr('stroke-width', width * this.strokeOffset); edge.attr('stroke-width', width * this.strokeOffset);
}) });
}).on('mouseout', () => { }).on('mouseout', () => {
edges.forEach((edge: SVG.Element) => { edges.forEach((edge: SVG.Element) => {
this.strokeOffset = consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH; this.strokeOffset = consts.CUBOID_UNACTIVE_EDGE_STROKE_WIDTH;
edge.attr('stroke-width', width * this.strokeOffset); edge.attr('stroke-width', width * this.strokeOffset);
}) });
}); });
}, },
@ -889,18 +965,12 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.dorsalRightEdge.stroke({ color: strokeColor }); this.dorsalRightEdge.stroke({ color: strokeColor });
this.dorsalLeftEdge.stroke({ color: strokeColor }); this.dorsalLeftEdge.stroke({ color: strokeColor });
this.bot.stroke({ color: strokeColor }) this.bot.stroke({ color: strokeColor }).fill({ color: fillColor });
.fill({ color: fillColor }); this.top.stroke({ color: strokeColor }).fill({ color: fillColor });
this.top.stroke({ color: strokeColor }) this.face.stroke({ color: strokeColor, width: 0 }).fill({ color: fillColor });
.fill({ color: fillColor }); this.right.stroke({ color: strokeColor }).fill({ color: fillColor });
this.face.stroke({ color: strokeColor, width: 0 }) this.dorsal.stroke({ color: strokeColor }).fill({ color: fillColor });
.fill({ color: fillColor }); this.left.stroke({ color: strokeColor }).fill({ color: fillColor });
this.right.stroke({ color: strokeColor })
.fill({ color: fillColor });
this.dorsal.stroke({ color: strokeColor })
.fill({ color: fillColor });
this.left.stroke({ color: strokeColor })
.fill({ color: fillColor });
}, },
dmove(dx: number, dy: number) { dmove(dx: number, dy: number) {
@ -954,9 +1024,13 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
this.updateView(); this.updateView();
// to correct getting of points in resizedone, dragdone // to correct getting of points in resizedone, dragdone
this._attr('points', this.cuboidModel this._attr(
'points',
this.cuboidModel
.getPoints() .getPoints()
.reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '')
.trim(),
);
}, },
computeHeightFace(point: Point, index: number) { computeHeightFace(point: Point, index: number) {
@ -1037,14 +1111,18 @@ function getTopDown(edgeIndex: EdgeIndex): number[] {
updateProjections() { updateProjections() {
const viewModel = this.cuboidModel; const viewModel = this.cuboidModel;
this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), this.ftProj.plot(
viewModel.ft.points[0], viewModel.vpl)); this.updateProjectionLine(viewModel.ft.getEquation(), viewModel.ft.points[0], viewModel.vpl),
this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), );
viewModel.ft.points[0], viewModel.vpl)); this.fbProj.plot(
this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), this.updateProjectionLine(viewModel.fb.getEquation(), viewModel.ft.points[0], viewModel.vpl),
viewModel.rt.points[1], viewModel.vpr)); );
this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), this.rtProj.plot(
viewModel.rt.points[1], viewModel.vpr)); this.updateProjectionLine(viewModel.rt.getEquation(), viewModel.rt.points[1], viewModel.vpr),
);
this.rbProj.plot(
this.updateProjectionLine(viewModel.rb.getEquation(), viewModel.rt.points[1], viewModel.vpr),
);
}, },
updateGrabPoints() { updateGrabPoints() {

@ -5,14 +5,9 @@
import * as SVG from 'svg.js'; import * as SVG from 'svg.js';
import consts from './consts'; import consts from './consts';
import { import { translateToSVG } from './shared';
translateToSVG,
} from './shared';
import {
Geometry,
} from './canvasModel';
import { Geometry } from './canvasModel';
export interface ZoomHandler { export interface ZoomHandler {
zoom(): void; zoom(): void;
@ -35,10 +30,7 @@ export class ZoomHandlerImpl implements ZoomHandler {
private onSelectStart(event: MouseEvent): void { private onSelectStart(event: MouseEvent): void {
if (!this.selectionRect && event.which === 1) { if (!this.selectionRect && event.which === 1) {
const point = translateToSVG( const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
(this.canvas.node as any as SVGSVGElement),
[event.clientX, event.clientY],
);
this.startSelectionPoint = { this.startSelectionPoint = {
x: point[0], x: point[0],
y: point[1], y: point[1],
@ -52,16 +44,15 @@ export class ZoomHandlerImpl implements ZoomHandler {
} }
} }
private getSelectionBox(event: MouseEvent): { private getSelectionBox(
event: MouseEvent,
): {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
} { } {
const point = translateToSVG( const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
(this.canvas.node as any as SVGSVGElement),
[event.clientX, event.clientY],
);
const stopSelectionPoint = { const stopSelectionPoint = {
x: point[0], x: point[0],
y: point[1], y: point[1],

@ -15,7 +15,5 @@
"cvat-canvas.node": ["dist/cvat-canvas.node"] "cvat-canvas.node": ["dist/cvat-canvas.node"]
} }
}, },
"include": [ "include": ["src/typescript/*.ts"]
"src/typescript/*.ts"
]
} }

@ -1,11 +1,12 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* eslint-disable */ // eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path'); const path = require('path');
const DtsBundleWebpack = require('dts-bundle-webpack')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const DtsBundleWebpack = require('dts-bundle-webpack');
const nodeConfig = { const nodeConfig = {
target: 'node', target: 'node',
@ -22,7 +23,8 @@ const nodeConfig = {
extensions: ['.ts', '.js', '.json'], extensions: ['.ts', '.js', '.json'],
}, },
module: { module: {
rules: [{ rules: [
{
test: /\.ts$/, test: /\.ts$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
@ -30,25 +32,29 @@ const nodeConfig = {
options: { options: {
plugins: [ plugins: [
'@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining' '@babel/plugin-proposal-optional-chaining',
],
presets: [
['@babel/preset-env'],
['@babel/typescript'],
], ],
presets: [['@babel/preset-env'], ['@babel/typescript']],
sourceType: 'unambiguous', sourceType: 'unambiguous',
}, },
}, },
}, { },
{
test: /\.(css|scss)$/, test: /\.(css|scss)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['style-loader', { use: [
'style-loader',
{
loader: 'css-loader', loader: 'css-loader',
options: { options: {
importLoaders: 2, importLoaders: 2,
}, },
}, 'postcss-loader', 'sass-loader'] },
}], 'postcss-loader',
'sass-loader',
],
},
],
}, },
plugins: [ plugins: [
new DtsBundleWebpack({ new DtsBundleWebpack({
@ -56,7 +62,7 @@ const nodeConfig = {
main: 'dist/declaration/src/typescript/canvas.d.ts', main: 'dist/declaration/src/typescript/canvas.d.ts',
out: '../cvat-canvas.node.d.ts', out: '../cvat-canvas.node.d.ts',
}), }),
] ],
}; };
const webConfig = { const webConfig = {
@ -82,7 +88,8 @@ const webConfig = {
extensions: ['.ts', '.js', '.json'], extensions: ['.ts', '.js', '.json'],
}, },
module: { module: {
rules: [{ rules: [
{
test: /\.ts$/, test: /\.ts$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
@ -90,24 +97,34 @@ const webConfig = {
options: { options: {
plugins: ['@babel/plugin-proposal-class-properties'], plugins: ['@babel/plugin-proposal-class-properties'],
presets: [ presets: [
['@babel/preset-env', { [
'@babel/preset-env',
{
targets: '> 2.5%', // https://github.com/browserslist/browserslist targets: '> 2.5%', // https://github.com/browserslist/browserslist
}], },
],
['@babel/typescript'], ['@babel/typescript'],
], ],
sourceType: 'unambiguous', sourceType: 'unambiguous',
}, },
}, },
}, { },
{
test: /\.scss$/, test: /\.scss$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['style-loader', { use: [
'style-loader',
{
loader: 'css-loader', loader: 'css-loader',
options: { options: {
importLoaders: 2, importLoaders: 2,
}, },
}, 'postcss-loader', 'sass-loader'] },
}], 'postcss-loader',
'sass-loader',
],
},
],
}, },
plugins: [ plugins: [
new DtsBundleWebpack({ new DtsBundleWebpack({
@ -115,7 +132,7 @@ const webConfig = {
main: 'dist/declaration/src/typescript/canvas.d.ts', main: 'dist/declaration/src/typescript/canvas.d.ts',
out: '../cvat-canvas.d.ts', out: '../cvat-canvas.d.ts',
}), }),
] ],
}; };
module.exports = [webConfig, nodeConfig] module.exports = [webConfig, nodeConfig];

@ -1,55 +1,48 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
"env": { env: {
"node": false, node: false,
"browser": true, browser: true,
"es6": true, es6: true,
"jquery": true, jquery: true,
"qunit": true, qunit: true,
}, },
"parserOptions": { parserOptions: {
"parser": "babel-eslint", parser: 'babel-eslint',
"sourceType": "module", sourceType: 'module',
"ecmaVersion": 2018, 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',
}, },
"plugins": [
"security",
"no-unsanitized",
"no-unsafe-innerhtml",
],
"extends": [
"eslint:recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"airbnb-base",
], ],
"rules": { 'no-plusplus': [0],
"no-await-in-loop": [0], 'no-param-reassign': [0],
"global-require": [0], 'no-underscore-dangle': ['error', { allowAfterThis: true }],
"no-new": [0], 'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
"class-methods-use-this": [0], 'no-continue': [0],
"no-restricted-properties": [0, { 'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
"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. // This rule actual for user input data on the node.js environment mainly.
"security/detect-object-injection": 0, 'security/detect-object-injection': 0,
"indent": ["warn", 4], indent: ['warn', 4],
"no-useless-constructor": 0, 'no-useless-constructor': 0,
"func-names": [0], 'func-names': [0],
"valid-typeof": [0], 'valid-typeof': [0],
"no-console": [0], // this rule deprecates console.log, console.warn etc. because "it is not good in production code" 'no-console': [0],
"max-classes-per-file": [0], 'max-classes-per-file': [0],
'max-len': ['warn', { code: 120 }],
}, },
}; };

@ -1,40 +1,47 @@
# Module CVAT-CORE # Module CVAT-CORE
## Description ## Description
This CVAT module is a client-side JavaScipt library to management of objects, frames, logs, etc. This CVAT module is a client-side JavaScipt library to management of objects, frames, logs, etc.
It contains the core logic of the Computer Vision Annotation Tool. It contains the core logic of the Computer Vision Annotation Tool.
## Versioning ## Versioning
If you make changes in this package, please do following: If you make changes in this package, please do following:
- After not important changes (typos, backward compatible bug fixes, refactoring) do: ``npm version patch`` - After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch`
- After changing API (backward compatible new features) do: ``npm version minor`` - After changing API (backward compatible new features) do: `npm version minor`
- After changing API (changes that break backward compatibility) do: ``npm version major`` - After changing API (changes that break backward compatibility) do: `npm version major`
### Commands ### Commands
- Dependencies installation - Dependencies installation
```bash ```bash
npm install npm install
``` ```
- Building the module from sources in the ```dist``` directory: - Building the module from sources in the `dist` directory:
```bash ```bash
npm run build npm run build
npm run build -- --mode=development # without a minification npm run build -- --mode=development # without a minification
``` ```
- Building the documentation in the ```docs``` directory: - Building the documentation in the `docs` directory:
```bash ```bash
npm run-script docs npm run-script docs
``` ```
- Running of tests: - Running of tests:
```bash ```bash
npm run-script test npm run-script test
``` ```
- Updating of a module version: - Updating of a module version:
```bash ```bash
npm version patch # updated after minor fixes npm version patch # updated after minor fixes
npm version minor # updated after major changes which don't affect API compatibility with previous versions npm version minor # updated after major changes which don't affect API compatibility with previous versions
@ -42,5 +49,6 @@ npm version major # updated after major changes which affect API compatibility
``` ```
Visual studio code configurations: Visual studio code configurations:
- cvat.js debug starts debugging with entrypoint api.js - cvat.js debug starts debugging with entrypoint api.js
- cvat.js test builds library and runs entrypoint tests.js - cvat.js test builds library and runs entrypoint tests.js

@ -1,32 +1,15 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const { defaults } = require('jest-config'); const { defaults } = require('jest-config');
module.exports = { module.exports = {
coverageDirectory: 'reports/coverage', coverageDirectory: 'reports/coverage',
coverageReporters: ['lcov'], coverageReporters: ['lcov'],
moduleFileExtensions: [ moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'],
...defaults.moduleFileExtensions, reporters: ['default', ['jest-junit', { outputDirectory: 'reports/junit' }]],
'ts', testMatch: ['**/tests/**/*.js'],
'tsx', testPathIgnorePatterns: ['/node_modules/', '/tests/mocks/*'],
],
reporters: [
'default',
['jest-junit', { outputDirectory: 'reports/junit' }],
],
testMatch: [
'**/tests/**/*.js',
],
testPathIgnorePatterns: [
'/node_modules/',
'/tests/mocks/*',
],
automock: false, automock: false,
}; };

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
plugins: [], plugins: [],

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const { const {
@ -28,18 +23,9 @@
const { checkObjectType } = require('./common'); const { checkObjectType } = require('./common');
const Statistics = require('./statistics'); const Statistics = require('./statistics');
const { Label } = require('./labels'); const { Label } = require('./labels');
const { const { DataError, ArgumentError, ScriptingError } = require('./exceptions');
DataError,
ArgumentError,
ScriptingError,
} = require('./exceptions');
const { const { HistoryActions, ObjectShape, ObjectType, colors } = require('./enums');
HistoryActions,
ObjectShape,
ObjectType,
colors,
} = require('./enums');
const ObjectState = require('./object-state'); const ObjectState = require('./object-state');
function shapeFactory(shapeData, clientID, injection) { function shapeFactory(shapeData, clientID, injection) {
@ -64,15 +50,12 @@
shapeModel = new CuboidShape(shapeData, clientID, color, injection); shapeModel = new CuboidShape(shapeData, clientID, color, injection);
break; break;
default: default:
throw new DataError( throw new DataError(`An unexpected type of shape "${type}"`);
`An unexpected type of shape "${type}"`,
);
} }
return shapeModel; return shapeModel;
} }
function trackFactory(trackData, clientID, injection) { function trackFactory(trackData, clientID, injection) {
if (trackData.shapes.length) { if (trackData.shapes.length) {
const { type } = trackData.shapes[0]; const { type } = trackData.shapes[0];
@ -96,9 +79,7 @@
trackModel = new CuboidTrack(trackData, clientID, color, injection); trackModel = new CuboidTrack(trackData, clientID, color, injection);
break; break;
default: default:
throw new DataError( throw new DataError(`An unexpected type of track "${type}"`);
`An unexpected type of track "${type}"`,
);
} }
return trackModel; return trackModel;
@ -185,18 +166,20 @@
export() { export() {
const data = { const data = {
tracks: this.tracks.filter((track) => !track.removed) tracks: this.tracks.filter((track) => !track.removed).map((track) => track.toJSON()),
.map((track) => track.toJSON()),
shapes: Object.values(this.shapes) shapes: Object.values(this.shapes)
.reduce((accumulator, value) => { .reduce((accumulator, value) => {
accumulator.push(...value); accumulator.push(...value);
return accumulator; return accumulator;
}, []).filter((shape) => !shape.removed) }, [])
.filter((shape) => !shape.removed)
.map((shape) => shape.toJSON()), .map((shape) => shape.toJSON()),
tags: Object.values(this.tags).reduce((accumulator, value) => { tags: Object.values(this.tags)
.reduce((accumulator, value) => {
accumulator.push(...value); accumulator.push(...value);
return accumulator; return accumulator;
}, []).filter((tag) => !tag.removed) }, [])
.filter((tag) => !tag.removed)
.map((tag) => tag.toJSON()), .map((tag) => tag.toJSON()),
}; };
@ -252,7 +235,7 @@
const objectsForMerge = objectStates.map((state) => { const objectsForMerge = objectStates.map((state) => {
checkObjectType('object state', state, null, ObjectState); checkObjectType('object state', state, null, ObjectState);
const object = this.objects[state.clientID]; const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') { if (typeof object === 'undefined') {
throw new ArgumentError( throw new ArgumentError(
'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it', 'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it',
); );
@ -263,15 +246,11 @@
const keyframes = {}; // frame: position const keyframes = {}; // frame: position
const { label, shapeType } = objectStates[0]; const { label, shapeType } = objectStates[0];
if (!(label.id in this.labels)) { if (!(label.id in this.labels)) {
throw new ArgumentError( throw new ArgumentError(`Unknown label for the task: ${label.id}`);
`Unknown label for the task: ${label.id}`,
);
} }
if (!Object.values(ObjectShape).includes(shapeType)) { if (!Object.values(ObjectShape).includes(shapeType)) {
throw new ArgumentError( throw new ArgumentError(`Got unknown shapeType "${shapeType}"`);
`Got unknown shapeType "${shapeType}"`,
);
} }
const labelAttributes = label.attributes.reduce((accumulator, attribute) => { const labelAttributes = label.attributes.reduce((accumulator, attribute) => {
@ -290,18 +269,14 @@
} }
if (state.shapeType !== shapeType) { if (state.shapeType !== shapeType) {
throw new ArgumentError( throw new ArgumentError(`All shapes are expected to be ${shapeType}, but got ${state.shapeType}`);
`All shapes are expected to be ${shapeType}, but got ${state.shapeType}`,
);
} }
// If this object is shape, get it position and save as a keyframe // If this object is shape, get it position and save as a keyframe
if (object instanceof Shape) { if (object instanceof Shape) {
// Frame already saved and it is not outside // Frame already saved and it is not outside
if (object.frame in keyframes && !keyframes[object.frame].outside) { if (object.frame in keyframes && !keyframes[object.frame].outside) {
throw new ArgumentError( throw new ArgumentError('Expected only one visible shape per frame');
'Expected only one visible shape per frame',
);
} }
keyframes[object.frame] = { keyframes[object.frame] = {
@ -325,9 +300,8 @@
// Push outside shape after each annotation shape // Push outside shape after each annotation shape
// Any not outside shape rewrites it // Any not outside shape rewrites it
if (!((object.frame + 1) in keyframes) && object.frame + 1 <= this.stopFrame) { if (!(object.frame + 1 in keyframes) && object.frame + 1 <= this.stopFrame) {
keyframes[object.frame + 1] = JSON keyframes[object.frame + 1] = JSON.parse(JSON.stringify(keyframes[object.frame]));
.parse(JSON.stringify(keyframes[object.frame]));
keyframes[object.frame + 1].outside = true; keyframes[object.frame + 1].outside = true;
keyframes[object.frame + 1].frame++; keyframes[object.frame + 1].frame++;
} }
@ -344,17 +318,14 @@
continue; continue;
} }
throw new ArgumentError( throw new ArgumentError('Expected only one visible shape per frame');
'Expected only one visible shape per frame',
);
} }
// We do not save an attribute if it has the same value // We do not save an attribute if it has the same value
// We save only updates // We save only updates
let updatedAttributes = false; let updatedAttributes = false;
for (const attrID in shape.attributes) { for (const attrID in shape.attributes) {
if (!(attrID in attributes) if (!(attrID in attributes) || attributes[attrID] !== shape.attributes[attrID]) {
|| attributes[attrID] !== shape.attributes[attrID]) {
updatedAttributes = true; updatedAttributes = true;
attributes[attrID] = shape.attributes[attrID]; attributes[attrID] = shape.attributes[attrID];
} }
@ -367,21 +338,22 @@
occluded: shape.occluded, occluded: shape.occluded,
outside: shape.outside, outside: shape.outside,
zOrder: shape.zOrder, zOrder: shape.zOrder,
attributes: updatedAttributes ? Object.keys(attributes) attributes: updatedAttributes
.reduce((accumulator, attrID) => { ? Object.keys(attributes).reduce((accumulator, attrID) => {
accumulator.push({ accumulator.push({
spec_id: +attrID, spec_id: +attrID,
value: attributes[attrID], value: attributes[attrID],
}); });
return accumulator; return accumulator;
}, []) : [], }, [])
: [],
}; };
} }
} else { } else {
throw new ArgumentError( throw new ArgumentError(
`Trying to merge unknown object type: ${object.constructor.name}. ` `Trying to merge unknown object type: ${object.constructor.name}. ` +
+ 'Only shapes and tracks are expected.', 'Only shapes and tracks are expected.',
); );
} }
} }
@ -399,13 +371,15 @@
const clientID = ++this.count; const clientID = ++this.count;
const track = { const track = {
frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)), frame: Math.min.apply(
null,
Object.keys(keyframes).map((frame) => +frame),
),
shapes: Object.values(keyframes), shapes: Object.values(keyframes),
group: 0, group: 0,
source: objectStates[0].source, source: objectStates[0].source,
label_id: label.id, label_id: label.id,
attributes: Object.keys(objectStates[0].attributes) attributes: Object.keys(objectStates[0].attributes).reduce((accumulator, attrID) => {
.reduce((accumulator, attrID) => {
if (!labelAttributes[attrID].mutable) { if (!labelAttributes[attrID].mutable) {
accumulator.push({ accumulator.push({
spec_id: +attrID, spec_id: +attrID,
@ -426,20 +400,23 @@
object.removed = true; object.removed = true;
} }
this.history.do(HistoryActions.MERGED_OBJECTS, () => { this.history.do(
HistoryActions.MERGED_OBJECTS,
() => {
trackModel.removed = true; trackModel.removed = true;
for (const object of objectsForMerge) { for (const object of objectsForMerge) {
object.removed = false; object.removed = false;
} }
}, () => { },
() => {
trackModel.removed = false; trackModel.removed = false;
for (const object of objectsForMerge) { for (const object of objectsForMerge) {
object.removed = true; object.removed = true;
} }
}, [ },
...objectsForMerge [...objectsForMerge.map((object) => object.clientID), trackModel.clientID],
.map((object) => object.clientID), trackModel.clientID, objectStates[0].frame,
], objectStates[0].frame); );
} }
split(objectState, frame) { split(objectState, frame) {
@ -447,10 +424,8 @@
checkObjectType('frame', frame, 'integer', null); checkObjectType('frame', frame, 'integer', null);
const object = this.objects[objectState.clientID]; const object = this.objects[objectState.clientID];
if (typeof (object) === 'undefined') { if (typeof object === 'undefined') {
throw new ArgumentError( throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before');
'The object has not been saved yet. Call annotations.put([state]) before',
);
} }
if (objectState.objectType !== ObjectType.TRACK) { if (objectState.objectType !== ObjectType.TRACK) {
@ -474,8 +449,7 @@
occluded: objectState.occluded, occluded: objectState.occluded,
outside: objectState.outside, outside: objectState.outside,
zOrder: objectState.zOrder, zOrder: objectState.zOrder,
attributes: Object.keys(objectState.attributes) attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => {
.reduce((accumulator, attrID) => {
if (!labelAttributes[attrID].mutable) { if (!labelAttributes[attrID].mutable) {
accumulator.push({ accumulator.push({
spec_id: +attrID, spec_id: +attrID,
@ -526,15 +500,21 @@
// Remove source object // Remove source object
object.removed = true; object.removed = true;
this.history.do(HistoryActions.SPLITTED_TRACK, () => { this.history.do(
HistoryActions.SPLITTED_TRACK,
() => {
object.removed = false; object.removed = false;
prevTrack.removed = true; prevTrack.removed = true;
nextTrack.removed = true; nextTrack.removed = true;
}, () => { },
() => {
object.removed = true; object.removed = true;
prevTrack.removed = false; prevTrack.removed = false;
nextTrack.removed = false; nextTrack.removed = false;
}, [object.clientID, prevTrack.clientID, nextTrack.clientID], frame); },
[object.clientID, prevTrack.clientID, nextTrack.clientID],
frame,
);
} }
group(objectStates, reset) { group(objectStates, reset) {
@ -543,10 +523,8 @@
const objectsForGroup = objectStates.map((state) => { const objectsForGroup = objectStates.map((state) => {
checkObjectType('object state', state, null, ObjectState); checkObjectType('object state', state, null, ObjectState);
const object = this.objects[state.clientID]; const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') { if (typeof object === 'undefined') {
throw new ArgumentError( throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before');
'The object has not been saved yet. Call annotations.put([state]) before',
);
} }
return object; return object;
}); });
@ -558,15 +536,21 @@
} }
const redoGroups = objectsForGroup.map((object) => object.group); const redoGroups = objectsForGroup.map((object) => object.group);
this.history.do(HistoryActions.GROUPED_OBJECTS, () => { this.history.do(
HistoryActions.GROUPED_OBJECTS,
() => {
objectsForGroup.forEach((object, idx) => { objectsForGroup.forEach((object, idx) => {
object.group = undoGroups[idx]; object.group = undoGroups[idx];
}); });
}, () => { },
() => {
objectsForGroup.forEach((object, idx) => { objectsForGroup.forEach((object, idx) => {
object.group = redoGroups[idx]; object.group = redoGroups[idx];
}); });
}, objectsForGroup.map((object) => object.clientID), objectStates[0].frame); },
objectsForGroup.map((object) => object.clientID),
objectStates[0].frame,
);
return groupIdx; return groupIdx;
} }
@ -629,9 +613,7 @@
} else if (object instanceof Tag) { } else if (object instanceof Tag) {
objectType = 'tag'; objectType = 'tag';
} else { } else {
throw new ScriptingError( throw new ScriptingError(`Unexpected object type: "${objectType}"`);
`Unexpected object type: "${objectType}"`,
);
} }
const label = object.label.name; const label = object.label.name;
@ -645,7 +627,8 @@
if (objectType === 'track') { if (objectType === 'track') {
const keyframes = Object.keys(object.shapes) const keyframes = Object.keys(object.shapes)
.sort((a, b) => +a - +b).map((el) => +el); .sort((a, b) => +a - +b)
.map((el) => +el);
let prevKeyframe = keyframes[0]; let prevKeyframe = keyframes[0];
let visible = false; let visible = false;
@ -680,7 +663,7 @@
for (const label of Object.keys(labels)) { for (const label of Object.keys(labels)) {
for (const key of Object.keys(labels[label])) { for (const key of Object.keys(labels[label])) {
if (typeof (labels[label][key]) === 'object') { if (typeof labels[label][key] === 'object') {
for (const objectType of Object.keys(labels[label][key])) { for (const objectType of Object.keys(labels[label][key])) {
total[key][objectType] += labels[label][key][objectType]; total[key][objectType] += labels[label][key][objectType];
} }
@ -723,8 +706,7 @@
checkObjectType('state attributes', state.attributes, null, Object); checkObjectType('state attributes', state.attributes, null, Object);
checkObjectType('state label', state.label, null, Label); checkObjectType('state label', state.label, null, Label);
const attributes = Object.keys(state.attributes) const attributes = Object.keys(state.attributes).reduce(convertAttributes.bind(state), []);
.reduce(convertAttributes.bind(state), []);
const labelAttributes = state.label.attributes.reduce((accumulator, attribute) => { const labelAttributes = state.label.attributes.reduce((accumulator, attribute) => {
accumulator[attribute.id] = attribute; accumulator[attribute.id] = attribute;
return accumulator; return accumulator;
@ -749,8 +731,7 @@
if (!Object.values(ObjectShape).includes(state.shapeType)) { if (!Object.values(ObjectShape).includes(state.shapeType)) {
throw new ArgumentError( throw new ArgumentError(
'Object shape must be one of: ' `Object shape must be one of: ${JSON.stringify(Object.values(ObjectShape))}`,
+ `${JSON.stringify(Object.values(ObjectShape))}`,
); );
} }
@ -768,27 +749,26 @@
}); });
} else if (state.objectType === 'track') { } else if (state.objectType === 'track') {
constructed.tracks.push({ constructed.tracks.push({
attributes: attributes attributes: attributes.filter((attr) => !labelAttributes[attr.spec_id].mutable),
.filter((attr) => !labelAttributes[attr.spec_id].mutable),
frame: state.frame, frame: state.frame,
group: 0, group: 0,
source: state.source, source: state.source,
label_id: state.label.id, label_id: state.label.id,
shapes: [{ shapes: [
attributes: attributes {
.filter((attr) => labelAttributes[attr.spec_id].mutable), attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable),
frame: state.frame, frame: state.frame,
occluded: state.occluded || false, occluded: state.occluded || false,
outside: false, outside: false,
points: [...state.points], points: [...state.points],
type: state.shapeType, type: state.shapeType,
z_order: state.zOrder, z_order: state.zOrder,
}], },
],
}); });
} else { } else {
throw new ArgumentError( throw new ArgumentError(
'Object type must be one of: ' `Object type must be one of: ${JSON.stringify(Object.values(ObjectType))}`,
+ `${JSON.stringify(Object.values(ObjectType))}`,
); );
} }
} }
@ -796,20 +776,24 @@
// Add constructed objects to a collection // Add constructed objects to a collection
const imported = this.import(constructed); const imported = this.import(constructed);
const importedArray = imported.tags const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes);
.concat(imported.tracks)
.concat(imported.shapes);
if (objectStates.length) { if (objectStates.length) {
this.history.do(HistoryActions.CREATED_OBJECTS, () => { this.history.do(
HistoryActions.CREATED_OBJECTS,
() => {
importedArray.forEach((object) => { importedArray.forEach((object) => {
object.removed = true; object.removed = true;
}); });
}, () => { },
() => {
importedArray.forEach((object) => { importedArray.forEach((object) => {
object.removed = false; object.removed = false;
}); });
}, importedArray.map((object) => object.clientID), objectStates[0].frame); },
importedArray.map((object) => object.clientID),
objectStates[0].frame,
);
} }
return importedArray.map((value) => value.clientID); return importedArray.map((value) => value.clientID);
@ -829,14 +813,11 @@
} }
const object = this.objects[state.clientID]; const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') { if (typeof object === 'undefined') {
throw new ArgumentError( throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before');
'The object has not been saved yet. Call annotations.put([state]) before',
);
} }
const distance = object.constructor.distance(state.points, x, y); const distance = object.constructor.distance(state.points, x, y);
if (distance !== null && (minimumDistance === null if (distance !== null && (minimumDistance === null || distance < minimumDistance)) {
|| distance < minimumDistance)) {
minimumDistance = distance; minimumDistance = distance;
minimumState = state; minimumState = state;
} }
@ -850,12 +831,8 @@
searchEmpty(frameFrom, frameTo) { searchEmpty(frameFrom, frameTo) {
const sign = Math.sign(frameTo - frameFrom); const sign = Math.sign(frameTo - frameFrom);
const predicate = sign > 0 const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo;
? (frame) => frame <= frameTo const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1;
: (frame) => frame >= frameTo;
const update = sign > 0
? (frame) => frame + 1
: (frame) => frame - 1;
for (let frame = frameFrom; predicate(frame); frame = update(frame)) { for (let frame = frameFrom; predicate(frame); frame = update(frame)) {
if (frame in this.shapes && this.shapes[frame].some((shape) => !shape.removed)) { if (frame in this.shapes && this.shapes[frame].some((shape) => !shape.removed)) {
continue; continue;
@ -890,9 +867,9 @@
const sign = Math.sign(frameTo - frameFrom); const sign = Math.sign(frameTo - frameFrom);
const flattenedQuery = groups.flat(Number.MAX_SAFE_INTEGER); const flattenedQuery = groups.flat(Number.MAX_SAFE_INTEGER);
const containsDifficultProperties = flattenedQuery const containsDifficultProperties = flattenedQuery.some(
.some((fragment) => fragment (fragment) => fragment.match(/^width/) || fragment.match(/^height/),
.match(/^width/) || fragment.match(/^height/)); );
const deepSearch = (deepSearchFrom, deepSearchTo) => { const deepSearch = (deepSearchFrom, deepSearchTo) => {
// deepSearchFrom is expected to be a frame that doesn't satisfy a filter // deepSearchFrom is expected to be a frame that doesn't satisfy a filter
@ -915,12 +892,8 @@
}; };
const keyframesMemory = {}; const keyframesMemory = {};
const predicate = sign > 0 const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo;
? (frame) => frame <= frameTo const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1;
: (frame) => frame >= frameTo;
const update = sign > 0
? (frame) => frame + 1
: (frame) => frame - 1;
for (let frame = frameFrom; predicate(frame); frame = update(frame)) { for (let frame = frameFrom; predicate(frame); frame = update(frame)) {
// First prepare all data for the frame // First prepare all data for the frame
// Consider all shapes, tags, and not outside tracks that have keyframe here // Consider all shapes, tags, and not outside tracks that have keyframe here
@ -934,15 +907,9 @@
.map((tag) => tag.get(frame)), .map((tag) => tag.get(frame)),
); );
const tracks = Object.values(this.tracks) const tracks = Object.values(this.tracks)
.filter((track) => ( .filter((track) => frame in track.shapes || frame === frameFrom || frame === frameTo)
frame in track.shapes .filter((track) => !track.removed);
|| frame === frameFrom statesData.push(...tracks.map((track) => track.get(frame)).filter((state) => !state.outside));
|| frame === frameTo
)).filter((track) => !track.removed);
statesData.push(
...tracks.map((track) => track.get(frame))
.filter((state) => !state.outside),
);
// Nothing to filtering, go to the next iteration // Nothing to filtering, go to the next iteration
if (!statesData.length) { if (!statesData.length) {
@ -963,12 +930,8 @@
for (const track of tracks) { for (const track of tracks) {
const trackIsSatisfy = filtered.includes(track.clientID); const trackIsSatisfy = filtered.includes(track.clientID);
if (!trackIsSatisfy) { if (!trackIsSatisfy) {
keyframesMemory[track.clientID] = [ keyframesMemory[track.clientID] = [filtered.includes(track.clientID), frame];
filtered.includes(track.clientID), } else if (keyframesMemory[track.clientID] && keyframesMemory[track.clientID][0] === false) {
frame,
];
} else if (keyframesMemory[track.clientID]
&& keyframesMemory[track.clientID][0] === false) {
withDeepSearch = true; withDeepSearch = true;
} }
} }
@ -976,9 +939,7 @@
if (withDeepSearch) { if (withDeepSearch) {
const reducer = sign > 0 ? Math.min : Math.max; const reducer = sign > 0 ? Math.min : Math.max;
const deepSearchFrom = reducer( const deepSearchFrom = reducer(...Object.values(keyframesMemory).map((value) => value[1]));
...Object.values(keyframesMemory).map((value) => value[1]),
);
return deepSearch(deepSearchFrom, frame); return deepSearch(deepSearchFrom, frame);
} }

@ -1,20 +1,11 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const jsonpath = require('jsonpath'); const jsonpath = require('jsonpath');
const { const { AttributeType, ObjectType } = require('./enums');
AttributeType,
ObjectType,
} = require('./enums');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
class AnnotationsFilter { class AnnotationsFilter {
constructor() { constructor() {
// eslint-disable-next-line security/detect-unsafe-regex // eslint-disable-next-line security/detect-unsafe-regex
@ -47,8 +38,7 @@ class AnnotationsFilter {
if (operators.includes(expression[i])) { if (operators.includes(expression[i])) {
if (!nestedCounter) { if (!nestedCounter) {
const subexpression = expression const subexpression = expression.substr(start + 1, i - start - 1).trim();
.substr(start + 1, i - start - 1).trim();
splitted.push(subexpression); splitted.push(subexpression);
splitted.push(expression[i]); splitted.push(expression[i]);
start = i; start = i;
@ -56,18 +46,14 @@ class AnnotationsFilter {
} }
} }
const subexpression = expression const subexpression = expression.substr(start + 1).trim();
.substr(start + 1).trim();
splitted.push(subexpression); splitted.push(subexpression);
splitted.forEach((internalExpression) => { splitted.forEach((internalExpression) => {
if (internalExpression === '|' || internalExpression === '&') { if (internalExpression === '|' || internalExpression === '&') {
container.push(internalExpression); container.push(internalExpression);
} else { } else {
this._groupByBrackets( this._groupByBrackets(container, internalExpression);
container,
internalExpression,
);
} }
}); });
} }
@ -103,12 +89,8 @@ class AnnotationsFilter {
endBracket = i; endBracket = i;
const subcontainer = []; const subcontainer = [];
const subexpression = expression const subexpression = expression.substr(startBracket + 1, endBracket - 1 - startBracket);
.substr(startBracket + 1, endBracket - 1 - startBracket); this._splitWithOperator(subcontainer, subexpression);
this._splitWithOperator(
subcontainer,
subexpression,
);
container.push(subcontainer); container.push(subcontainer);
@ -136,7 +118,7 @@ class AnnotationsFilter {
for (const group of groups) { for (const group of groups) {
if (Array.isArray(group)) { if (Array.isArray(group)) {
expression += `(${this._join(group)})`; expression += `(${this._join(group)})`;
} else if (typeof (group) === 'string') { } else if (typeof group === 'string') {
// it can be operator or expression // it can be operator or expression
if (group === '|' || group === '&') { if (group === '|' || group === '&') {
expression += group; expression += group;
@ -158,8 +140,7 @@ class AnnotationsFilter {
_convertObjects(statesData) { _convertObjects(statesData) {
const objects = statesData.map((state) => { const objects = statesData.map((state) => {
const labelAttributes = state.label.attributes const labelAttributes = state.label.attributes.reduce((acc, attr) => {
.reduce((acc, attr) => {
acc[attr.id] = attr; acc[attr.id] = attr;
return acc; return acc;
}, {}); }, {});
@ -172,10 +153,12 @@ class AnnotationsFilter {
if (state.objectType !== ObjectType.TAG) { if (state.objectType !== ObjectType.TAG) {
state.points.forEach((coord, idx) => { state.points.forEach((coord, idx) => {
if (idx % 2) { // y if (idx % 2) {
// y
ytl = Math.min(ytl, coord); ytl = Math.min(ytl, coord);
ybr = Math.max(ybr, coord); ybr = Math.max(ybr, coord);
} else { // x } else {
// x
xtl = Math.min(xtl, coord); xtl = Math.min(xtl, coord);
xbr = Math.max(xbr, coord); xbr = Math.max(xbr, coord);
} }
@ -216,7 +199,7 @@ class AnnotationsFilter {
toJSONQuery(filters) { toJSONQuery(filters) {
try { try {
if (!Array.isArray(filters) || filters.some((value) => typeof (value) !== 'string')) { if (!Array.isArray(filters) || filters.some((value) => typeof value !== 'string')) {
throw Error('Argument must be an array of strings'); throw Error('Argument must be an array of strings');
} }
@ -225,7 +208,10 @@ class AnnotationsFilter {
} }
const groups = []; const groups = [];
const expression = filters.map((filter) => `(${filter})`).join('|').replace(/\\"/g, '`'); const expression = filters
.map((filter) => `(${filter})`)
.join('|')
.replace(/\\"/g, '`');
this._splitWithOperator(groups, expression); this._splitWithOperator(groups, expression);
return [groups, `$.objects[?(${this._join(groups)})].clientID`]; return [groups, `$.objects[?(${this._join(groups)})].clientID`];
} catch (error) { } catch (error) {

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
const MAX_HISTORY_LENGTH = 128; const MAX_HISTORY_LENGTH = 128;

@ -1,32 +1,13 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const ObjectState = require('./object-state'); const ObjectState = require('./object-state');
const { const { checkObjectType } = require('./common');
checkObjectType, const { colors, Source, ObjectShape, ObjectType, AttributeType, HistoryActions } = require('./enums');
} = require('./common');
const { const { DataError, ArgumentError, ScriptingError } = require('./exceptions');
colors,
Source,
ObjectShape,
ObjectType,
AttributeType,
HistoryActions,
} = require('./enums');
const {
DataError,
ArgumentError,
ScriptingError,
} = require('./exceptions');
const { Label } = require('./labels'); const { Label } = require('./labels');
@ -48,38 +29,26 @@
function checkNumberOfPoints(shapeType, points) { function checkNumberOfPoints(shapeType, points) {
if (shapeType === ObjectShape.RECTANGLE) { if (shapeType === ObjectShape.RECTANGLE) {
if (points.length / 2 !== 2) { if (points.length / 2 !== 2) {
throw new DataError( throw new DataError(`Rectangle must have 2 points, but got ${points.length / 2}`);
`Rectangle must have 2 points, but got ${points.length / 2}`,
);
} }
} else if (shapeType === ObjectShape.POLYGON) { } else if (shapeType === ObjectShape.POLYGON) {
if (points.length / 2 < 3) { if (points.length / 2 < 3) {
throw new DataError( throw new DataError(`Polygon must have at least 3 points, but got ${points.length / 2}`);
`Polygon must have at least 3 points, but got ${points.length / 2}`,
);
} }
} else if (shapeType === ObjectShape.POLYLINE) { } else if (shapeType === ObjectShape.POLYLINE) {
if (points.length / 2 < 2) { if (points.length / 2 < 2) {
throw new DataError( throw new DataError(`Polyline must have at least 2 points, but got ${points.length / 2}`);
`Polyline must have at least 2 points, but got ${points.length / 2}`,
);
} }
} else if (shapeType === ObjectShape.POINTS) { } else if (shapeType === ObjectShape.POINTS) {
if (points.length / 2 < 1) { if (points.length / 2 < 1) {
throw new DataError( throw new DataError(`Points must have at least 1 points, but got ${points.length / 2}`);
`Points must have at least 1 points, but got ${points.length / 2}`,
);
} }
} else if (shapeType === ObjectShape.CUBOID) { } else if (shapeType === ObjectShape.CUBOID) {
if (points.length / 2 !== 8) { if (points.length / 2 !== 8) {
throw new DataError( throw new DataError(`Points must have exact 8 points, but got ${points.length / 2}`);
`Points must have exact 8 points, but got ${points.length / 2}`,
);
} }
} else { } else {
throw new ArgumentError( throw new ArgumentError(`Unknown value of shapeType has been recieved ${shapeType}`);
`Unknown value of shapeType has been recieved ${shapeType}`,
);
} }
} }
@ -104,10 +73,7 @@
} }
if (shapeType === ObjectShape.POLYLINE) { if (shapeType === ObjectShape.POLYLINE) {
const length = Math.max( const length = Math.max(xmax - xmin, ymax - ymin);
xmax - xmin,
ymax - ymin,
);
return length >= MIN_SHAPE_LENGTH; return length >= MIN_SHAPE_LENGTH;
} }
@ -126,10 +92,7 @@
checkObjectType('coordinate', x, 'number', null); checkObjectType('coordinate', x, 'number', null);
checkObjectType('coordinate', y, 'number', null); checkObjectType('coordinate', y, 'number', null);
fittedPoints.push( fittedPoints.push(Math.clamp(x, 0, maxX), Math.clamp(y, 0, maxY));
Math.clamp(x, 0, maxX),
Math.clamp(y, 0, maxY),
);
} }
return shapeType === ObjectShape.CUBOID ? points : fittedPoints; return shapeType === ObjectShape.CUBOID ? points : fittedPoints;
@ -149,15 +112,12 @@
const { values } = attr; const { values } = attr;
const type = attr.inputType; const type = attr.inputType;
if (typeof (value) !== 'string') { if (typeof value !== 'string') {
throw new ArgumentError( throw new ArgumentError(`Attribute value is expected to be string, but got ${typeof value}`);
`Attribute value is expected to be string, but got ${typeof (value)}`,
);
} }
if (type === AttributeType.NUMBER) { if (type === AttributeType.NUMBER) {
return +value >= +values[0] return +value >= +values[0] && +value <= +values[1];
&& +value <= +values[1];
} }
if (type === AttributeType.CHECKBOX) { if (type === AttributeType.CHECKBOX) {
@ -190,17 +150,18 @@
attributeAccumulator[attr.spec_id] = attr.value; attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator; return attributeAccumulator;
}, {}); }, {});
this.groupObject = Object.defineProperties({}, { this.groupObject = Object.defineProperties(
{},
{
color: { color: {
get: () => { get: () => {
if (this.group) { if (this.group) {
return this.groupColors[this.group] return this.groupColors[this.group] || colors[this.group % colors.length];
|| colors[this.group % colors.length];
} }
return defaultGroupColor; return defaultGroupColor;
}, },
set: (newColor) => { set: (newColor) => {
if (this.group && typeof (newColor) === 'string' && /^#[0-9A-F]{6}$/i.test(newColor)) { if (this.group && typeof newColor === 'string' && /^#[0-9A-F]{6}$/i.test(newColor)) {
this.groupColors[this.group] = newColor; this.groupColors[this.group] = newColor;
} }
}, },
@ -208,7 +169,8 @@
id: { id: {
get: () => this.group, get: () => this.group,
}, },
}); },
);
this.appendDefaultAttributes(this.label); this.appendDefaultAttributes(this.label);
injection.groups.max = Math.max(injection.groups.max, this.group); injection.groups.max = Math.max(injection.groups.max, this.group);
@ -218,13 +180,19 @@
const undoLock = this.lock; const undoLock = this.lock;
const redoLock = lock; const redoLock = lock;
this.history.do(HistoryActions.CHANGED_LOCK, () => { this.history.do(
HistoryActions.CHANGED_LOCK,
() => {
this.lock = undoLock; this.lock = undoLock;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.lock = redoLock; this.lock = redoLock;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.lock = lock; this.lock = lock;
} }
@ -233,13 +201,19 @@
const undoColor = this.color; const undoColor = this.color;
const redoColor = color; const redoColor = color;
this.history.do(HistoryActions.CHANGED_COLOR, () => { this.history.do(
HistoryActions.CHANGED_COLOR,
() => {
this.color = undoColor; this.color = undoColor;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.color = redoColor; this.color = redoColor;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.color = color; this.color = color;
} }
@ -248,13 +222,19 @@
const undoHidden = this.hidden; const undoHidden = this.hidden;
const redoHidden = hidden; const redoHidden = hidden;
this.history.do(HistoryActions.CHANGED_HIDDEN, () => { this.history.do(
HistoryActions.CHANGED_HIDDEN,
() => {
this.hidden = undoHidden; this.hidden = undoHidden;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.hidden = redoHidden; this.hidden = redoHidden;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.hidden = hidden; this.hidden = hidden;
} }
@ -268,15 +248,21 @@
this.appendDefaultAttributes(label); this.appendDefaultAttributes(label);
const redoAttributes = { ...this.attributes }; const redoAttributes = { ...this.attributes };
this.history.do(HistoryActions.CHANGED_LABEL, () => { this.history.do(
HistoryActions.CHANGED_LABEL,
() => {
this.label = undoLabel; this.label = undoLabel;
this.attributes = undoAttributes; this.attributes = undoAttributes;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.label = redoLabel; this.label = redoLabel;
this.attributes = redoAttributes; this.attributes = redoAttributes;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
_saveAttributes(attributes, frame) { _saveAttributes(attributes, frame) {
@ -288,13 +274,19 @@
const redoAttributes = { ...this.attributes }; const redoAttributes = { ...this.attributes };
this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => { this.history.do(
HistoryActions.CHANGED_ATTRIBUTES,
() => {
this.attributes = undoAttributes; this.attributes = undoAttributes;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.attributes = redoAttributes; this.attributes = redoAttributes;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
_validateStateBeforeSave(frame, data, updated) { _validateStateBeforeSave(frame, data, updated) {
@ -304,8 +296,7 @@
checkObjectType('label', data.label, null, Label); checkObjectType('label', data.label, null, Label);
} }
const labelAttributes = data.label.attributes const labelAttributes = data.label.attributes.reduce((accumulator, value) => {
.reduce((accumulator, value) => {
accumulator[value.id] = value; accumulator[value.id] = value;
return accumulator; return accumulator;
}, {}); }, {});
@ -334,9 +325,7 @@
const { width, height } = this.frameMeta[frame]; const { width, height } = this.frameMeta[frame];
fittedPoints = fitPoints(this.shapeType, data.points, width, height); fittedPoints = fitPoints(this.shapeType, data.points, width, height);
if ((!checkShapeArea(this.shapeType, fittedPoints)) if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) {
|| checkOutside(fittedPoints, width, height)
) {
fittedPoints = []; fittedPoints = [];
} }
} }
@ -364,9 +353,7 @@
if (updated.color) { if (updated.color) {
checkObjectType('color', data.color, 'string', null); checkObjectType('color', data.color, 'string', null);
if (!/^#[0-9A-F]{6}$/i.test(data.color)) { if (!/^#[0-9A-F]{6}$/i.test(data.color)) {
throw new ArgumentError( throw new ArgumentError(`Got invalid color value: "${data.color}"`);
`Got invalid color value: "${data.color}"`,
);
} }
} }
@ -396,9 +383,17 @@
} }
updateTimestamp(updated) { updateTimestamp(updated) {
const anyChanges = updated.label || updated.attributes || updated.points const anyChanges =
|| updated.outside || updated.occluded || updated.keyframe updated.label ||
|| updated.zOrder || updated.hidden || updated.lock || updated.pinned; updated.attributes ||
updated.points ||
updated.outside ||
updated.occluded ||
updated.keyframe ||
updated.zOrder ||
updated.hidden ||
updated.lock ||
updated.pinned;
if (anyChanges) { if (anyChanges) {
this.updated = Date.now(); this.updated = Date.now();
@ -409,14 +404,20 @@
if (!this.lock || force) { if (!this.lock || force) {
this.removed = true; this.removed = true;
this.history.do(HistoryActions.REMOVED_OBJECT, () => { this.history.do(
HistoryActions.REMOVED_OBJECT,
() => {
this.serverID = undefined; this.serverID = undefined;
this.removed = false; this.removed = false;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.removed = true; this.removed = true;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
return this.removed; return this.removed;
@ -436,33 +437,33 @@
const undoPinned = this.pinned; const undoPinned = this.pinned;
const redoPinned = pinned; const redoPinned = pinned;
this.history.do(HistoryActions.CHANGED_PINNED, () => { this.history.do(
HistoryActions.CHANGED_PINNED,
() => {
this.pinned = undoPinned; this.pinned = undoPinned;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.pinned = redoPinned; this.pinned = redoPinned;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.pinned = pinned; this.pinned = pinned;
} }
save() { save() {
throw new ScriptingError( throw new ScriptingError('Is not implemented');
'Is not implemented',
);
} }
get() { get() {
throw new ScriptingError( throw new ScriptingError('Is not implemented');
'Is not implemented',
);
} }
toJSON() { toJSON() {
throw new ScriptingError( throw new ScriptingError('Is not implemented');
'Is not implemented',
);
} }
} }
@ -501,9 +502,7 @@
// Method is used to construct ObjectState objects // Method is used to construct ObjectState objects
get(frame) { get(frame) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new ScriptingError( throw new ScriptingError('Got frame is not equal to the frame of the shape');
'Got frame is not equal to the frame of the shape',
);
} }
return { return {
@ -533,15 +532,21 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_POINTS, () => { this.history.do(
HistoryActions.CHANGED_POINTS,
() => {
this.points = undoPoints; this.points = undoPoints;
this.source = undoSource; this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.points = redoPoints; this.points = redoPoints;
this.source = redoSource; this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.source = Source.MANUAL; this.source = Source.MANUAL;
this.points = points; this.points = points;
@ -553,15 +558,21 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_OCCLUDED, () => { this.history.do(
HistoryActions.CHANGED_OCCLUDED,
() => {
this.occluded = undoOccluded; this.occluded = undoOccluded;
this.source = undoSource; this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.occluded = redoOccluded; this.occluded = redoOccluded;
this.source = redoSource; this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.source = Source.MANUAL; this.source = Source.MANUAL;
this.occluded = occluded; this.occluded = occluded;
@ -573,15 +584,21 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_ZORDER, () => { this.history.do(
HistoryActions.CHANGED_ZORDER,
() => {
this.zOrder = undoZOrder; this.zOrder = undoZOrder;
this.source = undoSource; this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.zOrder = redoZOrder; this.zOrder = redoZOrder;
this.source = redoSource; this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
this.source = Source.MANUAL; this.source = Source.MANUAL;
this.zOrder = zOrder; this.zOrder = zOrder;
@ -589,9 +606,7 @@
save(frame, data) { save(frame, data) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new ScriptingError( throw new ScriptingError('Got frame is not equal to the frame of the shape');
'Got frame is not equal to the frame of the shape',
);
} }
if (this.lock && data.lock) { if (this.lock && data.lock) {
@ -696,8 +711,8 @@
z_order: this.shapes[frame].zOrder, z_order: this.shapes[frame].zOrder,
points: [...this.shapes[frame].points], points: [...this.shapes[frame].points],
outside: this.shapes[frame].outside, outside: this.shapes[frame].outside,
attributes: Object.keys(this.shapes[frame].attributes) attributes: Object.keys(this.shapes[frame].attributes).reduce(
.reduce((attributeAccumulator, attrId) => { (attributeAccumulator, attrId) => {
if (labelAttributes[attrId].mutable) { if (labelAttributes[attrId].mutable) {
attributeAccumulator.push({ attributeAccumulator.push({
spec_id: attrId, spec_id: attrId,
@ -706,7 +721,9 @@
} }
return attributeAccumulator; return attributeAccumulator;
}, []), },
[],
),
id: this.shapes[frame].serverID, id: this.shapes[frame].serverID,
frame: +frame, frame: +frame,
}); });
@ -718,12 +735,7 @@
// Method is used to construct ObjectState objects // Method is used to construct ObjectState objects
get(frame) { get(frame) {
const { const { prev, next, first, last } = this.boundedKeyframes(frame);
prev,
next,
first,
last,
} = this.boundedKeyframes(frame);
return { return {
...this.getPosition(frame, prev, next), ...this.getPosition(frame, prev, next),
@ -838,27 +850,32 @@
})), })),
}; };
this.history.do(HistoryActions.CHANGED_LABEL, () => { this.history.do(
HistoryActions.CHANGED_LABEL,
() => {
this.label = undoLabel; this.label = undoLabel;
this.attributes = undoAttributes.unmutable; this.attributes = undoAttributes.unmutable;
for (const mutable of undoAttributes.mutable) { for (const mutable of undoAttributes.mutable) {
this.shapes[mutable.frame].attributes = mutable.attributes; this.shapes[mutable.frame].attributes = mutable.attributes;
} }
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.label = redoLabel; this.label = redoLabel;
this.attributes = redoAttributes.unmutable; this.attributes = redoAttributes.unmutable;
for (const mutable of redoAttributes.mutable) { for (const mutable of redoAttributes.mutable) {
this.shapes[mutable.frame].attributes = mutable.attributes; this.shapes[mutable.frame].attributes = mutable.attributes;
} }
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
_saveAttributes(attributes, frame) { _saveAttributes(attributes, frame) {
const current = this.get(frame); const current = this.get(frame);
const labelAttributes = this.label.attributes const labelAttributes = this.label.attributes.reduce((accumulator, value) => {
.reduce((accumulator, value) => {
accumulator[value.id] = value; accumulator[value.id] = value;
return accumulator; return accumulator;
}, {}); }, {});
@ -873,13 +890,14 @@
if (!labelAttributes[attrID].mutable) { if (!labelAttributes[attrID].mutable) {
redoAttributes[attrID] = attributes[attrID]; redoAttributes[attrID] = attributes[attrID];
} else if (attributes[attrID] !== current.attributes[attrID]) { } else if (attributes[attrID] !== current.attributes[attrID]) {
mutableAttributesUpdated = mutableAttributesUpdated mutableAttributesUpdated =
mutableAttributesUpdated ||
// not keyframe yet // not keyframe yet
|| !(frame in this.shapes) !(frame in this.shapes) ||
// keyframe, but without this attrID // keyframe, but without this attrID
|| !(attrID in this.shapes[frame].attributes) !(attrID in this.shapes[frame].attributes) ||
// keyframe with attrID, but with another value // keyframe with attrID, but with another value
|| (this.shapes[frame].attributes[attrID] !== attributes[attrID]); this.shapes[frame].attributes[attrID] !== attributes[attrID];
} }
} }
let redoShape; let redoShape;
@ -904,8 +922,7 @@
} }
for (const attrID of Object.keys(attributes)) { for (const attrID of Object.keys(attributes)) {
if (labelAttributes[attrID].mutable if (labelAttributes[attrID].mutable && attributes[attrID] !== current.attributes[attrID]) {
&& attributes[attrID] !== current.attributes[attrID]) {
redoShape.attributes[attrID] = attributes[attrID]; redoShape.attributes[attrID] = attributes[attrID];
} }
} }
@ -915,7 +932,9 @@
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
} }
this.history.do(HistoryActions.CHANGED_ATTRIBUTES, () => { this.history.do(
HistoryActions.CHANGED_ATTRIBUTES,
() => {
this.attributes = undoAttributes; this.attributes = undoAttributes;
if (undoShape) { if (undoShape) {
this.shapes[frame] = undoShape; this.shapes[frame] = undoShape;
@ -923,19 +942,23 @@
delete this.shapes[frame]; delete this.shapes[frame];
} }
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
this.attributes = redoAttributes; this.attributes = redoAttributes;
if (redoShape) { if (redoShape) {
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
} }
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
_appendShapeActionToHistory( _appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource) {
actionType, frame, undoShape, redoShape, undoSource, redoSource, this.history.do(
) { actionType,
this.history.do(actionType, () => { () => {
if (!undoShape) { if (!undoShape) {
delete this.shapes[frame]; delete this.shapes[frame];
} else { } else {
@ -943,7 +966,8 @@
} }
this.source = undoSource; this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { },
() => {
if (!redoShape) { if (!redoShape) {
delete this.shapes[frame]; delete this.shapes[frame];
} else { } else {
@ -951,7 +975,10 @@
} }
this.source = redoSource; this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); },
[this.clientID],
frame,
);
} }
_savePoints(points, frame) { _savePoints(points, frame) {
@ -960,7 +987,9 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : { const redoShape = wasKeyframe
? { ...this.shapes[frame], points }
: {
frame, frame,
points, points,
zOrder: current.zOrder, zOrder: current.zOrder,
@ -987,7 +1016,9 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : { const redoShape = wasKeyframe
? { ...this.shapes[frame], outside }
: {
frame, frame,
outside, outside,
zOrder: current.zOrder, zOrder: current.zOrder,
@ -1014,7 +1045,9 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : { const redoShape = wasKeyframe
? { ...this.shapes[frame], occluded }
: {
frame, frame,
occluded, occluded,
zOrder: current.zOrder, zOrder: current.zOrder,
@ -1041,7 +1074,9 @@
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : { const redoShape = wasKeyframe
? { ...this.shapes[frame], zOrder }
: {
frame, frame,
zOrder, zOrder,
occluded: current.occluded, occluded: current.occluded,
@ -1066,15 +1101,15 @@
const current = this.get(frame); const current = this.get(frame);
const wasKeyframe = frame in this.shapes; const wasKeyframe = frame in this.shapes;
if ((keyframe && wasKeyframe) if ((keyframe && wasKeyframe) || (!keyframe && !wasKeyframe)) {
|| (!keyframe && !wasKeyframe)) {
return; return;
} }
const undoSource = this.source; const undoSource = this.source;
const redoSource = Source.MANUAL; const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = keyframe ? { const redoShape = keyframe
? {
frame, frame,
zOrder: current.zOrder, zOrder: current.zOrder,
points: current.points, points: current.points,
@ -1082,7 +1117,8 @@
occluded: current.occluded, occluded: current.occluded,
attributes: {}, attributes: {},
source: current.source, source: current.source,
} : undefined; }
: undefined;
this.source = Source.MANUAL; this.source = Source.MANUAL;
if (redoShape) { if (redoShape) {
@ -1196,8 +1232,8 @@
} }
throw new DataError( throw new DataError(
'No one left position or right position was found. ' 'No one left position or right position was found. ' +
+ `Interpolation impossible. Client ID: ${this.clientID}`, `Interpolation impossible. Client ID: ${this.clientID}`,
); );
} }
} }
@ -1230,9 +1266,7 @@
// Method is used to construct ObjectState objects // Method is used to construct ObjectState objects
get(frame) { get(frame) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new ScriptingError( throw new ScriptingError('Got frame is not equal to the frame of the shape');
'Got frame is not equal to the frame of the shape',
);
} }
return { return {
@ -1252,9 +1286,7 @@
save(frame, data) { save(frame, data) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new ScriptingError( throw new ScriptingError('Got frame is not equal to the frame of the tag');
'Got frame is not equal to the frame of the tag',
);
} }
if (this.lock && data.lock) { if (this.lock && data.lock) {
@ -1324,7 +1356,7 @@
static distance(points, x, y) { static distance(points, x, y) {
function position(x1, y1, x2, y2) { function position(x1, y1, x2, y2) {
return ((x2 - x1) * (y - y1) - (x - x1) * (y2 - y1)); return (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1);
} }
let wn = 0; let wn = 0;
@ -1356,8 +1388,8 @@
// Find the shortest distance from point to an edge // Find the shortest distance from point to an edge
// Get an equation of a line in general // Get an equation of a line in general
const aCoef = (y1 - y2); const aCoef = y1 - y2;
const bCoef = (x2 - x1); const bCoef = x2 - x1;
// Vector (aCoef, bCoef) is a perpendicular to line // Vector (aCoef, bCoef) is a perpendicular to line
// Now find the point where two lines // Now find the point where two lines
@ -1365,13 +1397,9 @@
const xCross = x - aCoef; const xCross = x - aCoef;
const yCross = y - bCoef; const yCross = y - bCoef;
if (((xCross - x1) * (x2 - xCross)) >= 0 if ((xCross - x1) * (x2 - xCross) >= 0 && (yCross - y1) * (y2 - yCross) >= 0) {
&& ((yCross - y1) * (y2 - yCross)) >= 0) {
// Cross point is on segment between p1(x1,y1) and p2(x2,y2) // Cross point is on segment between p1(x1,y1) and p2(x2,y2)
distances.push(Math.sqrt( distances.push(Math.sqrt(Math.pow(x - xCross, 2) + Math.pow(y - yCross, 2)));
Math.pow(x - xCross, 2)
+ Math.pow(y - yCross, 2),
));
} else { } else {
distances.push( distances.push(
Math.min( Math.min(
@ -1409,12 +1437,12 @@
const y2 = points[i + 3]; const y2 = points[i + 3];
// Find the shortest distance from point to an edge // Find the shortest distance from point to an edge
if (((x - x1) * (x2 - x)) >= 0 && ((y - y1) * (y2 - y)) >= 0) { if ((x - x1) * (x2 - x) >= 0 && (y - y1) * (y2 - y) >= 0) {
// Find the length of a perpendicular // Find the length of a perpendicular
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
distances.push( distances.push(
Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / Math Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) /
.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)),
); );
} else { } else {
// The link below works for lines (which have infinit length) // The link below works for lines (which have infinit length)
@ -1447,9 +1475,7 @@
const x1 = points[i]; const x1 = points[i];
const y1 = points[i + 1]; const y1 = points[i + 1];
distances.push( distances.push(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)));
Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)),
);
} }
return Math.min.apply(null, distances); return Math.min.apply(null, distances);
@ -1499,11 +1525,13 @@
} }
lowerHull.pop(); lowerHull.pop();
if (upperHull.length if (
=== 1 && lowerHull.length upperHull.length === 1 &&
=== 1 && upperHull[0].x lowerHull.length === 1 &&
=== lowerHull[0].x && upperHull[0].y upperHull[0].x === lowerHull[0].x &&
=== lowerHull[0].y) return upperHull; upperHull[0].y === lowerHull[0].y
)
return upperHull;
return upperHull.concat(lowerHull); return upperHull.concat(lowerHull);
} }
@ -1522,7 +1550,7 @@
static contain(points, x, y) { static contain(points, x, y) {
function isLeft(P0, P1, P2) { function isLeft(P0, P1, P2) {
return ((P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y)); return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y);
} }
points = CuboidShape.makeHull(points); points = CuboidShape.makeHull(points);
let wn = 0; let wn = 0;
@ -1561,15 +1589,15 @@
const p2 = points[i + 1] || points[0]; const p2 = points[i + 1] || points[0];
// perpendicular from point to straight length // perpendicular from point to straight length
const distance = (Math.abs((p2.y - p1.y) * x const distance =
- (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x)) 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)); Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2));
// check if perpendicular belongs to the straight segment // check if perpendicular belongs to the straight segment
const a = Math.pow(p1.x - x, 2) + Math.pow(p1.y - y, 2); 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 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 c = Math.pow(p2.x - x, 2) + Math.pow(p2.y - y, 2);
if (distance < minDistance && (a + b - c) >= 0 && (c + b - a) >= 0) { if (distance < minDistance && a + b - c >= 0 && c + b - a >= 0) {
minDistance = distance; minDistance = distance;
} }
} }
@ -1588,14 +1616,10 @@
} }
interpolatePosition(leftPosition, rightPosition, offset) { interpolatePosition(leftPosition, rightPosition, offset) {
const positionOffset = leftPosition.points.map((point, index) => ( const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point);
rightPosition.points[index] - point
));
return { return {
points: leftPosition.points.map((point, index) => ( points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset),
point + positionOffset[index] * offset
)),
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
outside: leftPosition.outside, outside: leftPosition.outside,
zOrder: leftPosition.zOrder, zOrder: leftPosition.zOrder,
@ -1683,7 +1707,8 @@
function matchRightLeft(leftCurve, rightCurve, leftRightMatching) { function matchRightLeft(leftCurve, rightCurve, leftRightMatching) {
const matchedRightPoints = Object.values(leftRightMatching).flat(); const matchedRightPoints = Object.values(leftRightMatching).flat();
const unmatchedRightPoints = rightCurve.map((_, index) => index) const unmatchedRightPoints = rightCurve
.map((_, index) => index)
.filter((index) => !matchedRightPoints.includes(index)); .filter((index) => !matchedRightPoints.includes(index));
const updatedMatching = { ...leftRightMatching }; const updatedMatching = { ...leftRightMatching };
@ -1693,8 +1718,7 @@
} }
for (const key of Object.keys(updatedMatching)) { for (const key of Object.keys(updatedMatching)) {
const sortedRightIndexes = updatedMatching[key] const sortedRightIndexes = updatedMatching[key].sort((a, b) => a - b);
.sort((a, b) => a - b);
updatedMatching[key] = sortedRightIndexes; updatedMatching[key] = sortedRightIndexes;
} }
@ -1717,9 +1741,7 @@
} }
function computeDistance(point1, point2) { function computeDistance(point1, point2) {
return Math.sqrt( return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2);
((point1.x - point2.x) ** 2) + ((point1.y - point2.y) ** 2),
);
} }
function minimizeSegment(baseLength, N, startInterpolated, stopInterpolated) { function minimizeSegment(baseLength, N, startInterpolated, stopInterpolated) {
@ -1727,9 +1749,7 @@
const minimized = [interpolatedPoints[startInterpolated]]; const minimized = [interpolatedPoints[startInterpolated]];
let latestPushed = startInterpolated; let latestPushed = startInterpolated;
for (let i = startInterpolated + 1; i < stopInterpolated; i++) { for (let i = startInterpolated + 1; i < stopInterpolated; i++) {
const distance = computeDistance( const distance = computeDistance(interpolatedPoints[latestPushed], interpolatedPoints[i]);
interpolatedPoints[latestPushed], interpolatedPoints[i],
);
if (distance >= threshold) { if (distance >= threshold) {
minimized.push(interpolatedPoints[i]); minimized.push(interpolatedPoints[i]);
@ -1773,9 +1793,7 @@
const baseLength = curveLength(leftPoints.slice(start, stop + 1)); const baseLength = curveLength(leftPoints.slice(start, stop + 1));
const N = stop - start + 1; const N = stop - start + 1;
reduced.push( reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated));
...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated),
);
} }
function rightSegment(leftPoint) { function rightSegment(leftPoint) {
@ -1786,9 +1804,7 @@
const baseLength = curveLength(rightPoints.slice(start, stop + 1)); const baseLength = curveLength(rightPoints.slice(start, stop + 1));
const N = stop - start + 1; const N = stop - start + 1;
reduced.push( reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated));
...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated),
);
} }
let previousOpened = null; let previousOpened = null;
@ -1844,12 +1860,11 @@
const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints)); const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints));
const matching = matchLeftRight(leftOffsetVec, rightOffsetVec); const matching = matchLeftRight(leftOffsetVec, rightOffsetVec);
const completedMatching = matchRightLeft( const completedMatching = matchRightLeft(leftOffsetVec, rightOffsetVec, matching);
leftOffsetVec, rightOffsetVec, matching,
);
const interpolatedPoints = Object.keys(completedMatching) const interpolatedPoints = Object.keys(completedMatching)
.map((leftPointIdx) => +leftPointIdx).sort((a, b) => a - b) .map((leftPointIdx) => +leftPointIdx)
.sort((a, b) => a - b)
.reduce((acc, leftPointIdx) => { .reduce((acc, leftPointIdx) => {
const leftPoint = leftPoints[leftPointIdx]; const leftPoint = leftPoints[leftPointIdx];
for (const rightPointIdx of completedMatching[leftPointIdx]) { for (const rightPointIdx of completedMatching[leftPointIdx]) {
@ -1863,12 +1878,7 @@
return acc; return acc;
}, []); }, []);
const reducedPoints = reduceInterpolation( const reducedPoints = reduceInterpolation(interpolatedPoints, completedMatching, leftPoints, rightPoints);
interpolatedPoints,
completedMatching,
leftPoints,
rightPoints,
);
return { return {
points: toArray(reducedPoints), points: toArray(reducedPoints),
@ -1899,8 +1909,7 @@
points: [...rightPosition.points, rightPosition.points[0], rightPosition.points[1]], points: [...rightPosition.points, rightPosition.points[0], rightPosition.points[1]],
}; };
const result = PolyTrack.prototype.interpolatePosition const result = PolyTrack.prototype.interpolatePosition.call(this, copyLeft, copyRight, offset);
.call(this, copyLeft, copyRight, offset);
return { return {
...result, ...result,
@ -1961,14 +1970,10 @@
} }
interpolatePosition(leftPosition, rightPosition, offset) { interpolatePosition(leftPosition, rightPosition, offset) {
const positionOffset = leftPosition.points.map((point, index) => ( const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point);
rightPosition.points[index] - point
));
return { return {
points: leftPosition.points.map((point, index) => ( points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset),
point + positionOffset[index] * offset
)),
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
outside: leftPosition.outside, outside: leftPosition.outside,
zOrder: leftPosition.zOrder, zOrder: leftPosition.zOrder,

@ -1,16 +1,11 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const { Task } = require('./session'); const { Task } = require('./session');
const { ScriptingError } = ('./exceptions'); const { ScriptingError } = './exceptions';
class AnnotationsSaver { class AnnotationsSaver {
constructor(version, collection, session) { constructor(version, collection, session) {
@ -53,12 +48,7 @@
} }
async _request(data, action) { async _request(data, action) {
const result = await serverProxy.annotations.updateAnnotations( const result = await serverProxy.annotations.updateAnnotations(this.sessionType, this.id, data, action);
this.sessionType,
this.id,
data,
action,
);
return result; return result;
} }
@ -102,27 +92,37 @@
}, },
}; };
const keys = ['id', 'label_id', 'group', 'frame', const keys = [
'occluded', 'z_order', 'points', 'type', 'shapes', 'id',
'attributes', 'value', 'spec_id', 'source', 'outside']; 'label_id',
'group',
'frame',
'occluded',
'z_order',
'points',
'type',
'shapes',
'attributes',
'value',
'spec_id',
'source',
'outside',
];
// Find created and updated objects // Find created and updated objects
for (const type of Object.keys(exported)) { for (const type of Object.keys(exported)) {
for (const object of exported[type]) { for (const object of exported[type]) {
if (object.id in this.initialObjects[type]) { if (object.id in this.initialObjects[type]) {
const exportedHash = JSON.stringify(object, keys); const exportedHash = JSON.stringify(object, keys);
const initialHash = JSON.stringify( const initialHash = JSON.stringify(this.initialObjects[type][object.id], keys);
this.initialObjects[type][object.id], keys,
);
if (exportedHash !== initialHash) { if (exportedHash !== initialHash) {
splitted.updated[type].push(object); splitted.updated[type].push(object);
} }
} else if (typeof (object.id) === 'undefined') { } else if (typeof object.id === 'undefined') {
splitted.created[type].push(object); splitted.created[type].push(object);
} else { } else {
throw new ScriptingError( throw new ScriptingError(
`Id of object is defined "${object.id}"` `Id of object is defined "${object.id}" but it absents in initial state`,
+ 'but it absents in initial state',
); );
} }
} }
@ -144,21 +144,17 @@
} }
} }
return splitted; return splitted;
} }
_updateCreatedObjects(saved, indexes) { _updateCreatedObjects(saved, indexes) {
const savedLength = saved.tracks.length const savedLength = saved.tracks.length + saved.shapes.length + saved.tags.length;
+ saved.shapes.length + saved.tags.length;
const indexesLength = indexes.tracks.length const indexesLength = indexes.tracks.length + indexes.shapes.length + indexes.tags.length;
+ indexes.shapes.length + indexes.tags.length;
if (indexesLength !== savedLength) { if (indexesLength !== savedLength) {
throw new ScriptingError( throw new ScriptingError(
'Number of indexes is differed by number of saved objects' `Number of indexes is differed by number of saved objects ${indexesLength} vs ${savedLength}`,
+ `${indexesLength} vs ${savedLength}`,
); );
} }
@ -180,7 +176,9 @@
}; };
// Remove them from the request body // Remove them from the request body
exported.tracks.concat(exported.shapes).concat(exported.tags) exported.tracks
.concat(exported.shapes)
.concat(exported.tags)
.map((value) => { .map((value) => {
delete value.clientID; delete value.clientID;
return value; return value;
@ -214,11 +212,7 @@
} }
} }
} else { } else {
const { const { created, updated, deleted } = this._split(exported);
created,
updated,
deleted,
} = this._split(exported);
onUpdate('Created objects are being saved on the server'); onUpdate('Created objects are being saved on the server');
const indexes = this._receiveIndexes(created); const indexes = this._receiveIndexes(created);

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
@ -14,15 +9,8 @@
const AnnotationsHistory = require('./annotations-history'); const AnnotationsHistory = require('./annotations-history');
const { checkObjectType } = require('./common'); const { checkObjectType } = require('./common');
const { Task } = require('./session'); const { Task } = require('./session');
const { const { Loader, Dumper } = require('./annotation-formats');
Loader, const { ScriptingError, DataError, ArgumentError } = require('./exceptions');
Dumper,
} = require('./annotation-formats.js');
const {
ScriptingError,
DataError,
ArgumentError,
} = require('./exceptions');
const jobCache = new WeakMap(); const jobCache = new WeakMap();
const taskCache = new WeakMap(); const taskCache = new WeakMap();
@ -36,9 +24,7 @@
return jobCache; return jobCache;
} }
throw new ScriptingError( throw new ScriptingError(`Unknown session type was received ${sessionType}`);
`Unknown session type was received ${sessionType}`,
);
} }
async function getAnnotationsFromServer(session) { async function getAnnotationsFromServer(session) {
@ -46,8 +32,7 @@
const cache = getCache(sessionType); const cache = getCache(sessionType);
if (!cache.has(session)) { if (!cache.has(session)) {
const rawAnnotations = await serverProxy.annotations const rawAnnotations = await serverProxy.annotations.getAnnotations(sessionType, session.id);
.getAnnotations(sessionType, session.id);
// Get meta information about frames // Get meta information about frames
const startFrame = sessionType === 'job' ? session.startFrame : 0; const startFrame = sessionType === 'job' ? session.startFrame : 0;
@ -242,28 +227,22 @@
async function uploadAnnotations(session, file, loader) { async function uploadAnnotations(session, file, loader) {
const sessionType = session instanceof Task ? 'task' : 'job'; const sessionType = session instanceof Task ? 'task' : 'job';
if (!(loader instanceof Loader)) { if (!(loader instanceof Loader)) {
throw new ArgumentError( throw new ArgumentError('A loader must be instance of Loader class');
'A loader must be instance of Loader class',
);
} }
await serverProxy.annotations.uploadAnnotations(sessionType, session.id, file, loader.name); await serverProxy.annotations.uploadAnnotations(sessionType, session.id, file, loader.name);
} }
async function dumpAnnotations(session, name, dumper) { async function dumpAnnotations(session, name, dumper) {
if (!(dumper instanceof Dumper)) { if (!(dumper instanceof Dumper)) {
throw new ArgumentError( throw new ArgumentError('A dumper must be instance of Dumper class');
'A dumper must be instance of Dumper class',
);
} }
let result = null; let result = null;
const sessionType = session instanceof Task ? 'task' : 'job'; const sessionType = session instanceof Task ? 'task' : 'job';
if (sessionType === 'job') { if (sessionType === 'job') {
result = await serverProxy.annotations result = await serverProxy.annotations.dumpAnnotations(session.task.id, name, dumper.name);
.dumpAnnotations(session.task.id, name, dumper.name);
} else { } else {
result = await serverProxy.annotations result = await serverProxy.annotations.dumpAnnotations(session.id, name, dumper.name);
.dumpAnnotations(session.id, name, dumper.name);
} }
return result; return result;
@ -297,19 +276,14 @@
async function exportDataset(session, format) { async function exportDataset(session, format) {
if (!(format instanceof String || typeof format === 'string')) { if (!(format instanceof String || typeof format === 'string')) {
throw new ArgumentError( throw new ArgumentError('Format must be a string');
'Format must be a string',
);
} }
if (!(session instanceof Task)) { if (!(session instanceof Task)) {
throw new ArgumentError( throw new ArgumentError('A dataset can only be created from a task');
'A dataset can only be created from a task',
);
} }
let result = null; let result = null;
result = await serverProxy.tasks result = await serverProxy.tasks.exportDataset(session.id, format);
.exportDataset(session.id, format);
return result; return result;
} }

@ -1,30 +1,17 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */
/* global
require:false
*/
(() => { (() => {
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const lambdaManager = require('./lambda-manager'); const lambdaManager = require('./lambda-manager');
const { const { isBoolean, isInteger, isEnum, isString, checkFilter } = require('./common');
isBoolean,
isInteger,
isEnum,
isString,
checkFilter,
} = require('./common');
const { TaskStatus, TaskMode } = require('./enums'); const { TaskStatus, TaskMode } = require('./enums');
const User = require('./user'); const User = require('./user');
const { AnnotationFormats } = require('./annotation-formats.js'); const { AnnotationFormats } = require('./annotation-formats');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
const { Task } = require('./session'); const { Task } = require('./session');
@ -79,10 +66,24 @@
return result; return result;
}; };
cvat.server.register.implementation = async (username, firstName, lastName, cvat.server.register.implementation = async (
email, password1, password2, userConfirmations) => { username,
const user = await serverProxy.server.register(username, firstName, firstName,
lastName, email, password1, password2, userConfirmations); lastName,
email,
password1,
password2,
userConfirmations,
) => {
const user = await serverProxy.server.register(
username,
firstName,
lastName,
email,
password1,
password2,
userConfirmations,
);
return new User(user); return new User(user);
}; };
@ -95,9 +96,7 @@
await serverProxy.server.logout(); await serverProxy.server.logout();
}; };
cvat.server.changePassword.implementation = async ( cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
oldPassword, newPassword1, newPassword2,
) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
}; };
@ -105,9 +104,7 @@
await serverProxy.server.requestPasswordReset(email); await serverProxy.server.requestPasswordReset(email);
}; };
cvat.server.resetPassword.implementation = async ( cvat.server.resetPassword.implementation = async (newPassword1, newPassword2, uid, token) => {
newPassword1, newPassword2, uid, token,
) => {
await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token); await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token);
}; };
@ -144,16 +141,12 @@
jobID: isInteger, jobID: isInteger,
}); });
if (('taskID' in filter) && ('jobID' in filter)) { if ('taskID' in filter && 'jobID' in filter) {
throw new ArgumentError( throw new ArgumentError('Only one of fields "taskID" and "jobID" allowed simultaneously');
'Only one of fields "taskID" and "jobID" allowed simultaneously',
);
} }
if (!Object.keys(filter).length) { if (!Object.keys(filter).length) {
throw new ArgumentError( throw new ArgumentError('Job filter must not be empty');
'Job filter must not be empty',
);
} }
let tasks = null; let tasks = null;
@ -161,19 +154,17 @@
tasks = await serverProxy.tasks.getTasks(`id=${filter.taskID}`); tasks = await serverProxy.tasks.getTasks(`id=${filter.taskID}`);
} else { } else {
const job = await serverProxy.jobs.getJob(filter.jobID); const job = await serverProxy.jobs.getJob(filter.jobID);
if (typeof (job.task_id) !== 'undefined') { if (typeof job.task_id !== 'undefined') {
tasks = await serverProxy.tasks.getTasks(`id=${job.task_id}`); tasks = await serverProxy.tasks.getTasks(`id=${job.task_id}`);
} }
} }
// If task was found by its id, then create task instance and get Job instance from it // If task was found by its id, then create task instance and get Job instance from it
if (tasks !== null && tasks.length) { if (tasks !== null && tasks.length) {
const users = (await serverProxy.users.getUsers()) const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData));
.map((userData) => new User(userData));
const task = new Task(attachUsers(tasks[0], users)); const task = new Task(attachUsers(tasks[0], users));
return filter.jobID ? task.jobs return filter.jobID ? task.jobs.filter((job) => job.id === filter.jobID) : task.jobs;
.filter((job) => job.id === filter.jobID) : task.jobs;
} }
return []; return [];
@ -193,17 +184,13 @@
if ('search' in filter && Object.keys(filter).length > 1) { if ('search' in filter && Object.keys(filter).length > 1) {
if (!('page' in filter && Object.keys(filter).length === 2)) { if (!('page' in filter && Object.keys(filter).length === 2)) {
throw new ArgumentError( throw new ArgumentError('Do not use the filter field "search" with others');
'Do not use the filter field "search" with others',
);
} }
} }
if ('id' in filter && Object.keys(filter).length > 1) { if ('id' in filter && Object.keys(filter).length > 1) {
if (!('page' in filter && Object.keys(filter).length === 2)) { if (!('page' in filter && Object.keys(filter).length === 2)) {
throw new ArgumentError( throw new ArgumentError('Do not use the filter field "id" with others');
'Do not use the filter field "id" with others',
);
} }
} }
@ -214,13 +201,9 @@
} }
} }
const users = (await serverProxy.users.getUsers()) const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData));
.map((userData) => new User(userData));
const tasksData = await serverProxy.tasks.getTasks(searchParams.toString()); const tasksData = await serverProxy.tasks.getTasks(searchParams.toString());
const tasks = tasksData const tasks = tasksData.map((task) => attachUsers(task, users)).map((task) => new Task(task));
.map((task) => attachUsers(task, users))
.map((task) => new Task(task));
tasks.count = tasksData.count; tasks.count = tasksData.count;

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
/** /**
* External API which should be used by for development * External API which should be used by for development
@ -36,14 +31,7 @@ function build() {
Source, Source,
} = require('./enums'); } = require('./enums');
const { const { Exception, ArgumentError, DataError, ScriptingError, PluginError, ServerError } = require('./exceptions');
Exception,
ArgumentError,
DataError,
ScriptingError,
PluginError,
ServerError,
} = require('./exceptions');
const User = require('./user'); const User = require('./user');
const pjson = require('../package.json'); const pjson = require('../package.json');
@ -80,8 +68,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async about() { async about() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.about);
.apiWrapper(cvat.server.about);
return result; return result;
}, },
/** /**
@ -103,8 +90,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async share(directory = '/') { async share(directory = '/') {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.share, directory);
.apiWrapper(cvat.server.share, directory);
return result; return result;
}, },
/** /**
@ -117,8 +103,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async formats() { async formats() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.formats);
.apiWrapper(cvat.server.formats);
return result; return result;
}, },
/** /**
@ -131,8 +116,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async userAgreements() { async userAgreements() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.userAgreements);
.apiWrapper(cvat.server.userAgreements);
return result; return result;
}, },
/** /**
@ -151,7 +135,9 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async register( async register(username, firstName, lastName, email, password1, password2, userConfirmations) {
const result = await PluginRegistry.apiWrapper(
cvat.server.register,
username, username,
firstName, firstName,
lastName, lastName,
@ -159,10 +145,7 @@ function build() {
password1, password1,
password2, password2,
userConfirmations, userConfirmations,
) { );
const result = await PluginRegistry
.apiWrapper(cvat.server.register, username, firstName,
lastName, email, password1, password2, userConfirmations);
return result; return result;
}, },
/** /**
@ -176,8 +159,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async login(username, password) { async login(username, password) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.login, username, password);
.apiWrapper(cvat.server.login, username, password);
return result; return result;
}, },
/** /**
@ -189,8 +171,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async logout() { async logout() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.logout);
.apiWrapper(cvat.server.logout);
return result; return result;
}, },
/** /**
@ -205,9 +186,11 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async changePassword(oldPassword, newPassword1, newPassword2) { async changePassword(oldPassword, newPassword1, newPassword2) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(
.apiWrapper( cvat.server.changePassword,
cvat.server.changePassword, oldPassword, newPassword1, newPassword2, oldPassword,
newPassword1,
newPassword2,
); );
return result; return result;
}, },
@ -221,8 +204,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async requestPasswordReset(email) { async requestPasswordReset(email) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.requestPasswordReset, email);
.apiWrapper(cvat.server.requestPasswordReset, email);
return result; return result;
}, },
/** /**
@ -238,9 +220,13 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async resetPassword(newPassword1, newPassword2, uid, token) { async resetPassword(newPassword1, newPassword2, uid, token) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(
.apiWrapper(cvat.server.resetPassword, newPassword1, newPassword2, cvat.server.resetPassword,
uid, token); newPassword1,
newPassword2,
uid,
token,
);
return result; return result;
}, },
/** /**
@ -253,8 +239,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async authorized() { async authorized() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.authorized);
.apiWrapper(cvat.server.authorized);
return result; return result;
}, },
/** /**
@ -269,8 +254,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async request(url, data) { async request(url, data) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.server.request, url, data);
.apiWrapper(cvat.server.request, url, data);
return result; return result;
}, },
@ -322,8 +306,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async get(filter = {}) { async get(filter = {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.tasks.get, filter);
.apiWrapper(cvat.tasks.get, filter);
return result; return result;
}, },
}, },
@ -352,8 +335,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async get(filter = {}) { async get(filter = {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.jobs.get, filter);
.apiWrapper(cvat.jobs.get, filter);
return result; return result;
}, },
}, },
@ -380,8 +362,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async get(filter = {}) { async get(filter = {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.users.get, filter);
.apiWrapper(cvat.users.get, filter);
return result; return result;
}, },
}, },
@ -480,8 +461,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async list() { async list() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.plugins.list);
.apiWrapper(cvat.plugins.list);
return result; return result;
}, },
/** /**
@ -493,8 +473,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async register(plugin) { async register(plugin) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.plugins.register, plugin);
.apiWrapper(cvat.plugins.register, plugin);
return result; return result;
}, },
}, },
@ -515,8 +494,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async list() { async list() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.list);
.apiWrapper(cvat.lambda.list);
return result; return result;
}, },
@ -534,8 +512,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
async run(task, model, args) { async run(task, model, args) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.run, task, model, args);
.apiWrapper(cvat.lambda.run, task, model, args);
return result; return result;
}, },
@ -553,8 +530,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
async call(task, model, args) { async call(task, model, args) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.call, task, model, args);
.apiWrapper(cvat.lambda.call, task, model, args);
return result; return result;
}, },
@ -569,8 +545,7 @@ function build() {
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
async cancel(requestID) { async cancel(requestID) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.cancel, requestID);
.apiWrapper(cvat.lambda.cancel, requestID);
return result; return result;
}, },
@ -593,8 +568,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async listen(requestID, onChange) { async listen(requestID, onChange) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.listen, requestID, onChange);
.apiWrapper(cvat.lambda.listen, requestID, onChange);
return result; return result;
}, },
@ -607,8 +581,7 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async requests() { async requests() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper(cvat.lambda.requests);
.apiWrapper(cvat.lambda.requests);
return result; return result;
}, },
}, },

@ -1,21 +1,16 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
function isBoolean(value) { function isBoolean(value) {
return typeof (value) === 'boolean'; return typeof value === 'boolean';
} }
function isInteger(value) { function isInteger(value) {
return typeof (value) === 'number' && Number.isInteger(value); return typeof value === 'number' && Number.isInteger(value);
} }
// Called with specific Enum context // Called with specific Enum context
@ -32,20 +27,16 @@
} }
function isString(value) { function isString(value) {
return typeof (value) === 'string'; return typeof value === 'string';
} }
function checkFilter(filter, fields) { function checkFilter(filter, fields) {
for (const prop in filter) { for (const prop in filter) {
if (Object.prototype.hasOwnProperty.call(filter, prop)) { if (Object.prototype.hasOwnProperty.call(filter, prop)) {
if (!(prop in fields)) { if (!(prop in fields)) {
throw new ArgumentError( throw new ArgumentError(`Unsupported filter property has been recieved: "${prop}"`);
`Unsupported filter property has been recieved: "${prop}"`,
);
} else if (!fields[prop](filter[prop])) { } else if (!fields[prop](filter[prop])) {
throw new ArgumentError( throw new ArgumentError(`Received filter property "${prop}" is not satisfied for checker`);
`Received filter property "${prop}" is not satisfied for checker`,
);
} }
} }
} }
@ -53,28 +44,24 @@
function checkObjectType(name, value, type, instance) { function checkObjectType(name, value, type, instance) {
if (type) { if (type) {
if (typeof (value) !== type) { if (typeof value !== type) {
// specific case for integers which aren't native type in JS // specific case for integers which aren't native type in JS
if (type === 'integer' && Number.isInteger(value)) { if (type === 'integer' && Number.isInteger(value)) {
return true; return true;
} }
throw new ArgumentError( throw new ArgumentError(`"${name}" is expected to be "${type}", but "${typeof value}" has been got.`);
`"${name}" is expected to be "${type}", but "${typeof (value)}" has been got.`,
);
} }
} else if (instance) { } else if (instance) {
if (!(value instanceof instance)) { if (!(value instanceof instance)) {
if (value !== undefined) { if (value !== undefined) {
throw new ArgumentError( throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but ` `"${name}" is expected to be ${instance.name}, but ` +
+ `"${value.constructor.name}" has been got`, `"${value.constructor.name}" has been got`,
); );
} }
throw new ArgumentError( throw new ArgumentError(`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`);
`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`,
);
} }
} }

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
backendAPI: 'http://localhost:7000/api/v1', backendAPI: 'http://localhost:7000/api/v1',

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const Axios = require('axios'); const Axios = require('axios');
@ -13,7 +8,6 @@ Axios.defaults.withCredentials = true;
Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN';
Axios.defaults.xsrfCookieName = 'csrftoken'; Axios.defaults.xsrfCookieName = 'csrftoken';
onmessage = (e) => { onmessage = (e) => {
Axios.get(e.data.url, e.data.config) Axios.get(e.data.url, e.data.config)
.then((response) => { .then((response) => {

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
@ -272,11 +271,36 @@
* @readonly * @readonly
*/ */
const colors = [ const colors = [
'#33ddff', '#fa3253', '#34d1b7', '#ff007c', '#ff6037', '#ddff33', '#33ddff',
'#24b353', '#b83df5', '#66ff66', '#32b7fa', '#ffcc33', '#83e070', '#fa3253',
'#fafa37', '#5986b3', '#8c78f0', '#ff6a4d', '#f078f0', '#2a7dd1', '#34d1b7',
'#b25050', '#cc3366', '#cc9933', '#aaf0d1', '#ff00cc', '#3df53d', '#ff007c',
'#fa32b7', '#fa7dbb', '#ff355e', '#f59331', '#3d3df5', '#733380', '#ff6037',
'#ddff33',
'#24b353',
'#b83df5',
'#66ff66',
'#32b7fa',
'#ffcc33',
'#83e070',
'#fafa37',
'#5986b3',
'#8c78f0',
'#ff6a4d',
'#f078f0',
'#2a7dd1',
'#b25050',
'#cc3366',
'#cc9933',
'#aaf0d1',
'#ff00cc',
'#3df53d',
'#fa32b7',
'#fa7dbb',
'#ff355e',
'#f59331',
'#3d3df5',
'#733380',
]; ];
module.exports = { module.exports = {

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const Platform = require('platform'); const Platform = require('platform');
@ -32,15 +27,13 @@
const filename = `${info.fileName}`; const filename = `${info.fileName}`;
const line = info.lineNumber; const line = info.lineNumber;
const column = info.columnNumber; const column = info.columnNumber;
const { const { jobID, taskID, clientID } = config;
jobID,
taskID,
clientID,
} = config;
const projID = undefined; // wasn't implemented const projID = undefined; // wasn't implemented
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
system: { system: {
/** /**
* @name system * @name system
@ -141,7 +134,8 @@
*/ */
get: () => column, get: () => column,
}, },
})); }),
);
} }
/** /**
@ -246,7 +240,9 @@
constructor(message, code) { constructor(message, code) {
super(message); super(message);
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* @name code * @name code
* @type {(string|integer)} * @type {(string|integer)}
@ -257,7 +253,8 @@
code: { code: {
get: () => code, get: () => code,
}, },
})); }),
);
} }
} }

@ -1,12 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
global:false
*/
(() => { (() => {
const cvatData = require('cvat-data'); const cvatData = require('cvat-data');
@ -24,17 +18,10 @@
* @hideconstructor * @hideconstructor
*/ */
class FrameData { class FrameData {
constructor({ constructor({ width, height, name, taskID, frameNumber, startFrame, stopFrame, decodeForward }) {
width, Object.defineProperties(
height, this,
name, Object.freeze({
taskID,
frameNumber,
startFrame,
stopFrame,
decodeForward,
}) {
Object.defineProperties(this, Object.freeze({
/** /**
* @name filename * @name filename
* @type {string} * @type {string}
@ -95,7 +82,8 @@
value: decodeForward, value: decodeForward,
writable: false, writable: false,
}, },
})); }),
);
} }
/** /**
@ -111,8 +99,7 @@
* @throws {module:API.cvat.exception.PluginError} * @throws {module:API.cvat.exception.PluginError}
*/ */
async data(onServerRequest = () => {}) { async data(onServerRequest = () => {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest);
.apiWrapper.call(this, FrameData.prototype.data, onServerRequest);
return result; return result;
} }
} }
@ -136,15 +123,14 @@
const { provider } = frameDataCache[this.tid]; const { provider } = frameDataCache[this.tid];
const { chunkSize } = frameDataCache[this.tid]; const { chunkSize } = frameDataCache[this.tid];
const start = parseInt(this.number / chunkSize, 10) * chunkSize; const start = parseInt(this.number / chunkSize, 10) * chunkSize;
const stop = Math.min( const stop = Math.min(this.stopFrame, (parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1);
this.stopFrame,
(parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1,
);
const chunkNumber = Math.floor(this.number / chunkSize); const chunkNumber = Math.floor(this.number / chunkSize);
const onDecodeAll = async (frameNumber) => { const onDecodeAll = async (frameNumber) => {
if (frameDataCache[this.tid].activeChunkRequest if (
&& chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber) { frameDataCache[this.tid].activeChunkRequest &&
chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber
) {
const callbackArray = frameDataCache[this.tid].activeChunkRequest.callbacks; const callbackArray = frameDataCache[this.tid].activeChunkRequest.callbacks;
for (let i = callbackArray.length - 1; i >= 0; --i) { for (let i = callbackArray.length - 1; i >= 0; --i) {
if (callbackArray[i].frameNumber === frameNumber) { if (callbackArray[i].frameNumber === frameNumber) {
@ -160,8 +146,10 @@
}; };
const rejectRequestAll = () => { const rejectRequestAll = () => {
if (frameDataCache[this.tid].activeChunkRequest if (
&& 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) { for (const r of frameDataCache[this.tid].activeChunkRequest.callbacks) {
r.reject(r.frameNumber); r.reject(r.frameNumber);
} }
@ -172,23 +160,28 @@
const makeActiveRequest = () => { const makeActiveRequest = () => {
const taskDataCache = frameDataCache[this.tid]; const taskDataCache = frameDataCache[this.tid];
const activeChunk = taskDataCache.activeChunkRequest; const activeChunk = taskDataCache.activeChunkRequest;
activeChunk.request = serverProxy.frames.getData(this.tid, activeChunk.request = serverProxy.frames
activeChunk.chunkNumber).then((chunk) => { .getData(this.tid, activeChunk.chunkNumber)
.then((chunk) => {
frameDataCache[this.tid].activeChunkRequest.completed = true; frameDataCache[this.tid].activeChunkRequest.completed = true;
if (!taskDataCache.nextChunkRequest) { if (!taskDataCache.nextChunkRequest) {
provider.requestDecodeBlock(chunk, provider.requestDecodeBlock(
chunk,
taskDataCache.activeChunkRequest.start, taskDataCache.activeChunkRequest.start,
taskDataCache.activeChunkRequest.stop, taskDataCache.activeChunkRequest.stop,
taskDataCache.activeChunkRequest.onDecodeAll, taskDataCache.activeChunkRequest.onDecodeAll,
taskDataCache.activeChunkRequest.rejectRequestAll); taskDataCache.activeChunkRequest.rejectRequestAll,
);
} }
}).catch((exception) => { })
.catch((exception) => {
if (exception instanceof Exception) { if (exception instanceof Exception) {
reject(exception); reject(exception);
} else { } else {
reject(new Exception(exception.message)); reject(new Exception(exception.message));
} }
}).finally(() => { })
.finally(() => {
if (taskDataCache.nextChunkRequest) { if (taskDataCache.nextChunkRequest) {
if (taskDataCache.activeChunkRequest) { if (taskDataCache.activeChunkRequest) {
for (const r of taskDataCache.activeChunkRequest.callbacks) { for (const r of taskDataCache.activeChunkRequest.callbacks) {
@ -205,15 +198,19 @@
if (isNode) { if (isNode) {
resolve('Dummy data'); resolve('Dummy data');
} else if (isBrowser) { } else if (isBrowser) {
provider.frame(this.number).then((frame) => { provider
.frame(this.number)
.then((frame) => {
if (frame === null) { if (frame === null) {
onServerRequest(); onServerRequest();
const activeRequest = frameDataCache[this.tid].activeChunkRequest; const activeRequest = frameDataCache[this.tid].activeChunkRequest;
if (!provider.isChunkCached(start, stop)) { if (!provider.isChunkCached(start, stop)) {
if (!activeRequest if (
|| (activeRequest !activeRequest ||
&& activeRequest.completed (activeRequest &&
&& activeRequest.chunkNumber !== chunkNumber)) { activeRequest.completed &&
activeRequest.chunkNumber !== chunkNumber)
) {
if (activeRequest && activeRequest.rejectRequestAll) { if (activeRequest && activeRequest.rejectRequestAll) {
activeRequest.rejectRequestAll(); activeRequest.rejectRequestAll();
} }
@ -225,16 +222,17 @@
onDecodeAll, onDecodeAll,
rejectRequestAll, rejectRequestAll,
completed: false, completed: false,
callbacks: [{ callbacks: [
{
resolve: resolveWrapper, resolve: resolveWrapper,
reject, reject,
frameNumber: this.number, frameNumber: this.number,
}], },
],
}; };
makeActiveRequest(); makeActiveRequest();
} else if (activeRequest.chunkNumber === chunkNumber) { } else if (activeRequest.chunkNumber === chunkNumber) {
if (!activeRequest.onDecodeAll if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) {
&& !activeRequest.rejectRequestAll) {
activeRequest.onDecodeAll = onDecodeAll; activeRequest.onDecodeAll = onDecodeAll;
activeRequest.rejectRequestAll = rejectRequestAll; activeRequest.rejectRequestAll = rejectRequestAll;
} }
@ -258,11 +256,13 @@
onDecodeAll, onDecodeAll,
rejectRequestAll, rejectRequestAll,
completed: false, completed: false,
callbacks: [{ callbacks: [
{
resolve: resolveWrapper, resolve: resolveWrapper,
reject, reject,
frameNumber: this.number, frameNumber: this.number,
}], },
],
}; };
} }
} else { } else {
@ -271,14 +271,15 @@
reject, reject,
frameNumber: this.number, frameNumber: this.number,
}); });
provider.requestDecodeBlock(null, start, stop, provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll);
onDecodeAll, rejectRequestAll);
} }
} else { } else {
if (this.number % chunkSize > chunkSize / 4 if (
&& provider.decodedBlocksCacheSize > 1 this.number % chunkSize > chunkSize / 4 &&
&& this.decodeForward provider.decodedBlocksCacheSize > 1 &&
&& !provider.isNextChunkExists(this.number)) { this.decodeForward &&
!provider.isNextChunkExists(this.number)
) {
const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; const nextChunkNumber = Math.floor(this.number / chunkSize) + 1;
if (nextChunkNumber * chunkSize < this.stopFrame) { if (nextChunkNumber * chunkSize < this.stopFrame) {
provider.setReadyToLoading(nextChunkNumber); provider.setReadyToLoading(nextChunkNumber);
@ -299,14 +300,14 @@
makeActiveRequest(); makeActiveRequest();
} }
} else { } else {
provider.requestDecodeBlock(null, nextStart, nextStop, provider.requestDecodeBlock(null, nextStart, nextStop, null, null);
null, null);
} }
} }
} }
resolveWrapper(frame); resolveWrapper(frame);
} }
}).catch((exception) => { })
.catch((exception) => {
if (exception instanceof Exception) { if (exception instanceof Exception) {
reject(exception); reject(exception);
} else { } else {
@ -324,16 +325,12 @@
[size] = meta.frames; [size] = meta.frames;
} else if (mode === 'annotation') { } else if (mode === 'annotation') {
if (frame >= meta.size) { if (frame >= meta.size) {
throw new ArgumentError( throw new ArgumentError(`Meta information about frame ${frame} can't be received from the server`);
`Meta information about frame ${frame} can't be received from the server`,
);
} else { } else {
size = meta.frames[frame]; size = meta.frames[frame];
} }
} else { } else {
throw new DataError( throw new DataError(`Invalid mode is specified ${mode}`);
`Invalid mode is specified ${mode}`,
);
} }
return size; return size;
} }
@ -377,21 +374,26 @@
decodeForward: false, decodeForward: false,
}); });
frameData.data().then(() => { frameData
if (!(chunkIdx in this._requestedChunks) .data()
|| !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)) { .then(() => {
if (
!(chunkIdx in this._requestedChunks) ||
!this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)
) {
reject(chunkIdx); reject(chunkIdx);
} else { } else {
this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame);
this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData;
if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) {
const bufferedframes = Object.keys( const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map(
this._requestedChunks[chunkIdx].buffer, (f) => +f,
).map((f) => +f); );
this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes));
} }
} }
}).catch(() => { })
.catch(() => {
reject(chunkIdx); reject(chunkIdx);
}); });
} }
@ -455,7 +457,7 @@
await this.fillBuffer(start, step, count); await this.fillBuffer(start, step, count);
this._activeFillBufferRequest = false; this._activeFillBufferRequest = false;
} catch (error) { } catch (error) {
if (typeof (error) === 'number' && error in this._requestedChunks) { if (typeof error === 'number' && error in this._requestedChunks) {
this._activeFillBufferRequest = false; this._activeFillBufferRequest = false;
} }
throw error; throw error;
@ -465,8 +467,7 @@
async require(frameNumber, taskID, fillBuffer, frameStep) { async require(frameNumber, taskID, fillBuffer, frameStep) {
for (const frame in this._buffer) { for (const frame in this._buffer) {
if (frame < frameNumber if (frame < frameNumber || frame >= frameNumber + this._size * frameStep) {
|| frame >= frameNumber + this._size * frameStep) {
delete this._buffer[frame]; delete this._buffer[frame];
} }
} }
@ -486,9 +487,12 @@
frame = this._buffer[frameNumber]; frame = this._buffer[frameNumber];
delete this._buffer[frameNumber]; delete this._buffer[frameNumber];
const cachedFrames = this.cachedFrames(); const cachedFrames = this.cachedFrames();
if (fillBuffer && !this._activeFillBufferRequest if (
&& this._size > this._chunkSize fillBuffer &&
&& cachedFrames.length < (this._size * 3) / 4) { !this._activeFillBufferRequest &&
this._size > this._chunkSize &&
cachedFrames.length < (this._size * 3) / 4
) {
const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber; const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber;
if (maxFrame < this._stopFrame) { if (maxFrame < this._stopFrame) {
this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => { this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => {
@ -512,8 +516,10 @@
clear() { clear() {
for (const chunkIdx in this._requestedChunks) { for (const chunkIdx in this._requestedChunks) {
if (Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) if (
&& this._requestedChunks[chunkIdx].reject) { Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) &&
this._requestedChunks[chunkIdx].reject
) {
this._requestedChunks[chunkIdx].reject('not needed'); this._requestedChunks[chunkIdx].reject('not needed');
} }
} }
@ -530,8 +536,11 @@
async function getPreview(taskID) { async function getPreview(taskID) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Just go to server and get preview (no any cache) // Just go to server and get preview (no any cache)
serverProxy.frames.getPreview(taskID).then((result) => { serverProxy.frames
.getPreview(taskID)
.then((result) => {
if (isNode) { if (isNode) {
// eslint-disable-next-line no-undef
resolve(global.Buffer.from(result, 'binary').toString('base64')); resolve(global.Buffer.from(result, 'binary').toString('base64'));
} else if (isBrowser) { } else if (isBrowser) {
const reader = new FileReader(); const reader = new FileReader();
@ -540,28 +549,26 @@
}; };
reader.readAsDataURL(result); reader.readAsDataURL(result);
} }
}).catch((error) => { })
.catch((error) => {
reject(error); reject(error);
}); });
}); });
} }
async function getFrame(taskID, chunkSize, chunkType, mode, frame, async function getFrame(taskID, chunkSize, chunkType, mode, frame, startFrame, stopFrame, isPlaying, step) {
startFrame, stopFrame, isPlaying, step) {
if (!(taskID in frameDataCache)) { if (!(taskID in frameDataCache)) {
const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE;
: cvatData.BlockType.ARCHIVE;
const meta = await serverProxy.frames.getMeta(taskID); const meta = await serverProxy.frames.getMeta(taskID);
const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length;
/ meta.frames.length; const stdDev = Math.sqrt(
const stdDev = Math.sqrt(meta.frames.map( meta.frames.map((x) => Math.pow(x.width * x.height - mean, 2)).reduce((a, b) => a + b) /
(x) => Math.pow(x.width * x.height - mean, 2), meta.frames.length,
).reduce((a, b) => a + b) / meta.frames.length); );
// limit of decoded frames cache by 2GB // limit of decoded frames cache by 2GB
const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) || 1;
|| 1;
frameDataCache[taskID] = { frameDataCache[taskID] = {
meta, meta,
@ -570,8 +577,11 @@
startFrame, startFrame,
stopFrame, stopFrame,
provider: new cvatData.FrameProvider( provider: new cvatData.FrameProvider(
blockType, chunkSize, Math.max(decodedBlocksCacheSize, 9), blockType,
decodedBlocksCacheSize, 1, chunkSize,
Math.max(decodedBlocksCacheSize, 9),
decodedBlocksCacheSize,
1,
), ),
frameBuffer: new FrameBuffer( frameBuffer: new FrameBuffer(
Math.min(180, decodedBlocksCacheSize * chunkSize), Math.min(180, decodedBlocksCacheSize * chunkSize),

@ -1,16 +1,9 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const { const { AttributeType } = require('./enums');
AttributeType,
} = require('./enums');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
/** /**
@ -42,12 +35,12 @@
} }
if (!Object.values(AttributeType).includes(data.input_type)) { if (!Object.values(AttributeType).includes(data.input_type)) {
throw new ArgumentError( throw new ArgumentError(`Got invalid attribute type ${data.input_type}`);
`Got invalid attribute type ${data.input_type}`,
);
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* @name id * @name id
* @type {integer} * @type {integer}
@ -108,7 +101,8 @@
values: { values: {
get: () => [...data.values], get: () => [...data.values],
}, },
})); }),
);
} }
toJSON() { toJSON() {
@ -120,7 +114,7 @@
values: this.values, values: this.values,
}; };
if (typeof (this.id) !== 'undefined') { if (typeof this.id !== 'undefined') {
object.id = this.id; object.id = this.id;
} }
@ -151,14 +145,18 @@
data.attributes = []; data.attributes = [];
if (Object.prototype.hasOwnProperty.call(initialData, 'attributes') if (
&& Array.isArray(initialData.attributes)) { Object.prototype.hasOwnProperty.call(initialData, 'attributes') &&
Array.isArray(initialData.attributes)
) {
for (const attrData of initialData.attributes) { for (const attrData of initialData.attributes) {
data.attributes.push(new Attribute(attrData)); data.attributes.push(new Attribute(attrData));
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* @name id * @name id
* @type {integer} * @type {integer}
@ -206,7 +204,8 @@
attributes: { attributes: {
get: () => [...data.attributes], get: () => [...data.attributes],
}, },
})); }),
);
} }
toJSON() { toJSON() {
@ -216,7 +215,7 @@
color: this.color, color: this.color,
}; };
if (typeof (this.id) !== 'undefined') { if (typeof this.id !== 'undefined') {
object.id = this.id; object.id = this.id;
} }

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
@ -28,10 +23,12 @@ class LambdaManager {
const models = []; const models = [];
for (const model of result) { for (const model of result) {
models.push(new MLModel({ models.push(
new MLModel({
...model, ...model,
type: model.kind, type: model.kind,
})); }),
);
} }
this.cachedList = models; this.cachedList = models;
@ -41,20 +38,18 @@ class LambdaManager {
async run(task, model, args) { async run(task, model, args) {
if (!(task instanceof Task)) { if (!(task instanceof Task)) {
throw new ArgumentError( throw new ArgumentError(
`Argument task is expected to be an instance of Task class, but got ${typeof (task)}`, `Argument task is expected to be an instance of Task class, but got ${typeof task}`,
); );
} }
if (!(model instanceof MLModel)) { if (!(model instanceof MLModel)) {
throw new ArgumentError( throw new ArgumentError(
`Argument model is expected to be an instance of MLModel class, but got ${typeof (model)}`, `Argument model is expected to be an instance of MLModel class, but got ${typeof model}`,
); );
} }
if (args && typeof (args) !== 'object') { if (args && typeof args !== 'object') {
throw new ArgumentError( throw new ArgumentError(`Argument args is expected to be an object, but got ${typeof model}`);
`Argument args is expected to be an object, but got ${typeof (model)}`,
);
} }
const body = args; const body = args;
@ -78,7 +73,7 @@ class LambdaManager {
} }
async cancel(requestID) { async cancel(requestID) {
if (typeof (requestID) !== 'string') { if (typeof requestID !== 'string') {
throw new ArgumentError(`Request id argument is required to be a string. But got ${requestID}`); throw new ArgumentError(`Request id argument is required to be a string. But got ${requestID}`);
} }
@ -108,7 +103,11 @@ class LambdaManager {
delete this.listening[requestID]; delete this.listening[requestID];
} }
} catch (error) { } catch (error) {
onUpdate(RQStatus.UNKNOWN, 0, `Could not get a status of the request ${requestID}. ${error.toString()}`); onUpdate(
RQStatus.UNKNOWN,
0,
`Could not get a status of the request ${requestID}. ${error.toString()}`,
);
} }
}; };

@ -2,10 +2,6 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/* global
require:false
*/
const { detect } = require('detect-browser'); const { detect } = require('detect-browser');
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
@ -30,7 +26,7 @@ class Log {
} }
validatePayload() { validatePayload() {
if (typeof (this.payload) !== 'object') { if (typeof this.payload !== 'object') {
throw new ArgumentError('Payload must be an object'); throw new ArgumentError('Payload must be an object');
} }
@ -77,8 +73,7 @@ class Log {
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
async close(payload = {}) { async close(payload = {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, Log.prototype.close, payload);
.apiWrapper.call(this, Log.prototype.close, payload);
return result; return result;
} }
} }
@ -96,8 +91,7 @@ class LogWithCount extends Log {
validatePayload() { validatePayload() {
Log.prototype.validatePayload.call(this); Log.prototype.validatePayload.call(this);
if (!Number.isInteger(this.payload.count) || this.payload.count < 1) { if (!Number.isInteger(this.payload.count) || this.payload.count < 1) {
const message = `The field "count" is required for "${this.type}" log` const message = `The field "count" is required for "${this.type}" log. It must be a positive integer`;
+ 'It must be a positive integer';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
} }
@ -148,12 +142,14 @@ class LogWithWorkingTime extends Log {
validatePayload() { validatePayload() {
Log.prototype.validatePayload.call(this); Log.prototype.validatePayload.call(this);
if (!('working_time' in this.payload) if (
|| !typeof (this.payload.working_time) === 'number' !('working_time' in this.payload) ||
|| this.payload.working_time < 0 !typeof this.payload.working_time === 'number' ||
this.payload.working_time < 0
) { ) {
const message = `The field "working_time" is required for ${this.type} log. ` const message = `
+ 'It must be a number not less than 0'; The field "working_time" is required for ${this.type} log. It must be a number not less than 0
`;
throw new ArgumentError(message); throw new ArgumentError(message);
} }
} }
@ -163,33 +159,28 @@ class LogWithExceptionInfo extends Log {
validatePayload() { validatePayload() {
Log.prototype.validatePayload.call(this); Log.prototype.validatePayload.call(this);
if (typeof (this.payload.message) !== 'string') { if (typeof this.payload.message !== 'string') {
const message = `The field "message" is required for ${this.type} log. ` const message = `The field "message" is required for ${this.type} log. It must be a string`;
+ 'It must be a string';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
if (typeof (this.payload.filename) !== 'string') { if (typeof this.payload.filename !== 'string') {
const message = `The field "filename" is required for ${this.type} log. ` const message = `The field "filename" is required for ${this.type} log. It must be a string`;
+ 'It must be a string';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
if (typeof (this.payload.line) !== 'number') { if (typeof this.payload.line !== 'number') {
const message = `The field "line" is required for ${this.type} log. ` const message = `The field "line" is required for ${this.type} log. It must be a number`;
+ 'It must be a number';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
if (typeof (this.payload.column) !== 'number') { if (typeof this.payload.column !== 'number') {
const message = `The field "column" is required for ${this.type} log. ` const message = `The field "column" is required for ${this.type} log. It must be a number`;
+ 'It must be a number';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
if (typeof (this.payload.stack) !== 'string') { if (typeof this.payload.stack !== 'string') {
const message = `The field "stack" is required for ${this.type} log. ` const message = `The field "stack" is required for ${this.type} log. It must be a string`;
+ 'It must be a string';
throw new ArgumentError(message); throw new ArgumentError(message);
} }
} }
@ -222,8 +213,11 @@ class LogWithExceptionInfo extends Log {
function logFactory(logType, payload) { function logFactory(logType, payload) {
const logsWithCount = [ const logsWithCount = [
LogType.deleteObject, LogType.mergeObjects, LogType.copyObject, LogType.deleteObject,
LogType.undoAction, LogType.redoAction, LogType.mergeObjects,
LogType.copyObject,
LogType.undoAction,
LogType.redoAction,
]; ];
if (logsWithCount.includes(logType)) { if (logsWithCount.includes(logType)) {

@ -2,10 +2,6 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/* global
require:false
*/
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const logFactory = require('./log'); const logFactory = require('./log');
@ -41,8 +37,10 @@ class LoggerStorage {
this.ignoreRules[LogType.changeAttribute] = { this.ignoreRules[LogType.changeAttribute] = {
lastLog: null, lastLog: null,
ignore(previousLog, currentPayload) { ignore(previousLog, currentPayload) {
return currentPayload.object_id === previousLog.payload.object_id return (
&& currentPayload.id === previousLog.payload.id; currentPayload.object_id === previousLog.payload.object_id &&
currentPayload.id === previousLog.payload.id
);
}, },
}; };
} }
@ -57,32 +55,28 @@ class LoggerStorage {
} }
async configure(isActiveChecker, activityHelper) { async configure(isActiveChecker, activityHelper) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call( this,
this, LoggerStorage.prototype.configure, LoggerStorage.prototype.configure,
isActiveChecker, activityHelper, isActiveChecker,
activityHelper,
); );
return result; return result;
} }
async log(logType, payload = {}, wait = false) { async log(logType, payload = {}, wait = false) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
return result; return result;
} }
async save() { async save() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save);
.apiWrapper.call(this, LoggerStorage.prototype.save);
return result; return result;
} }
} }
LoggerStorage.prototype.configure.implementation = function ( LoggerStorage.prototype.configure.implementation = function (isActiveChecker, userActivityCallback) {
isActiveChecker, if (typeof isActiveChecker !== 'function') {
userActivityCallback,
) {
if (typeof (isActiveChecker) !== 'function') {
throw new ArgumentError('isActiveChecker argument must be callable'); throw new ArgumentError('isActiveChecker argument must be callable');
} }
@ -95,11 +89,11 @@ LoggerStorage.prototype.configure.implementation = function (
}; };
LoggerStorage.prototype.log.implementation = function (logType, payload, wait) { LoggerStorage.prototype.log.implementation = function (logType, payload, wait) {
if (typeof (payload) !== 'object') { if (typeof payload !== 'object') {
throw new ArgumentError('Payload must be an object'); throw new ArgumentError('Payload must be an object');
} }
if (typeof (wait) !== 'boolean') { if (typeof wait !== 'boolean') {
throw new ArgumentError('Payload must be an object'); throw new ArgumentError('Payload must be an object');
} }

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/** /**
* Class representing a machine learning model * Class representing a machine learning model

@ -1,14 +1,9 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
const { Source } = require('./enums'); const { Source } = require('./enums');
/* global
require:false
*/
(() => { (() => {
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
@ -77,7 +72,9 @@ const { Source } = require('./enums');
writable: false, writable: false,
}); });
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
// Internal property. We don't need document it. // Internal property. We don't need document it.
updateFlags: { updateFlags: {
get: () => data.updateFlags, get: () => data.updateFlags,
@ -196,9 +193,10 @@ const { Source } = require('./enums');
data.points = [...points]; data.points = [...points];
} else { } else {
throw new ArgumentError( throw new ArgumentError(
'Points are expected to be an array ' 'Points are expected to be an array ' +
+ `but got ${typeof (points) === 'object' `but got ${
? points.constructor.name : typeof (points)}`, typeof points === 'object' ? points.constructor.name : typeof points
}`,
); );
} }
}, },
@ -263,7 +261,7 @@ const { Source } = require('./enums');
* @instance * @instance
*/ */
get: () => { get: () => {
if (typeof (data.keyframes) === 'object') { if (typeof data.keyframes === 'object') {
return { ...data.keyframes }; return { ...data.keyframes };
} }
@ -304,7 +302,7 @@ const { Source } = require('./enums');
* @instance * @instance
*/ */
get: () => { get: () => {
if (typeof (data.pinned) === 'boolean') { if (typeof data.pinned === 'boolean') {
return data.pinned; return data.pinned;
} }
@ -338,11 +336,14 @@ const { Source } = require('./enums');
*/ */
get: () => data.attributes, get: () => data.attributes,
set: (attributes) => { set: (attributes) => {
if (typeof (attributes) !== 'object') { if (typeof attributes !== 'object') {
throw new ArgumentError( throw new ArgumentError(
'Attributes are expected to be an object ' 'Attributes are expected to be an object ' +
+ `but got ${typeof (attributes) === 'object' `but got ${
? attributes.constructor.name : typeof (attributes)}`, typeof attributes === 'object'
? attributes.constructor.name
: typeof attributes
}`,
); );
} }
@ -352,7 +353,8 @@ const { Source } = require('./enums');
} }
}, },
}, },
})); }),
);
this.label = serialized.label; this.label = serialized.label;
this.lock = serialized.lock; this.lock = serialized.lock;
@ -360,31 +362,31 @@ const { Source } = require('./enums');
if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) { if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) {
data.source = serialized.source; data.source = serialized.source;
} }
if (typeof (serialized.zOrder) === 'number') { if (typeof serialized.zOrder === 'number') {
this.zOrder = serialized.zOrder; this.zOrder = serialized.zOrder;
} }
if (typeof (serialized.occluded) === 'boolean') { if (typeof serialized.occluded === 'boolean') {
this.occluded = serialized.occluded; this.occluded = serialized.occluded;
} }
if (typeof (serialized.outside) === 'boolean') { if (typeof serialized.outside === 'boolean') {
this.outside = serialized.outside; this.outside = serialized.outside;
} }
if (typeof (serialized.keyframe) === 'boolean') { if (typeof serialized.keyframe === 'boolean') {
this.keyframe = serialized.keyframe; this.keyframe = serialized.keyframe;
} }
if (typeof (serialized.pinned) === 'boolean') { if (typeof serialized.pinned === 'boolean') {
this.pinned = serialized.pinned; this.pinned = serialized.pinned;
} }
if (typeof (serialized.hidden) === 'boolean') { if (typeof serialized.hidden === 'boolean') {
this.hidden = serialized.hidden; this.hidden = serialized.hidden;
} }
if (typeof (serialized.color) === 'string') { if (typeof serialized.color === 'string') {
this.color = serialized.color; this.color = serialized.color;
} }
if (Array.isArray(serialized.points)) { if (Array.isArray(serialized.points)) {
this.points = serialized.points; this.points = serialized.points;
} }
if (typeof (serialized.attributes) === 'object') { if (typeof serialized.attributes === 'object') {
this.attributes = serialized.attributes; this.attributes = serialized.attributes;
} }
@ -403,8 +405,7 @@ const { Source } = require('./enums');
* @returns {module:API.cvat.classes.ObjectState} updated state of an object * @returns {module:API.cvat.classes.ObjectState} updated state of an object
*/ */
async save() { async save() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.save);
.apiWrapper.call(this, ObjectState.prototype.save);
return result; return result;
} }
@ -422,8 +423,7 @@ const { Source } = require('./enums');
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
async delete(frame, force = false) { async delete(frame, force = false) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.delete, frame, force);
.apiWrapper.call(this, ObjectState.prototype.delete, frame, force);
return result; return result;
} }
} }

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const { PluginError } = require('./exceptions'); const { PluginError } = require('./exceptions');
@ -16,8 +11,7 @@
// I have to optimize the wrapper // I have to optimize the wrapper
const pluginList = await PluginRegistry.list(); const pluginList = await PluginRegistry.list();
for (const plugin of pluginList) { for (const plugin of pluginList) {
const pluginDecorators = plugin.functions const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.enter) { if (pluginDecorators && pluginDecorators.enter) {
try { try {
await pluginDecorators.enter.call(this, plugin, ...args); await pluginDecorators.enter.call(this, plugin, ...args);
@ -34,8 +28,7 @@
let result = await wrappedFunc.implementation.call(this, ...args); let result = await wrappedFunc.implementation.call(this, ...args);
for (const plugin of pluginList) { for (const plugin of pluginList) {
const pluginDecorators = plugin.functions const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.leave) { if (pluginDecorators && pluginDecorators.leave) {
try { try {
result = await pluginDecorators.leave.call(this, plugin, result, ...args); result = await pluginDecorators.leave.call(this, plugin, result, ...args);
@ -56,15 +49,15 @@
static async register(plug) { static async register(plug) {
const functions = []; const functions = [];
if (typeof (plug) !== 'object') { if (typeof plug !== 'object') {
throw new PluginError(`Plugin should be an object, but got "${typeof (plug)}"`); throw new PluginError(`Plugin should be an object, but got "${typeof plug}"`);
} }
if (!('name' in plug) || typeof (plug.name) !== 'string') { if (!('name' in plug) || typeof plug.name !== 'string') {
throw new PluginError('Plugin must contain a "name" field and it must be a string'); throw new PluginError('Plugin must contain a "name" field and it must be a string');
} }
if (!('description' in plug) || typeof (plug.description) !== 'string') { if (!('description' in plug) || typeof plug.description !== 'string') {
throw new PluginError('Plugin must contain a "description" field and it must be a string'); throw new PluginError('Plugin must contain a "description" field and it must be a string');
} }
@ -76,13 +69,15 @@
const decorator = {}; const decorator = {};
for (const key in plugin) { for (const key in plugin) {
if (Object.prototype.hasOwnProperty.call(plugin, key)) { if (Object.prototype.hasOwnProperty.call(plugin, key)) {
if (typeof (plugin[key]) === 'object') { if (typeof plugin[key] === 'object') {
if (Object.prototype.hasOwnProperty.call(api, key)) { if (Object.prototype.hasOwnProperty.call(api, key)) {
traverse(plugin[key], api[key]); traverse(plugin[key], api[key]);
} }
} else if (['enter', 'leave'].includes(key) } else if (
&& typeof (api) === 'function' ['enter', 'leave'].includes(key) &&
&& typeof (plugin[key] === 'function')) { typeof api === 'function' &&
typeof (plugin[key] === 'function')
) {
decorator.callback = api; decorator.callback = api;
decorator[key] = plugin[key]; decorator[key] = plugin[key];
} }
@ -92,9 +87,9 @@
if (Object.keys(decorator).length) { if (Object.keys(decorator).length) {
functions.push(decorator); functions.push(decorator);
} }
}(plug, { })(plug, {
cvat: this, cvat: this,
})); });
Object.defineProperty(plug, 'functions', { Object.defineProperty(plug, 'functions', {
value: functions, value: functions,

@ -1,17 +1,10 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const FormData = require('form-data'); const FormData = require('form-data');
const { const { ServerError } = require('./exceptions');
ServerError,
} = require('./exceptions');
const store = require('store'); const store = require('store');
const config = require('./config'); const config = require('./config');
const DownloadWorker = require('./download.worker'); const DownloadWorker = require('./download.worker');
@ -71,12 +64,15 @@
}); });
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
get: { get: {
value: get, value: get,
writable: false, writable: false,
}, },
})); }),
);
} }
} }
@ -154,7 +150,6 @@
return response.data; return response.data;
} }
async function userAgreements() { async function userAgreements() {
const { backendAPI } = config; const { backendAPI } = config;
let response = null; let response = null;
@ -169,15 +164,7 @@
return response.data; return response.data;
} }
async function register( async function register(username, firstName, lastName, email, password1, password2, confirmations) {
username,
firstName,
lastName,
email,
password1,
password2,
confirmations,
) {
let response = null; let response = null;
try { try {
const data = JSON.stringify({ const data = JSON.stringify({
@ -203,20 +190,19 @@
} }
async function login(username, password) { async function login(username, password) {
const authenticationData = ([ const authenticationData = [
`${encodeURIComponent('username')}=${encodeURIComponent(username)}`, `${encodeURIComponent('username')}=${encodeURIComponent(username)}`,
`${encodeURIComponent('password')}=${encodeURIComponent(password)}`, `${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
]).join('&').replace(/%20/g, '+'); ]
.join('&')
.replace(/%20/g, '+');
Axios.defaults.headers.common.Authorization = ''; Axios.defaults.headers.common.Authorization = '';
let authenticationResponse = null; let authenticationResponse = null;
try { try {
authenticationResponse = await Axios.post( authenticationResponse = await Axios.post(`${config.backendAPI}/auth/login`, authenticationData, {
`${config.backendAPI}/auth/login`,
authenticationData, {
proxy: config.proxy, proxy: config.proxy,
}, });
);
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }
@ -315,10 +301,12 @@
async function serverRequest(url, data) { async function serverRequest(url, data) {
try { try {
return (await Axios({ return (
await Axios({
url, url,
...data, ...data,
})).data; })
).data;
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }
@ -372,8 +360,7 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
async function request() { async function request() {
try { try {
const response = await Axios const response = await Axios.get(`${url}`, {
.get(`${url}`, {
proxy: config.proxy, proxy: config.proxy,
}); });
if (response.status === 202) { if (response.status === 202) {
@ -409,21 +396,22 @@
} else if (response.data.state === 'Failed') { } else if (response.data.state === 'Failed') {
// If request has been successful, but task hasn't been created // If request has been successful, but task hasn't been created
// Then passed data is wrong and we can pass code 400 // Then passed data is wrong and we can pass code 400
const message = 'Could not create the task on the server. ' const message = `
+ `${response.data.message}.`; Could not create the task on the server. ${response.data.message}.
`;
reject(new ServerError(message, 400)); reject(new ServerError(message, 400));
} else { } else {
// If server has another status, it is unexpected // If server has another status, it is unexpected
// Therefore it is server error and we can pass code 500 // Therefore it is server error and we can pass code 500
reject(new ServerError( reject(
new ServerError(
`Unknown task state has been received: ${response.data.state}`, `Unknown task state has been received: ${response.data.state}`,
500, 500,
)); ),
);
} }
} catch (errorData) { } catch (errorData) {
reject( reject(generateError(errorData));
generateError(errorData),
);
} }
} }
@ -559,10 +547,7 @@
}); });
} catch (errorData) { } catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code; const code = errorData.response ? errorData.response.status : errorData.code;
throw new ServerError( throw new ServerError(`Could not get preview frame for the task ${tid} from the server`, code);
`Could not get preview frame for the task ${tid} from the server`,
code,
);
} }
return response.data; return response.data;
@ -656,10 +641,13 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
async function request() { async function request() {
try { try {
const response = await Axios const response = await Axios.put(
.put(`${backendAPI}/${session}s/${id}/annotations?format=${format}`, annotationData, { `${backendAPI}/${session}s/${id}/annotations?format=${format}`,
annotationData,
{
proxy: config.proxy, proxy: config.proxy,
}); },
);
if (response.status === 202) { if (response.status === 202) {
annotationData = new FormData(); annotationData = new FormData();
setTimeout(request, 3000); setTimeout(request, 3000);
@ -690,7 +678,8 @@
async function request() { async function request() {
Axios.get(`${url}`, { Axios.get(`${url}`, {
proxy: config.proxy, proxy: config.proxy,
}).then((response) => { })
.then((response) => {
if (response.status === 202) { if (response.status === 202) {
setTimeout(request, 3000); setTimeout(request, 3000);
} else { } else {
@ -698,7 +687,8 @@
url = `${baseURL}?${query}`; url = `${baseURL}?${query}`;
resolve(url); resolve(url);
} }
}).catch((errorData) => { })
.catch((errorData) => {
reject(generateError(errorData)); reject(generateError(errorData));
}); });
} }
@ -739,8 +729,7 @@
const { backendAPI } = config; const { backendAPI } = config;
try { try {
const response = await Axios.post(`${backendAPI}/lambda/requests`, const response = await Axios.post(`${backendAPI}/lambda/requests`, JSON.stringify(body), {
JSON.stringify(body), {
proxy: config.proxy, proxy: config.proxy,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -757,8 +746,7 @@
const { backendAPI } = config; const { backendAPI } = config;
try { try {
const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, JSON.stringify(body), {
JSON.stringify(body), {
proxy: config.proxy, proxy: config.proxy,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -802,11 +790,9 @@
const { backendAPI } = config; const { backendAPI } = config;
try { try {
await Axios.delete( await Axios.delete(`${backendAPI}/lambda/requests/${requestId}`, {
`${backendAPI}/lambda/requests/${requestId}`, {
method: 'DELETE', method: 'DELETE',
}, });
);
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }
@ -824,7 +810,9 @@
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
server: { server: {
value: Object.freeze({ value: Object.freeze({
about, about,
@ -909,7 +897,8 @@
}), }),
writable: false, writable: false,
}, },
})); }),
);
} }
} }

@ -1,22 +1,12 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => { (() => {
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const loggerStorage = require('./logger-storage'); const loggerStorage = require('./logger-storage');
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const { const { getFrame, getRanges, getPreview, clear: clearFrames } = require('./frames');
getFrame,
getRanges,
getPreview,
clear: clearFrames,
} = require('./frames');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
const { TaskStatus } = require('./enums'); const { TaskStatus } = require('./enums');
const { Label } = require('./labels'); const { Label } = require('./labels');
@ -27,109 +17,142 @@
annotations: Object.freeze({ annotations: Object.freeze({
value: { value: {
async upload(file, loader) { async upload(file, loader) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.upload, file, loader); this,
prototype.annotations.upload,
file,
loader,
);
return result; return result;
}, },
async save(onUpdate) { async save(onUpdate) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.save, onUpdate);
.apiWrapper.call(this, prototype.annotations.save, onUpdate);
return result; return result;
}, },
async clear(reload = false) { async clear(reload = false) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload);
.apiWrapper.call(this, prototype.annotations.clear, reload);
return result; return result;
}, },
async dump(dumper, name = null) { async dump(dumper, name = null) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.dump, dumper, name); this,
prototype.annotations.dump,
dumper,
name,
);
return result; return result;
}, },
async statistics() { async statistics() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.statistics);
.apiWrapper.call(this, prototype.annotations.statistics);
return result; return result;
}, },
async put(arrayOfObjects = []) { async put(arrayOfObjects = []) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.put, arrayOfObjects); this,
prototype.annotations.put,
arrayOfObjects,
);
return result; return result;
}, },
async get(frame, allTracks = false, filters = []) { async get(frame, allTracks = false, filters = []) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.get, this,
frame, allTracks, filters); prototype.annotations.get,
frame,
allTracks,
filters,
);
return result; return result;
}, },
async search(filters, frameFrom, frameTo) { async search(filters, frameFrom, frameTo) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.search, this,
filters, frameFrom, frameTo); prototype.annotations.search,
filters,
frameFrom,
frameTo,
);
return result; return result;
}, },
async searchEmpty(frameFrom, frameTo) { async searchEmpty(frameFrom, frameTo) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.searchEmpty, this,
frameFrom, frameTo); prototype.annotations.searchEmpty,
frameFrom,
frameTo,
);
return result; return result;
}, },
async select(objectStates, x, y) { async select(objectStates, x, y) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, this,
prototype.annotations.select, objectStates, x, y); prototype.annotations.select,
objectStates,
x,
y,
);
return result; return result;
}, },
async merge(objectStates) { async merge(objectStates) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.merge, objectStates); this,
prototype.annotations.merge,
objectStates,
);
return result; return result;
}, },
async split(objectState, frame) { async split(objectState, frame) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.split, objectState, frame); this,
prototype.annotations.split,
objectState,
frame,
);
return result; return result;
}, },
async group(objectStates, reset = false) { async group(objectStates, reset = false) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.group, this,
objectStates, reset); prototype.annotations.group,
objectStates,
reset,
);
return result; return result;
}, },
async import(data) { async import(data) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.import, data);
.apiWrapper.call(this, prototype.annotations.import, data);
return result; return result;
}, },
async export() { async export() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.export);
.apiWrapper.call(this, prototype.annotations.export);
return result; return result;
}, },
async exportDataset(format) { async exportDataset(format) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.annotations.exportDataset, format); this,
prototype.annotations.exportDataset,
format,
);
return result; return result;
}, },
hasUnsavedChanges() { hasUnsavedChanges() {
const result = prototype.annotations const result = prototype.annotations.hasUnsavedChanges.implementation.call(this);
.hasUnsavedChanges.implementation.call(this);
return result; return result;
}, },
}, },
@ -138,18 +161,21 @@
frames: Object.freeze({ frames: Object.freeze({
value: { value: {
async get(frame, isPlaying = false, step = 1) { async get(frame, isPlaying = false, step = 1) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.frames.get, frame, isPlaying, step); this,
prototype.frames.get,
frame,
isPlaying,
step,
);
return result; return result;
}, },
async ranges() { async ranges() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.ranges);
.apiWrapper.call(this, prototype.frames.ranges);
return result; return result;
}, },
async preview() { async preview() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.preview);
.apiWrapper.call(this, prototype.frames.preview);
return result; return result;
}, },
}, },
@ -158,8 +184,13 @@
logger: Object.freeze({ logger: Object.freeze({
value: { value: {
async log(logType, payload = {}, wait = false) { async log(logType, payload = {}, wait = false) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.logger.log, logType, payload, wait); this,
prototype.logger.log,
logType,
payload,
wait,
);
return result; return result;
}, },
}, },
@ -168,28 +199,23 @@
actions: Object.freeze({ actions: Object.freeze({
value: { value: {
async undo(count = 1) { async undo(count = 1) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.undo, count);
.apiWrapper.call(this, prototype.actions.undo, count);
return result; return result;
}, },
async redo(count = 1) { async redo(count = 1) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.redo, count);
.apiWrapper.call(this, prototype.actions.redo, count);
return result; return result;
}, },
async freeze(frozen) { async freeze(frozen) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.freeze, frozen);
.apiWrapper.call(this, prototype.actions.freeze, frozen);
return result; return result;
}, },
async clear() { async clear() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.clear);
.apiWrapper.call(this, prototype.actions.clear);
return result; return result;
}, },
async get() { async get() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.get);
.apiWrapper.call(this, prototype.actions.get);
return result; return result;
}, },
}, },
@ -198,13 +224,21 @@
events: Object.freeze({ events: Object.freeze({
value: { value: {
async subscribe(evType, callback) { async subscribe(evType, callback) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.events.subscribe, evType, callback); this,
prototype.events.subscribe,
evType,
callback,
);
return result; return result;
}, },
async unsubscribe(evType, callback = null) { async unsubscribe(evType, callback = null) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(
.apiWrapper.call(this, prototype.events.unsubscribe, evType, callback); this,
prototype.events.unsubscribe,
evType,
callback,
);
return result; return result;
}, },
}, },
@ -468,8 +502,6 @@
* @instance * @instance
* @async * @async
*/ */
/** /**
* Namespace is used for an interaction with frames * Namespace is used for an interaction with frames
* @namespace frames * @namespace frames
@ -488,7 +520,6 @@
* @throws {module:API.cvat.exceptions.DataError} * @throws {module:API.cvat.exceptions.DataError}
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
/** /**
* Get the first frame of a task for preview * Get the first frame of a task for preview
* @method preview * @method preview
@ -500,7 +531,6 @@
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
/** /**
* Returns the ranges of cached frames * Returns the ranges of cached frames
* @method ranges * @method ranges
@ -509,13 +539,11 @@
* @instance * @instance
* @async * @async
*/ */
/** /**
* Namespace is used for an interaction with logs * Namespace is used for an interaction with logs
* @namespace logger * @namespace logger
* @memberof Session * @memberof Session
*/ */
/** /**
* Create a log and add it to a log collection <br> * Create a log and add it to a log collection <br>
* Durable logs will be added after "close" method is called for them <br> * Durable logs will be added after "close" method is called for them <br>
@ -534,13 +562,11 @@
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
/** /**
* Namespace is used for an interaction with actions * Namespace is used for an interaction with actions
* @namespace actions * @namespace actions
* @memberof Session * @memberof Session
*/ */
/** /**
* @typedef {Object} HistoryActions * @typedef {Object} HistoryActions
* @property {string[]} [undo] - array of possible actions to undo * @property {string[]} [undo] - array of possible actions to undo
@ -597,8 +623,6 @@
* @instance * @instance
* @async * @async
*/ */
/** /**
* Namespace is used for an interaction with events * Namespace is used for an interaction with events
* @namespace events * @namespace events
@ -655,14 +679,14 @@
} }
if (data[property] === undefined) { if (data[property] === undefined) {
throw new ArgumentError( throw new ArgumentError(`Job field "${property}" was not initialized`);
`Job field "${property}" was not initialized`,
);
} }
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* @name id * @name id
* @type {integer} * @type {integer}
@ -685,9 +709,7 @@
get: () => data.assignee, get: () => data.assignee,
set: (assignee) => { set: (assignee) => {
if (assignee !== null && !(assignee instanceof User)) { if (assignee !== null && !(assignee instanceof User)) {
throw new ArgumentError( throw new ArgumentError('Value must be a user instance');
'Value must be a user instance',
);
} }
data.assignee = assignee; data.assignee = assignee;
}, },
@ -750,7 +772,8 @@
task: { task: {
get: () => data.task, get: () => data.task,
}, },
})); }),
);
// When we call a function, for example: task.annotations.get() // When we call a function, for example: task.annotations.get()
// In the method get we lose the task context // In the method get we lose the task context
@ -771,8 +794,7 @@
import: Object.getPrototypeOf(this).annotations.import.bind(this), import: Object.getPrototypeOf(this).annotations.import.bind(this),
export: Object.getPrototypeOf(this).annotations.export.bind(this), export: Object.getPrototypeOf(this).annotations.export.bind(this),
statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this),
hasUnsavedChanges: Object.getPrototypeOf(this) hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this),
.annotations.hasUnsavedChanges.bind(this),
}; };
this.actions = { this.actions = {
@ -805,8 +827,7 @@
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async save() { async save() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save);
.apiWrapper.call(this, Job.prototype.save);
return result; return result;
} }
} }
@ -855,8 +876,7 @@
}; };
for (const property in data) { for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property) if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
&& property in initialData) {
data[property] = initialData[property]; data[property] = initialData[property];
} }
} }
@ -895,7 +915,9 @@
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* @name id * @name id
* @type {integer} * @type {integer}
@ -917,9 +939,7 @@
get: () => data.name, get: () => data.name,
set: (value) => { set: (value) => {
if (!value.trim().length) { if (!value.trim().length) {
throw new ArgumentError( throw new ArgumentError('Value must not be empty');
'Value must not be empty',
);
} }
data.name = value; data.name = value;
}, },
@ -977,9 +997,7 @@
get: () => data.assignee, get: () => data.assignee,
set: (assignee) => { set: (assignee) => {
if (assignee !== null && !(assignee instanceof User)) { if (assignee !== null && !(assignee instanceof User)) {
throw new ArgumentError( throw new ArgumentError('Value must be a user instance');
'Value must be a user instance',
);
} }
data.assignee = assignee; data.assignee = assignee;
}, },
@ -1028,9 +1046,7 @@
get: () => data.overlap, get: () => data.overlap,
set: (overlap) => { set: (overlap) => {
if (!Number.isInteger(overlap) || overlap < 0) { if (!Number.isInteger(overlap) || overlap < 0) {
throw new ArgumentError( throw new ArgumentError('Value must be a non negative integer');
'Value must be a non negative integer',
);
} }
data.overlap = overlap; data.overlap = overlap;
}, },
@ -1046,9 +1062,7 @@
get: () => data.segment_size, get: () => data.segment_size,
set: (segment) => { set: (segment) => {
if (!Number.isInteger(segment) || segment < 0) { if (!Number.isInteger(segment) || segment < 0) {
throw new ArgumentError( throw new ArgumentError('Value must be a positive integer');
'Value must be a positive integer',
);
} }
data.segment_size = segment; data.segment_size = segment;
}, },
@ -1064,9 +1078,7 @@
get: () => data.image_quality, get: () => data.image_quality,
set: (quality) => { set: (quality) => {
if (!Number.isInteger(quality) || quality < 0) { if (!Number.isInteger(quality) || quality < 0) {
throw new ArgumentError( throw new ArgumentError('Value must be a positive integer');
'Value must be a positive integer',
);
} }
data.image_quality = quality; data.image_quality = quality;
}, },
@ -1081,10 +1093,8 @@
useZipChunks: { useZipChunks: {
get: () => data.use_zip_chunks, get: () => data.use_zip_chunks,
set: (useZipChunks) => { set: (useZipChunks) => {
if (typeof (useZipChunks) !== 'boolean') { if (typeof useZipChunks !== 'boolean') {
throw new ArgumentError( throw new ArgumentError('Value must be a boolean');
'Value must be a boolean',
);
} }
data.use_zip_chunks = useZipChunks; data.use_zip_chunks = useZipChunks;
}, },
@ -1099,10 +1109,8 @@
useCache: { useCache: {
get: () => data.use_cache, get: () => data.use_cache,
set: (useCache) => { set: (useCache) => {
if (typeof (useCache) !== 'boolean') { if (typeof useCache !== 'boolean') {
throw new ArgumentError( throw new ArgumentError('Value must be a boolean');
'Value must be a boolean',
);
} }
data.use_cache = useCache; data.use_cache = useCache;
}, },
@ -1119,16 +1127,13 @@
get: () => [...data.labels], get: () => [...data.labels],
set: (labels) => { set: (labels) => {
if (!Array.isArray(labels)) { if (!Array.isArray(labels)) {
throw new ArgumentError( throw new ArgumentError('Value must be an array of Labels');
'Value must be an array of Labels',
);
} }
for (const label of labels) { for (const label of labels) {
if (!(label instanceof Label)) { if (!(label instanceof Label)) {
throw new ArgumentError( throw new ArgumentError(
'Each array value must be an instance of Label. ' `Each array value must be an instance of Label. ${typeof label} was found`,
+ `${typeof (label)} was found`,
); );
} }
} }
@ -1159,14 +1164,14 @@
set: (serverFiles) => { set: (serverFiles) => {
if (!Array.isArray(serverFiles)) { if (!Array.isArray(serverFiles)) {
throw new ArgumentError( throw new ArgumentError(
`Value must be an array. But ${typeof (serverFiles)} has been got.`, `Value must be an array. But ${typeof serverFiles} has been got.`,
); );
} }
for (const value of serverFiles) { for (const value of serverFiles) {
if (typeof (value) !== 'string') { if (typeof value !== 'string') {
throw new ArgumentError( throw new ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`, `Array values must be a string. But ${typeof value} has been got.`,
); );
} }
} }
@ -1187,7 +1192,7 @@
set: (clientFiles) => { set: (clientFiles) => {
if (!Array.isArray(clientFiles)) { if (!Array.isArray(clientFiles)) {
throw new ArgumentError( throw new ArgumentError(
`Value must be an array. But ${typeof (clientFiles)} has been got.`, `Value must be an array. But ${typeof clientFiles} has been got.`,
); );
} }
@ -1215,14 +1220,14 @@
set: (remoteFiles) => { set: (remoteFiles) => {
if (!Array.isArray(remoteFiles)) { if (!Array.isArray(remoteFiles)) {
throw new ArgumentError( throw new ArgumentError(
`Value must be an array. But ${typeof (remoteFiles)} has been got.`, `Value must be an array. But ${typeof remoteFiles} has been got.`,
); );
} }
for (const value of remoteFiles) { for (const value of remoteFiles) {
if (typeof (value) !== 'string') { if (typeof value !== 'string') {
throw new ArgumentError( throw new ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`, `Array values must be a string. But ${typeof value} has been got.`,
); );
} }
} }
@ -1242,9 +1247,7 @@
get: () => data.start_frame, get: () => data.start_frame,
set: (frame) => { set: (frame) => {
if (!Number.isInteger(frame) || frame < 0) { if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError( throw new ArgumentError('Value must be a not negative integer');
'Value must be a not negative integer',
);
} }
data.start_frame = frame; data.start_frame = frame;
}, },
@ -1261,9 +1264,7 @@
get: () => data.stop_frame, get: () => data.stop_frame,
set: (frame) => { set: (frame) => {
if (!Number.isInteger(frame) || frame < 0) { if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError( throw new ArgumentError('Value must be a not negative integer');
'Value must be a not negative integer',
);
} }
data.stop_frame = frame; data.stop_frame = frame;
}, },
@ -1279,9 +1280,9 @@
frameFilter: { frameFilter: {
get: () => data.frame_filter, get: () => data.frame_filter,
set: (filter) => { set: (filter) => {
if (typeof (filter) !== 'string') { if (typeof filter !== 'string') {
throw new ArgumentError( throw new ArgumentError(
`Filter value must be a string. But ${typeof (filter)} has been got.`, `Filter value must be a string. But ${typeof filter} has been got.`,
); );
} }
@ -1291,7 +1292,7 @@
dataChunkSize: { dataChunkSize: {
get: () => data.data_chunk_size, get: () => data.data_chunk_size,
set: (chunkSize) => { set: (chunkSize) => {
if (typeof (chunkSize) !== 'number' || chunkSize < 1) { if (typeof chunkSize !== 'number' || chunkSize < 1) {
throw new ArgumentError( throw new ArgumentError(
`Chunk size value must be a positive number. But value ${chunkSize} has been got.`, `Chunk size value must be a positive number. But value ${chunkSize} has been got.`,
); );
@ -1303,7 +1304,8 @@
dataChunkType: { dataChunkType: {
get: () => data.data_compressed_chunk_type, get: () => data.data_compressed_chunk_type,
}, },
})); }),
);
// When we call a function, for example: task.annotations.get() // When we call a function, for example: task.annotations.get()
// In the method get we lose the task context // In the method get we lose the task context
@ -1324,10 +1326,8 @@
import: Object.getPrototypeOf(this).annotations.import.bind(this), import: Object.getPrototypeOf(this).annotations.import.bind(this),
export: Object.getPrototypeOf(this).annotations.export.bind(this), export: Object.getPrototypeOf(this).annotations.export.bind(this),
statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this),
hasUnsavedChanges: Object.getPrototypeOf(this) hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this),
.annotations.hasUnsavedChanges.bind(this), exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this),
exportDataset: Object.getPrototypeOf(this)
.annotations.exportDataset.bind(this),
}; };
this.actions = { this.actions = {
@ -1379,8 +1379,7 @@
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async save(onUpdate = () => {}) { async save(onUpdate = () => {}) {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, onUpdate);
.apiWrapper.call(this, Task.prototype.save, onUpdate);
return result; return result;
} }
@ -1395,8 +1394,7 @@
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async delete() { async delete() {
const result = await PluginRegistry const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete);
.apiWrapper.call(this, Task.prototype.delete);
return result; return result;
} }
} }
@ -1447,22 +1445,16 @@
return this; return this;
} }
throw new ArgumentError( throw new ArgumentError('Can not save job without and id');
'Can not save job without and id',
);
}; };
Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) { Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) {
if (!Number.isInteger(frame) || frame < 0) { if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError( throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
`Frame must be a positive integer. Got: "${frame}"`,
);
} }
if (frame < this.startFrame || frame > this.stopFrame) { if (frame < this.startFrame || frame > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError(`The frame with number ${frame} is out of the job`);
`The frame with number ${frame} is out of the job`,
);
} }
const frameData = await getFrame( const frameData = await getFrame(
@ -1480,30 +1472,22 @@
}; };
Job.prototype.frames.ranges.implementation = async function () { Job.prototype.frames.ranges.implementation = async function () {
const rangesData = await getRanges( const rangesData = await getRanges(this.task.id);
this.task.id,
);
return rangesData; return rangesData;
}; };
// TODO: Check filter for annotations // TODO: Check filter for annotations
Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) {
if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError( throw new ArgumentError('The filters argument must be an array of strings');
'The filters argument must be an array of strings',
);
} }
if (!Number.isInteger(frame)) { if (!Number.isInteger(frame)) {
throw new ArgumentError( throw new ArgumentError('The frame argument must be an integer');
'The frame argument must be an integer',
);
} }
if (frame < this.startFrame || frame > this.stopFrame) { if (frame < this.startFrame || frame > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError(`Frame ${frame} does not exist in the job`);
`Frame ${frame} does not exist in the job`,
);
} }
const annotationsData = await getAnnotations(this, frame, allTracks, filters); const annotationsData = await getAnnotations(this, frame, allTracks, filters);
@ -1511,28 +1495,20 @@
}; };
Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) {
if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError( throw new ArgumentError('The filters argument must be an array of strings');
'The filters argument must be an array of strings',
);
} }
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError( throw new ArgumentError('The start and end frames both must be an integer');
'The start and end frames both must be an integer',
);
} }
if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError('The start frame is out of the job');
'The start frame is out of the job',
);
} }
if (frameTo < this.startFrame || frameTo > this.stopFrame) { if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError('The stop frame is out of the job');
'The stop frame is out of the job',
);
} }
const result = searchAnnotations(this, filters, frameFrom, frameTo); const result = searchAnnotations(this, filters, frameFrom, frameTo);
@ -1541,21 +1517,15 @@
Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError( throw new ArgumentError('The start and end frames both must be an integer');
'The start and end frames both must be an integer',
);
} }
if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError('The start frame is out of the job');
'The start frame is out of the job',
);
} }
if (frameTo < this.startFrame || frameTo > this.stopFrame) { if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError( throw new ArgumentError('The stop frame is out of the job');
'The stop frame is out of the job',
);
} }
const result = searchEmptyFrame(this, frameFrom, frameTo); const result = searchEmptyFrame(this, frameFrom, frameTo);
@ -1674,7 +1644,7 @@
Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) { Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) {
// TODO: Add ability to change an owner and an assignee // TODO: Add ability to change an owner and an assignee
if (typeof (this.id) !== 'undefined') { if (typeof this.id !== 'undefined') {
// If the task has been already created, we update it // If the task has been already created, we update it
const taskData = { const taskData = {
assignee: this.assignee ? this.assignee.id : null, assignee: this.assignee ? this.assignee.id : null,
@ -1692,13 +1662,13 @@
labels: this.labels.map((el) => el.toJSON()), labels: this.labels.map((el) => el.toJSON()),
}; };
if (typeof (this.bugTracker) !== 'undefined') { if (typeof this.bugTracker !== 'undefined') {
taskSpec.bug_tracker = this.bugTracker; taskSpec.bug_tracker = this.bugTracker;
} }
if (typeof (this.segmentSize) !== 'undefined') { if (typeof this.segmentSize !== 'undefined') {
taskSpec.segment_size = this.segmentSize; taskSpec.segment_size = this.segmentSize;
} }
if (typeof (this.overlap) !== 'undefined') { if (typeof this.overlap !== 'undefined') {
taskSpec.overlap = this.overlap; taskSpec.overlap = this.overlap;
} }
@ -1711,16 +1681,16 @@
use_cache: this.useCache, use_cache: this.useCache,
}; };
if (typeof (this.startFrame) !== 'undefined') { if (typeof this.startFrame !== 'undefined') {
taskDataSpec.start_frame = this.startFrame; taskDataSpec.start_frame = this.startFrame;
} }
if (typeof (this.stopFrame) !== 'undefined') { if (typeof this.stopFrame !== 'undefined') {
taskDataSpec.stop_frame = this.stopFrame; taskDataSpec.stop_frame = this.stopFrame;
} }
if (typeof (this.frameFilter) !== 'undefined') { if (typeof this.frameFilter !== 'undefined') {
taskDataSpec.frame_filter = this.frameFilter; taskDataSpec.frame_filter = this.frameFilter;
} }
if (typeof (this.dataChunkSize) !== 'undefined') { if (typeof this.dataChunkSize !== 'undefined') {
taskDataSpec.chunk_size = this.dataChunkSize; taskDataSpec.chunk_size = this.dataChunkSize;
} }
@ -1735,15 +1705,11 @@
Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) { Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) {
if (!Number.isInteger(frame) || frame < 0) { if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError( throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
`Frame must be a positive integer. Got: "${frame}"`,
);
} }
if (frame >= this.size) { if (frame >= this.size) {
throw new ArgumentError( throw new ArgumentError(`The frame with number ${frame} is out of the task`);
`The frame with number ${frame} is out of the task`,
);
} }
const result = await getFrame( const result = await getFrame(
@ -1766,9 +1732,7 @@
}; };
Task.prototype.frames.ranges.implementation = async function () { Task.prototype.frames.ranges.implementation = async function () {
const rangesData = await getRanges( const rangesData = await getRanges(this.id);
this.id,
);
return rangesData; return rangesData;
}; };
@ -1779,22 +1743,16 @@
// TODO: Check filter for annotations // TODO: Check filter for annotations
Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) {
if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError( throw new ArgumentError('The filters argument must be an array of strings');
'The filters argument must be an array of strings',
);
} }
if (!Number.isInteger(frame) || frame < 0) { if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError( throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
`Frame must be a positive integer. Got: "${frame}"`,
);
} }
if (frame >= this.size) { if (frame >= this.size) {
throw new ArgumentError( throw new ArgumentError(`Frame ${frame} does not exist in the task`);
`Frame ${frame} does not exist in the task`,
);
} }
const result = await getAnnotations(this, frame, allTracks, filters); const result = await getAnnotations(this, frame, allTracks, filters);
@ -1802,28 +1760,20 @@
}; };
Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) {
if (!Array.isArray(filters) || filters.some((filter) => typeof (filter) !== 'string')) { if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError( throw new ArgumentError('The filters argument must be an array of strings');
'The filters argument must be an array of strings',
);
} }
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError( throw new ArgumentError('The start and end frames both must be an integer');
'The start and end frames both must be an integer',
);
} }
if (frameFrom < 0 || frameFrom >= this.size) { if (frameFrom < 0 || frameFrom >= this.size) {
throw new ArgumentError( throw new ArgumentError('The start frame is out of the task');
'The start frame is out of the task',
);
} }
if (frameTo < 0 || frameTo >= this.size) { if (frameTo < 0 || frameTo >= this.size) {
throw new ArgumentError( throw new ArgumentError('The stop frame is out of the task');
'The stop frame is out of the task',
);
} }
const result = searchAnnotations(this, filters, frameFrom, frameTo); const result = searchAnnotations(this, filters, frameFrom, frameTo);
@ -1832,21 +1782,15 @@
Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError( throw new ArgumentError('The start and end frames both must be an integer');
'The start and end frames both must be an integer',
);
} }
if (frameFrom < 0 || frameFrom >= this.size) { if (frameFrom < 0 || frameFrom >= this.size) {
throw new ArgumentError( throw new ArgumentError('The start frame is out of the task');
'The start frame is out of the task',
);
} }
if (frameTo < 0 || frameTo >= this.size) { if (frameTo < 0 || frameTo >= this.size) {
throw new ArgumentError( throw new ArgumentError('The stop frame is out of the task');
'The stop frame is out of the task',
);
} }
const result = searchEmptyFrame(this, frameFrom, frameTo); const result = searchEmptyFrame(this, frameFrom, frameTo);

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
@ -12,7 +10,9 @@
*/ */
class Statistics { class Statistics {
constructor(label, total) { constructor(label, total) {
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
/** /**
* Statistics by labels with a structure: * Statistics by labels with a structure:
* @example * @example
@ -91,7 +91,8 @@
total: { total: {
get: () => JSON.parse(JSON.stringify(total)), get: () => JSON.parse(JSON.stringify(total)),
}, },
})); }),
);
} }
} }

@ -1,7 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
@ -27,13 +26,14 @@
}; };
for (const property in data) { for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property) if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
&& property in initialData) {
data[property] = initialData[property]; data[property] = initialData[property];
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
id: { id: {
/** /**
* @name id * @name id
@ -154,7 +154,8 @@
*/ */
get: () => !data.email_verification_required, get: () => !data.email_verification_required,
}, },
})); }),
);
} }
} }

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -48,30 +41,25 @@ describe('Feature: get annotations', () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
// Out of task // Out of task
expect(task.annotations.get(500)) expect(task.annotations.get(500)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
// Out of task // Out of task
expect(task.annotations.get(-1)) expect(task.annotations.get(-1)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get annotations for frame out of job', async () => { test('get annotations for frame out of job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 101 }))[0];
// Out of segment // Out of segment
expect(job.annotations.get(500)) expect(job.annotations.get(500)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
// Out of segment // Out of segment
expect(job.annotations.get(-1)) expect(job.annotations.get(-1)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
// TODO: Test filter (hasn't been implemented yet) // TODO: Test filter (hasn't been implemented yet)
}); });
describe('Feature: put annotations', () => { describe('Feature: put annotations', () => {
test('put a shape to a task', async () => { test('put a shape to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const task = (await window.cvat.tasks.get({ id: 101 }))[0];
@ -173,8 +161,7 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape with bad attributes to a task', async () => { test('put shape with bad attributes to a task', async () => {
@ -191,8 +178,7 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape with bad zOrder to a task', async () => { test('put shape with bad zOrder to a task', async () => {
@ -209,8 +195,7 @@ describe('Feature: put annotations', () => {
zOrder: 'bad value', zOrder: 'bad value',
}); });
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const state1 = new window.cvat.classes.ObjectState({ const state1 = new window.cvat.classes.ObjectState({
frame: 1, frame: 1,
@ -223,8 +208,7 @@ describe('Feature: put annotations', () => {
zOrder: NaN, zOrder: NaN,
}); });
expect(task.annotations.put([state1])) expect(task.annotations.put([state1])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape without points and with invalud points to a task', async () => { test('put shape without points and with invalud points to a task', async () => {
@ -240,16 +224,13 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
await expect(task.annotations.put([state])) await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
.rejects.toThrow(window.cvat.exceptions.DataError);
delete state.points; delete state.points;
await expect(task.annotations.put([state])) await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
.rejects.toThrow(window.cvat.exceptions.DataError);
state.points = ['150,50 250,30']; state.points = ['150,50 250,30'];
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape without type to a task', async () => { test('put shape without type to a task', async () => {
@ -264,8 +245,7 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape without label and with bad label to a task', async () => { test('put shape without label and with bad label to a task', async () => {
@ -280,16 +260,13 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
await expect(task.annotations.put([state])) await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = 'bad label'; state.label = 'bad label';
await expect(task.annotations.put([state])) await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = {}; state.label = {};
await expect(task.annotations.put([state])) await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('put shape with bad frame to a task', async () => { test('put shape with bad frame to a task', async () => {
@ -305,8 +282,7 @@ describe('Feature: put annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(task.annotations.put([state])) expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
@ -436,8 +412,7 @@ describe('Feature: save annotations', () => {
// have been sent to a server // have been sent to a server
const oldImplementation = serverProxy.annotations.updateAnnotations; const oldImplementation = serverProxy.annotations.updateAnnotations;
serverProxy.annotations.updateAnnotations = async (session, id, data, action) => { serverProxy.annotations.updateAnnotations = async (session, id, data, action) => {
const result = await oldImplementation const result = await oldImplementation.call(serverProxy.annotations, session, id, data, action);
.call(serverProxy.annotations, session, id, data, action);
if (action === 'delete') { if (action === 'delete') {
okay = okay || (action === 'delete' && !!(data.shapes.length || data.tracks.length)); okay = okay || (action === 'delete' && !!(data.shapes.length || data.tracks.length));
} }
@ -459,10 +434,12 @@ describe('Feature: merge annotations', () => {
const annotations1 = await task.annotations.get(1); const annotations1 = await task.annotations.get(1);
const states = [annotations0[0], annotations1[0]]; const states = [annotations0[0], annotations1[0]];
await task.annotations.merge(states); await task.annotations.merge(states);
const merged0 = (await task.annotations.get(0)) const merged0 = (await task.annotations.get(0)).filter(
.filter((state) => state.objectType === window.cvat.enums.ObjectType.TRACK); (state) => state.objectType === window.cvat.enums.ObjectType.TRACK,
const merged1 = (await task.annotations.get(1)) );
.filter((state) => state.objectType === window.cvat.enums.ObjectType.TRACK); const merged1 = (await task.annotations.get(1)).filter(
(state) => state.objectType === window.cvat.enums.ObjectType.TRACK,
);
expect(merged0).toHaveLength(1); expect(merged0).toHaveLength(1);
expect(merged1).toHaveLength(1); expect(merged1).toHaveLength(1);
@ -476,10 +453,12 @@ describe('Feature: merge annotations', () => {
const annotations1 = await job.annotations.get(1); const annotations1 = await job.annotations.get(1);
const states = [annotations0[0], annotations1[0]]; const states = [annotations0[0], annotations1[0]];
await job.annotations.merge(states); await job.annotations.merge(states);
const merged0 = (await job.annotations.get(0)) const merged0 = (await job.annotations.get(0)).filter(
.filter((state) => state.objectType === window.cvat.enums.ObjectType.TRACK); (state) => state.objectType === window.cvat.enums.ObjectType.TRACK,
const merged1 = (await job.annotations.get(1)) );
.filter((state) => state.objectType === window.cvat.enums.ObjectType.TRACK); const merged1 = (await job.annotations.get(1)).filter(
(state) => state.objectType === window.cvat.enums.ObjectType.TRACK,
);
expect(merged0).toHaveLength(1); expect(merged0).toHaveLength(1);
expect(merged1).toHaveLength(1); expect(merged1).toHaveLength(1);
@ -492,8 +471,7 @@ describe('Feature: merge annotations', () => {
const annotations0 = await task.annotations.get(0); const annotations0 = await task.annotations.get(0);
const states = [annotations0[0], {}]; const states = [annotations0[0], {}];
expect(task.annotations.merge(states)) expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to merge object state which is not saved in a collection', async () => { test('trying to merge object state which is not saved in a collection', async () => {
@ -510,8 +488,7 @@ describe('Feature: merge annotations', () => {
}); });
const states = [annotations0[0], state]; const states = [annotations0[0], state];
expect(task.annotations.merge(states)) expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to merge with bad label', async () => { test('trying to merge with bad label', async () => {
@ -525,19 +502,18 @@ describe('Feature: merge annotations', () => {
attributes: [], attributes: [],
}); });
expect(task.annotations.merge(states)) expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to merge with different shape types', async () => { test('trying to merge with different shape types', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0); const annotations0 = await task.annotations.get(0);
const annotations1 = (await task.annotations.get(1)) const annotations1 = (await task.annotations.get(1)).filter(
.filter((state) => state.shapeType === window.cvat.enums.ObjectShape.POLYGON); (state) => state.shapeType === window.cvat.enums.ObjectShape.POLYGON,
);
const states = [annotations0[0], annotations1[0]]; const states = [annotations0[0], annotations1[0]];
expect(task.annotations.merge(states)) expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to merge with different labels', async () => { test('trying to merge with different labels', async () => {
@ -551,8 +527,7 @@ describe('Feature: merge annotations', () => {
attributes: [], attributes: [],
}); });
expect(task.annotations.merge(states)) expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
@ -587,8 +562,9 @@ describe('Feature: split annotations', () => {
const annotations5 = await task.annotations.get(5); const annotations5 = await task.annotations.get(5);
expect(annotations4[0].clientID).toBe(annotations5[0].clientID); expect(annotations4[0].clientID).toBe(annotations5[0].clientID);
expect(task.annotations.split(annotations5[0], 'bad frame')) expect(task.annotations.split(annotations5[0], 'bad frame')).rejects.toThrow(
.rejects.toThrow(window.cvat.exceptions.ArgumentError); window.cvat.exceptions.ArgumentError,
);
}); });
}); });
@ -597,7 +573,7 @@ describe('Feature: group annotations', () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0); let annotations = await task.annotations.get(0);
const groupID = await task.annotations.group(annotations); const groupID = await task.annotations.group(annotations);
expect(typeof (groupID)).toBe('number'); expect(typeof groupID).toBe('number');
annotations = await task.annotations.get(0); annotations = await task.annotations.get(0);
for (const state of annotations) { for (const state of annotations) {
expect(state.group.id).toBe(groupID); expect(state.group.id).toBe(groupID);
@ -608,7 +584,7 @@ describe('Feature: group annotations', () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0); let annotations = await job.annotations.get(0);
const groupID = await job.annotations.group(annotations); const groupID = await job.annotations.group(annotations);
expect(typeof (groupID)).toBe('number'); expect(typeof groupID).toBe('number');
annotations = await job.annotations.get(0); annotations = await job.annotations.get(0);
for (const state of annotations) { for (const state of annotations) {
expect(state.group.id).toBe(groupID); expect(state.group.id).toBe(groupID);
@ -629,15 +605,13 @@ describe('Feature: group annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(task.annotations.group([state])) expect(task.annotations.group([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to group not object state', async () => { test('trying to group not object state', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0); const annotations = await task.annotations.get(0);
expect(task.annotations.group(annotations.concat({}))) expect(task.annotations.group(annotations.concat({}))).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
@ -689,8 +663,7 @@ describe('Feature: clear annotations', () => {
test('clear annotations with bad reload parameter', async () => { test('clear annotations with bad reload parameter', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
await task.annotations.clear(true); await task.annotations.clear(true);
expect(task.annotations.clear('reload')) expect(task.annotations.clear('reload')).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
@ -752,18 +725,16 @@ describe('Feature: select object', () => {
test('trying to select from not object states', async () => { test('trying to select from not object states', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0); const annotations = await task.annotations.get(0);
expect(task.annotations.select(annotations.concat({}), 500, 500)) expect(task.annotations.select(annotations.concat({}), 500, 500)).rejects.toThrow(
.rejects.toThrow(window.cvat.exceptions.ArgumentError); window.cvat.exceptions.ArgumentError,
);
}); });
test('trying to select with invalid coordinates', async () => { test('trying to select with invalid coordinates', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0); const annotations = await task.annotations.get(0);
expect(task.annotations.select(annotations, null, null)) expect(task.annotations.select(annotations, null, null)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError); expect(task.annotations.select(annotations, null, null)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.annotations.select(annotations, null, null)) expect(task.annotations.select(annotations, '5', '10')).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.annotations.select(annotations, '5', '10'))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -35,22 +28,18 @@ describe('Feature: get frame meta', () => {
test('pass frame number out of a task', async () => { test('pass frame number out of a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get(100)) expect(task.frames.get(100)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError); expect(task.frames.get(-1)).rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.frames.get(-1))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('pass bad frame number', async () => { test('pass bad frame number', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get('5')) expect(task.frames.get('5')).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('do not pass any frame number', async () => { test('do not pass any frame number', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get()) expect(task.frames.get()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
@ -59,14 +48,14 @@ describe('Feature: get frame data', () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const frame = await task.frames.get(0); const frame = await task.frames.get(0);
const frameData = await frame.data(); const frameData = await frame.data();
expect(typeof (frameData)).toBe('string'); expect(typeof frameData).toBe('string');
}); });
test('get frame data for a job', async () => { test('get frame data for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const frame = await job.frames.get(0); const frame = await job.frames.get(0);
const frameData = await frame.data(); const frameData = await frame.data();
expect(typeof (frameData)).toBe('string'); expect(typeof frameData).toBe('string');
}); });
}); });
@ -74,12 +63,12 @@ describe('Feature: get frame preview', () => {
test('get frame preview for a task', async () => { test('get frame preview for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const frame = await task.frames.preview(); const frame = await task.frames.preview();
expect(typeof (frame)).toBe('string'); expect(typeof frame).toBe('string');
}); });
test('get frame preview for a job', async () => { test('get frame preview for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const frame = await job.frames.preview(); const frame = await job.frames.preview();
expect(typeof (frame)).toBe('string'); expect(typeof frame).toBe('string');
}); });
}); });

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -20,7 +13,6 @@ window.cvat = require('../../src/api');
const { Job } = require('../../src/session'); const { Job } = require('../../src/session');
// Test cases // Test cases
describe('Feature: get a list of jobs', () => { describe('Feature: get a list of jobs', () => {
test('get jobs by a task id', async () => { test('get jobs by a task id', async () => {
@ -45,7 +37,6 @@ describe('Feature: get a list of jobs', () => {
expect(result).toHaveLength(0); expect(result).toHaveLength(0);
}); });
test('get jobs by a job id', async () => { test('get jobs by a job id', async () => {
const result = await window.cvat.jobs.get({ const result = await window.cvat.jobs.get({
jobID: 1, jobID: 1,
@ -64,32 +55,39 @@ describe('Feature: get a list of jobs', () => {
}); });
test('get jobs by invalid filter with both taskID and jobID', async () => { test('get jobs by invalid filter with both taskID and jobID', async () => {
expect(window.cvat.jobs.get({ expect(
window.cvat.jobs.get({
taskID: 1, taskID: 1,
jobID: 1, jobID: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get jobs by invalid job id', async () => { test('get jobs by invalid job id', async () => {
expect(window.cvat.jobs.get({ expect(
window.cvat.jobs.get({
jobID: '1', jobID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get jobs by invalid task id', async () => { test('get jobs by invalid task id', async () => {
expect(window.cvat.jobs.get({ expect(
window.cvat.jobs.get({
taskID: '1', taskID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get jobs by unknown filter', async () => { test('get jobs by unknown filter', async () => {
expect(window.cvat.jobs.get({ expect(
window.cvat.jobs.get({
unknown: 50, unknown: 50,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });
describe('Feature: save job', () => { describe('Feature: save job', () => {
test('save status of a job', async () => { test('save status of a job', async () => {
let result = await window.cvat.jobs.get({ let result = await window.cvat.jobs.get({

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -168,30 +161,25 @@ describe('Feature: save object from its state', () => {
const state = annotations[0]; const state = annotations[0];
state.occluded = 'false'; state.occluded = 'false';
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points; const oldPoints = state.points;
state.occluded = false; state.occluded = false;
state.points = ['100', '50', '100', {}]; state.points = ['100', '50', '100', {}];
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints; state.points = oldPoints;
state.lock = 'true'; state.lock = 'true';
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldLabel = state.label; const oldLabel = state.label;
state.lock = false; state.lock = false;
state.label = 1; state.label = 1;
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = oldLabel; state.label = oldLabel;
state.attributes = { 1: {}, 2: false, 3: () => {} }; state.attributes = { 1: {}, 2: false, 3: () => {} };
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('save bad values for a track', async () => { test('save bad values for a track', async () => {
@ -200,40 +188,33 @@ describe('Feature: save object from its state', () => {
const state = annotations[0]; const state = annotations[0];
state.occluded = 'false'; state.occluded = 'false';
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points; const oldPoints = state.points;
state.occluded = false; state.occluded = false;
state.points = ['100', '50', '100', {}]; state.points = ['100', '50', '100', {}];
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints; state.points = oldPoints;
state.lock = 'true'; state.lock = 'true';
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldLabel = state.label; const oldLabel = state.label;
state.lock = false; state.lock = false;
state.label = 1; state.label = 1;
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = oldLabel; state.label = oldLabel;
state.outside = 5; state.outside = 5;
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.outside = false; state.outside = false;
state.keyframe = '10'; state.keyframe = '10';
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.keyframe = true; state.keyframe = true;
state.attributes = { 1: {}, 2: false, 3: () => {} }; state.attributes = { 1: {}, 2: false, 3: () => {} };
await expect(state.save()) await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('trying to change locked shape', async () => { test('trying to change locked shape', async () => {

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -17,11 +10,7 @@ jest.mock('../../src/server-proxy', () => {
// Initialize api // Initialize api
window.cvat = require('../../src/api'); window.cvat = require('../../src/api');
const { const { AnnotationFormats, Loader, Dumper } = require('../../src/annotation-formats');
AnnotationFormats,
Loader,
Dumper,
} = require('../../src/annotation-formats');
// Test cases // Test cases
describe('Feature: get info about cvat', () => { describe('Feature: get info about cvat', () => {
@ -34,7 +23,6 @@ describe('Feature: get info about cvat', () => {
}); });
}); });
describe('Feature: get share storage info', () => { describe('Feature: get share storage info', () => {
test('get files in a root of a share storage', async () => { test('get files in a root of a share storage', async () => {
const result = await window.cvat.server.share(); const result = await window.cvat.server.share();
@ -49,9 +37,7 @@ describe('Feature: get share storage info', () => {
}); });
test('get files in a some unknown dir of a share storage', async () => { test('get files in a some unknown dir of a share storage', async () => {
expect(window.cvat.server.share( expect(window.cvat.server.share('Unknown Directory')).rejects.toThrow(window.cvat.exceptions.ServerError);
'Unknown Directory',
)).rejects.toThrow(window.cvat.exceptions.ServerError);
}); });
}); });

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -20,7 +13,6 @@ window.cvat = require('../../src/api');
const { Task } = require('../../src/session'); const { Task } = require('../../src/session');
// Test cases // Test cases
describe('Feature: get a list of tasks', () => { describe('Feature: get a list of tasks', () => {
test('get all tasks', async () => { test('get all tasks', async () => {
@ -51,9 +43,11 @@ describe('Feature: get a list of tasks', () => {
}); });
test('get a task by an invalid id', async () => { test('get a task by an invalid id', async () => {
expect(window.cvat.tasks.get({ expect(
window.cvat.tasks.get({
id: '50', id: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get tasks by filters', async () => { test('get tasks by filters', async () => {
@ -69,9 +63,11 @@ describe('Feature: get a list of tasks', () => {
}); });
test('get tasks by invalid filters', async () => { test('get tasks by invalid filters', async () => {
expect(window.cvat.tasks.get({ expect(
window.cvat.tasks.get({
unknown: '5', unknown: '5',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get task by name, status and mode', async () => { test('get task by name, status and mode', async () => {
@ -117,14 +113,16 @@ describe('Feature: save a task', () => {
const labelsLength = result[0].labels.length; const labelsLength = result[0].labels.length;
const newLabel = new window.cvat.classes.Label({ const newLabel = new window.cvat.classes.Label({
name: 'My boss\'s car', name: "My boss's car",
attributes: [{ attributes: [
{
default_value: 'false', default_value: 'false',
input_type: 'checkbox', input_type: 'checkbox',
mutable: true, mutable: true,
name: 'parked', name: 'parked',
values: ['false'], values: ['false'],
}], },
],
}); });
result[0].labels = [...result[0].labels, newLabel]; result[0].labels = [...result[0].labels, newLabel];
@ -135,7 +133,7 @@ describe('Feature: save a task', () => {
}); });
expect(result[0].labels).toHaveLength(labelsLength + 1); 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).toHaveLength(1);
expect(appendedLabel[0].attributes).toHaveLength(1); expect(appendedLabel[0].attributes).toHaveLength(1);
expect(appendedLabel[0].attributes[0].name).toBe('parked'); expect(appendedLabel[0].attributes[0].name).toBe('parked');
@ -147,22 +145,26 @@ describe('Feature: save a task', () => {
test('save new task without an id', async () => { test('save new task without an id', async () => {
const task = new window.cvat.classes.Task({ const task = new window.cvat.classes.Task({
name: 'New Task', name: 'New Task',
labels: [{ labels: [
name: 'My boss\'s car', {
attributes: [{ name: "My boss's car",
attributes: [
{
default_value: 'false', default_value: 'false',
input_type: 'checkbox', input_type: 'checkbox',
mutable: true, mutable: true,
name: 'parked', name: 'parked',
values: ['false'], values: ['false'],
}], },
}], ],
},
],
bug_tracker: 'bug tracker value', bug_tracker: 'bug tracker value',
image_quality: 50, image_quality: 50,
}); });
const result = await task.save(); const result = await task.save();
expect(typeof (result.id)).toBe('number'); expect(typeof result.id).toBe('number');
}); });
}); });

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -41,14 +34,18 @@ describe('Feature: get a list of users', () => {
}); });
test('get users with unknown filter key', async () => { test('get users with unknown filter key', async () => {
expect(window.cvat.users.get({ expect(
window.cvat.users.get({
unknown: '50', unknown: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
test('get users with invalid filter key', async () => { test('get users with invalid filter key', async () => {
expect(window.cvat.users.get({ expect(
window.cvat.users.get({
self: 1, self: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); }),
).rejects.toThrow(window.cvat.exceptions.ArgumentError);
}); });
}); });

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server // Setup mock for a server
jest.mock('../../src/server-proxy', () => { jest.mock('../../src/server-proxy', () => {
@ -25,7 +18,7 @@ describe('Feature: toJSONQuery', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery([]); const [groups, query] = annotationsFilter.toJSONQuery([]);
expect(Array.isArray(groups)).toBeTruthy(); expect(Array.isArray(groups)).toBeTruthy();
expect(typeof (query)).toBe('string'); expect(typeof query).toBe('string');
}); });
test('convert empty fitlers to a json query', () => { test('convert empty fitlers to a json query', () => {
@ -64,61 +57,65 @@ describe('Feature: toJSONQuery', () => {
test('convert filters to a json query', () => { test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter const [groups, query] = annotationsFilter.toJSONQuery(['clientID==5 & shape=="rectangle" & label==["car"]']);
.toJSONQuery(['clientID==5 & shape=="rectangle" & label==["car"]']); expect(groups).toEqual([['clientID==5', '&', 'shape=="rectangle"', '&', 'label==["car"]']]);
expect(groups).toEqual([
['clientID==5', '&', 'shape=="rectangle"', '&', 'label==["car"]'],
]);
expect(query).toBe('$.objects[?((@.clientID==5&@.shape=="rectangle"&@.label==["car"]))].clientID'); expect(query).toBe('$.objects[?((@.clientID==5&@.shape=="rectangle"&@.label==["car"]))].clientID');
}); });
test('convert filters to a json query', () => { test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter const [groups, query] = annotationsFilter.toJSONQuery(['label=="car" | width >= height & type=="track"']);
.toJSONQuery(['label=="car" | width >= height & type=="track"']); expect(groups).toEqual([['label=="car"', '|', 'width >= height', '&', 'type=="track"']]);
expect(groups).toEqual([
['label=="car"', '|', 'width >= height', '&', 'type=="track"'],
]);
expect(query).toBe('$.objects[?((@.label=="car"|@.width>=@.height&@.type=="track"))].clientID'); expect(query).toBe('$.objects[?((@.label=="car"|@.width>=@.height&@.type=="track"))].clientID');
}); });
test('convert filters to a json query', () => { test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter const [groups, query] = annotationsFilter.toJSONQuery([
.toJSONQuery(['label=="person" & attr["Attribute 1"] ==attr["Attribute 2"]']); 'label=="person" & attr["Attribute 1"] ==attr["Attribute 2"]',
expect(groups).toEqual([
['label=="person"', '&', 'attr["Attribute 1"] ==attr["Attribute 2"]'],
]); ]);
expect(groups).toEqual([['label=="person"', '&', 'attr["Attribute 1"] ==attr["Attribute 2"]']]);
expect(query).toBe('$.objects[?((@.label=="person"&@.attr["Attribute 1"]==@.attr["Attribute 2"]))].clientID'); expect(query).toBe('$.objects[?((@.label=="person"&@.attr["Attribute 1"]==@.attr["Attribute 2"]))].clientID');
}); });
test('convert filters to a json query', () => { test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter const [groups, query] = annotationsFilter.toJSONQuery([
.toJSONQuery(['label=="car" & attr["parked"]==true', 'label=="pedestrian" & width > 150']); 'label=="car" & attr["parked"]==true',
'label=="pedestrian" & width > 150',
]);
expect(groups).toEqual([ expect(groups).toEqual([
['label=="car"', '&', 'attr["parked"]==true'], ['label=="car"', '&', 'attr["parked"]==true'],
'|', '|',
['label=="pedestrian"', '&', 'width > 150'], ['label=="pedestrian"', '&', 'width > 150'],
]); ]);
expect(query).toBe('$.objects[?((@.label=="car"&@.attr["parked"]==true)|(@.label=="pedestrian"&@.width>150))].clientID'); expect(query).toBe(
'$.objects[?((@.label=="car"&@.attr["parked"]==true)|(@.label=="pedestrian"&@.width>150))].clientID',
);
}); });
test('convert filters to a json query', () => { test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter(); const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter const [groups, query] = annotationsFilter.toJSONQuery([
.toJSONQuery(['(( label==["car \\"mazda\\""]) & (attr["sunglass ( help ) es"]==true | (width > 150 | height > 150 & (clientID == serverID))))) ']); // eslint-disable-next-line
expect(groups).toEqual([[[ '(( label==["car \\"mazda\\""]) & (attr["sunglass ( help ) es"]==true | (width > 150 | height > 150 & (clientID == serverID))))) ',
]);
expect(groups).toEqual([
[
[
['label==["car `mazda`"]'], ['label==["car `mazda`"]'],
'&', '&',
['attr["sunglass ( help ) es"]==true', '|',
['width > 150', '|', 'height > 150', '&',
[ [
'clientID == serverID', 'attr["sunglass ( help ) es"]==true',
'|',
['width > 150', '|', 'height > 150', '&', ['clientID == serverID']],
], ],
], ],
], ],
]]]); ]);
expect(query).toBe('$.objects[?((((@.label==["car `mazda`"])&(@.attr["sunglass ( help ) es"]==true|(@.width>150|@.height>150&(@.clientID==serverID))))))].clientID'); expect(query).toBe(
// eslint-disable-next-line
'$.objects[?((((@.label==["car `mazda`"])&(@.attr["sunglass ( help ) es"]==true|(@.width>150|@.height>150&(@.clientID==serverID))))))].clientID',
);
}); });
}); });

File diff suppressed because it is too large Load Diff

@ -1,13 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* eslint import/no-extraneous-dependencies: 0 */
/* global
require:false
*/
const { const {
tasksDummyData, tasksDummyData,
@ -34,14 +27,11 @@ class ServerProxy {
const components = directory.split('/'); const components = directory.split('/');
for (const component of components) { for (const component of components) {
const idx = position.map(x => x.name).indexOf(component); const idx = position.map((x) => x.name).indexOf(component);
if (idx !== -1 && 'children' in position[idx]) { if (idx !== -1 && 'children' in position[idx]) {
position = position[idx].children; position = position[idx].children;
} else { } else {
throw new window.cvat.exceptions.ServerError( throw new window.cvat.exceptions.ServerError(`${component} is not a valid directory`, 400);
`${component} is not a valid directory`,
400,
);
} }
} }
} }
@ -101,17 +91,19 @@ class ServerProxy {
} }
async function saveTask(id, taskData) { async function saveTask(id, taskData) {
const object = tasksDummyData.results.filter(task => task.id === id)[0]; const object = tasksDummyData.results.filter((task) => task.id === id)[0];
for (const prop in taskData) { for (const prop in taskData) {
if (Object.prototype.hasOwnProperty.call(taskData, prop) if (
&& Object.prototype.hasOwnProperty.call(object, prop)) { Object.prototype.hasOwnProperty.call(taskData, prop) &&
Object.prototype.hasOwnProperty.call(object, prop)
) {
object[prop] = taskData[prop]; object[prop] = taskData[prop];
} }
} }
} }
async function createTask(taskData) { async function createTask(taskData) {
const id = Math.max(...tasksDummyData.results.map(el => el.id)) + 1; const id = Math.max(...tasksDummyData.results.map((el) => el.id)) + 1;
tasksDummyData.results.push({ tasksDummyData.results.push({
id, id,
url: `http://localhost:7000/api/v1/tasks/${id}`, url: `http://localhost:7000/api/v1/tasks/${id}`,
@ -137,14 +129,15 @@ class ServerProxy {
async function deleteTask(id) { async function deleteTask(id) {
const tasks = tasksDummyData.results; const tasks = tasksDummyData.results;
const task = tasks.filter(el => el.id === id)[0]; const task = tasks.filter((el) => el.id === id)[0];
if (task) { if (task) {
tasks.splice(tasks.indexOf(task), 1); tasks.splice(tasks.indexOf(task), 1);
} }
} }
async function getJob(jobID) { async function getJob(jobID) {
const jobs = tasksDummyData.results.reduce((acc, task) => { const jobs = tasksDummyData.results
.reduce((acc, task) => {
for (const segment of task.segments) { for (const segment of task.segments) {
for (const job of segment.jobs) { for (const job of segment.jobs) {
const copy = JSON.parse(JSON.stringify(job)); const copy = JSON.parse(JSON.stringify(job));
@ -157,15 +150,19 @@ class ServerProxy {
} }
return acc; return acc;
}, []).filter(job => job.id === jobID); }, [])
.filter((job) => job.id === jobID);
return jobs[0] || { return (
jobs[0] || {
detail: 'Not found.', detail: 'Not found.',
}; }
);
} }
async function saveJob(id, jobData) { async function saveJob(id, jobData) {
const object = tasksDummyData.results.reduce((acc, task) => { const object = tasksDummyData.results
.reduce((acc, task) => {
for (const segment of task.segments) { for (const segment of task.segments) {
for (const job of segment.jobs) { for (const job of segment.jobs) {
acc.push(job); acc.push(job);
@ -173,11 +170,14 @@ class ServerProxy {
} }
return acc; return acc;
}, []).filter(job => job.id === id)[0]; }, [])
.filter((job) => job.id === id)[0];
for (const prop in jobData) { for (const prop in jobData) {
if (Object.prototype.hasOwnProperty.call(jobData, prop) if (
&& Object.prototype.hasOwnProperty.call(object, prop)) { Object.prototype.hasOwnProperty.call(jobData, prop) &&
Object.prototype.hasOwnProperty.call(object, prop)
) {
object[prop] = jobData[prop]; object[prop] = jobData[prop];
} }
} }
@ -223,7 +223,10 @@ class ServerProxy {
if (action === 'create') { if (action === 'create') {
let idGenerator = 1000; let idGenerator = 1000;
data.tracks.concat(data.tags).concat(data.shapes).map((el) => { data.tracks
.concat(data.tags)
.concat(data.shapes)
.map((el) => {
el.id = ++idGenerator; el.id = ++idGenerator;
return el; return el;
}); });
@ -242,7 +245,9 @@ class ServerProxy {
return null; return null;
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
this,
Object.freeze({
server: { server: {
value: Object.freeze({ value: Object.freeze({
about, about,
@ -298,7 +303,8 @@ class ServerProxy {
// To implement on of important tests // To implement on of important tests
writable: true, writable: true,
}, },
})); }),
);
} }
} }

@ -1,6 +1,9 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/* global /* global
require:true, __dirname:true
__dirname:true,
*/ */
const path = require('path'); const path = require('path');
@ -16,10 +19,12 @@ const nodeConfig = {
libraryTarget: 'commonjs', libraryTarget: 'commonjs',
}, },
module: { module: {
rules: [{ rules: [
{
test: /.js?$/, test: /.js?$/,
exclude: /node_modules/, exclude: /node_modules/,
}], },
],
}, },
stats: { stats: {
warnings: false, warnings: false,
@ -40,21 +45,26 @@ const webConfig = {
libraryTarget: 'window', libraryTarget: 'window',
}, },
module: { module: {
rules: [{ rules: [
{
test: /.js?$/, test: /.js?$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
['@babel/preset-env', { [
'@babel/preset-env',
{
targets: '> 2.5%', targets: '> 2.5%',
}], },
],
], ],
sourceType: 'unambiguous', sourceType: 'unambiguous',
}, },
}, },
}, { },
{
test: /3rdparty\/.*\.worker\.js$/, test: /3rdparty\/.*\.worker\.js$/,
use: { use: {
loader: 'worker-loader', loader: 'worker-loader',
@ -63,7 +73,8 @@ const webConfig = {
name: '[name].[contenthash].js', name: '[name].[contenthash].js',
}, },
}, },
}, { },
{
test: /\.worker\.js$/, test: /\.worker\.js$/,
exclude: /3rdparty/, exclude: /3rdparty/,
use: { use: {

@ -1,56 +1,50 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2018-2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
"env": { env: {
"node": false, node: false,
"browser": true, browser: true,
"es6": true, es6: true,
"jquery": true, jquery: true,
"qunit": true, qunit: true,
}, },
"parserOptions": { parserOptions: {
"parser": "babel-eslint", parser: 'babel-eslint',
"sourceType": "module", sourceType: 'module',
"ecmaVersion": 2018, 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',
}, },
"plugins": [
"security",
"no-unsanitized",
"no-unsafe-innerhtml",
],
"extends": [
"eslint:recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"airbnb-base",
], ],
"rules": { 'no-plusplus': [0],
"no-await-in-loop": [0], 'no-param-reassign': [0],
"global-require": [0], 'no-underscore-dangle': ['error', { allowAfterThis: true }],
"no-new": [0], 'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
"class-methods-use-this": [0], 'no-continue': [0],
"no-restricted-properties": [0, { 'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
"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. // This rule actual for user input data on the node.js environment mainly.
"security/detect-object-injection": 0, 'security/detect-object-injection': 0,
"indent": ["warn", 4], indent: ['warn', 4],
"no-useless-constructor": 0, 'no-useless-constructor': 0,
"func-names": [0], 'func-names': [0],
"valid-typeof": [0], 'valid-typeof': [0],
"no-console": [0], // this rule deprecates console.log, console.warn etc. because "it is not good in production code" 'no-console': [0],
"max-classes-per-file": [0], 'max-classes-per-file': [0],
"quotes": ["warn", "single"], 'max-len': ['error', { code: 120 }],
quotes: ['error', 'single'],
'operator-linebreak': ['error', 'after'],
}, },
}; };

@ -7,8 +7,9 @@ npm run server # run debug server
``` ```
## Versioning ## Versioning
If you make changes in this package, please do following: If you make changes in this package, please do following:
- After not important changes (typos, backward compatible bug fixes, refactoring) do: ``npm version patch`` - After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch`
- After changing API (backward compatible new features) do: ``npm version minor`` - After changing API (backward compatible new features) do: `npm version minor`
- After changing API (changes that break backward compatibility) do: ``npm version major`` - After changing API (changes that break backward compatibility) do: `npm version major`

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:true
*/
const { Mutex } = require('async-mutex'); const { Mutex } = require('async-mutex');
// eslint-disable-next-line max-classes-per-file // eslint-disable-next-line max-classes-per-file
@ -19,14 +14,12 @@ const BlockType = Object.freeze({
}); });
class FrameProvider { class FrameProvider {
constructor(blockType, blockSize, cachedBlockCount, constructor(blockType, blockSize, cachedBlockCount, decodedBlocksCacheSize = 5, maxWorkerThreadCount = 2) {
decodedBlocksCacheSize = 5, maxWorkerThreadCount = 2) {
this._frames = {}; this._frames = {};
this._cachedBlockCount = Math.max(1, cachedBlockCount); // number of stored blocks this._cachedBlockCount = Math.max(1, cachedBlockCount); // number of stored blocks
this._decodedBlocksCacheSize = decodedBlocksCacheSize; this._decodedBlocksCacheSize = decodedBlocksCacheSize;
this._blocksRanges = []; this._blocksRanges = [];
this._blocks = {}; this._blocks = {};
this._blockSize = blockSize;
this._running = false; this._running = false;
this._blockType = blockType; this._blockType = blockType;
this._currFrame = -1; this._currFrame = -1;
@ -42,15 +35,14 @@ class FrameProvider {
} }
async _worker() { async _worker() {
if (this._requestedBlockDecode !== null if (this._requestedBlockDecode !== null && this._decodeThreadCount < this._maxWorkerThreadCount) {
&& this._decodeThreadCount < this._maxWorkerThreadCount) {
await this.startDecode(); await this.startDecode();
} }
this._timerId = setTimeout(this._worker.bind(this), 100); this._timerId = setTimeout(this._worker.bind(this), 100);
} }
isChunkCached(start, end) { isChunkCached(start, end) {
return (`${start}:${end}` in this._blocksRanges); return `${start}:${end}` in this._blocksRanges;
} }
/* This method removes extra data from a cache when memory overflow */ /* This method removes extra data from a cache when memory overflow */
@ -68,8 +60,10 @@ class FrameProvider {
const distance = Math.floor(this._decodedBlocksCacheSize / 2); const distance = Math.floor(this._decodedBlocksCacheSize / 2);
for (let i = 0; i < this._blocksRanges.length; i++) { for (let i = 0; i < this._blocksRanges.length; i++) {
const [start, end] = this._blocksRanges[i].split(':').map((el) => +el); const [start, end] = this._blocksRanges[i].split(':').map((el) => +el);
if (end < this._currFrame - distance * this._blockSize if (
|| start > this._currFrame + distance * this._blockSize) { end < this._currFrame - distance * this._blockSize ||
start > this._currFrame + distance * this._blockSize
) {
for (let j = start; j <= end; j++) { for (let j = start; j <= end; j++) {
delete this._frames[j]; delete this._frames[j];
} }
@ -81,8 +75,7 @@ class FrameProvider {
const release = await this._mutex.acquire(); const release = await this._mutex.acquire();
try { try {
if (this._requestedBlockDecode !== null) { if (this._requestedBlockDecode !== null) {
if (start === this._requestedBlockDecode.start if (start === this._requestedBlockDecode.start && end === this._requestedBlockDecode.end) {
&& end === this._requestedBlockDecode.end) {
this._requestedBlockDecode.resolveCallback = resolveCallback; this._requestedBlockDecode.resolveCallback = resolveCallback;
this._requestedBlockDecode.rejectCallback = rejectCallback; this._requestedBlockDecode.rejectCallback = rejectCallback;
} else if (this._requestedBlockDecode.rejectCallback) { } else if (this._requestedBlockDecode.rejectCallback) {
@ -158,8 +151,7 @@ class FrameProvider {
} }
static cropImage(imageBuffer, imageWidth, imageHeight, xOffset, yOffset, width, height) { static cropImage(imageBuffer, imageWidth, imageHeight, xOffset, yOffset, width, height) {
if (xOffset === 0 && width === imageWidth if (xOffset === 0 && width === imageWidth && yOffset === 0 && height === imageHeight) {
&& yOffset === 0 && height === imageHeight) {
return new ImageData(new Uint8ClampedArray(imageBuffer), width, height); return new ImageData(new Uint8ClampedArray(imageBuffer), width, height);
} }
const source = new Uint32Array(imageBuffer); const source = new Uint32Array(imageBuffer);
@ -170,11 +162,7 @@ class FrameProvider {
const rgbaInt8Clamped = new Uint8ClampedArray(buffer); const rgbaInt8Clamped = new Uint8ClampedArray(buffer);
if (imageWidth === width) { if (imageWidth === width) {
return new ImageData( return new ImageData(new Uint8ClampedArray(imageBuffer, yOffset * 4, bufferSize), width, height);
new Uint8ClampedArray(imageBuffer, yOffset * 4, bufferSize),
width,
height,
);
} }
let writeIdx = 0; let writeIdx = 0;
@ -207,14 +195,20 @@ class FrameProvider {
let index = start; let index = start;
worker.onmessage = (e) => { worker.onmessage = (e) => {
if (e.data.consoleLog) { // ignore initialization message if (e.data.consoleLog) {
// ignore initialization message
return; return;
} }
const scaleFactor = Math.ceil(this._height / e.data.height); const scaleFactor = Math.ceil(this._height / e.data.height);
this._frames[index] = FrameProvider.cropImage( this._frames[index] = FrameProvider.cropImage(
e.data.buf, e.data.width, e.data.height, 0, 0, e.data.buf,
Math.floor(width / scaleFactor), Math.floor(height / scaleFactor), e.data.width,
e.data.height,
0,
0,
Math.floor(width / scaleFactor),
Math.floor(height / scaleFactor),
); );
if (this._decodingBlocks[`${start}:${end}`].resolveCallback) { if (this._decodingBlocks[`${start}:${end}`].resolveCallback) {
@ -303,8 +297,8 @@ class FrameProvider {
// so, we get raw data and decode it here, no other way // so, we get raw data and decode it here, no other way
const createImageBitmap = async function (blob) { const createImageBitmap = async function (blob) {
return new Promise((resolve,reject) => { return new Promise((resolve) => {
let img = document.createElement('img'); const img = document.createElement('img');
img.addEventListener('load', function () { img.addEventListener('load', function () {
resolve(this); resolve(this);
}); });
@ -322,9 +316,7 @@ class FrameProvider {
} }
if (event.data.index in this._promisedFrames) { if (event.data.index in this._promisedFrames) {
this._promisedFrames[event.data.index].resolve( this._promisedFrames[event.data.index].resolve(this._frames[event.data.index]);
this._frames[event.data.index],
);
delete this._promisedFrames[event.data.index]; delete this._promisedFrames[event.data.index];
} }
@ -357,9 +349,7 @@ class FrameProvider {
Is an array of strings like "start:end" Is an array of strings like "start:end"
*/ */
get cachedFrames() { get cachedFrames() {
return [...this._blocksRanges].sort( return [...this._blocksRanges].sort((a, b) => a.split(':')[0] - b.split(':')[0]);
(a, b) => a.split(':')[0] - b.split(':')[0],
);
} }
} }

@ -1,11 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
/* global
require:true
*/
const JSZip = require('jszip'); const JSZip = require('jszip');
@ -19,7 +14,10 @@ onmessage = (e) => {
_zip.forEach((relativePath) => { _zip.forEach((relativePath) => {
const fileIndex = index++; const fileIndex = index++;
if (fileIndex <= end) { if (fileIndex <= end) {
_zip.file(relativePath).async('blob').then((fileData) => { _zip.file(relativePath)
.async('blob')
.then((fileData) => {
// eslint-disable-next-line no-restricted-globals
if (self.createImageBitmap) { if (self.createImageBitmap) {
createImageBitmap(fileData).then((img) => { createImageBitmap(fileData).then((img) => {
postMessage({ postMessage({

@ -1,6 +1,9 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/* global /* global
require:true, __dirname:true
__dirname:true,
*/ */
const path = require('path'); const path = require('path');
@ -27,14 +30,18 @@ const cvatData = {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
['@babel/preset-env', { [
'@babel/preset-env',
{
targets: '> 2.5%', // https://github.com/browserslist/browserslist targets: '> 2.5%', // https://github.com/browserslist/browserslist
}], },
],
], ],
sourceType: 'unambiguous', sourceType: 'unambiguous',
}, },
}, },
}, { },
{
test: /\.worker\.js$/, test: /\.worker\.js$/,
exclude: /3rdparty/, exclude: /3rdparty/,
use: { use: {
@ -44,7 +51,8 @@ const cvatData = {
name: '[name].[contenthash].js', name: '[name].[contenthash].js',
}, },
}, },
}, { },
{
test: /3rdparty\/.*\.worker\.js$/, test: /3rdparty\/.*\.worker\.js$/,
use: { use: {
loader: 'worker-loader', loader: 'worker-loader',
@ -56,11 +64,7 @@ const cvatData = {
}, },
], ],
}, },
plugins: [ plugins: [new CopyPlugin(['./src/js/3rdparty/avc.wasm'])],
new CopyPlugin([
'./src/js/3rdparty/avc.wasm',
]),
],
}; };
module.exports = cvatData; module.exports = cvatData;

@ -13,17 +13,13 @@ module.exports = {
ecmaVersion: 6, ecmaVersion: 6,
project: './tsconfig.json', project: './tsconfig.json',
}, },
plugins: ['@typescript-eslint', 'import', 'eslint-plugin-header'], plugins: ['@typescript-eslint', 'import'],
ignorePatterns: ['*.svg', '*.scss'],
extends: [ extends: [
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'airbnb-typescript', 'airbnb-typescript',
'plugin:import/errors', 'plugin:import/errors',
'plugin:import/warnings', 'plugin:import/warnings',
'plugin:import/typescript', 'plugin:import/typescript',
'prettier',
'prettier/@typescript-eslint',
'prettier/react',
], ],
rules: { rules: {
'@typescript-eslint/indent': ['warn', 4], '@typescript-eslint/indent': ['warn', 4],
@ -38,12 +34,29 @@ module.exports = {
'no-plusplus': [0], 'no-plusplus': [0],
'lines-between-class-members': 0, 'lines-between-class-members': 0,
'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875 'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875
'header/header': [2, '.header-tpl.ts'], quotes: ['error', 'single'],
'max-len': ['error', { code: 120 }],
'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',
{
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
},
},
],
}, },
settings: { settings: {
'import/resolver': { 'import/resolver': {
typescript: { node: {
directory: './tsconfig.json', paths: ['src'],
}, },
}, },
}, },

@ -1,19 +1,22 @@
# cvat-ui module # cvat-ui module
## Description ## Description
This is a client UI for Computer Vision Annotation Tool based on React, Redux and Antd This is a client UI for Computer Vision Annotation Tool based on React, Redux and Antd
## Versioning ## Versioning
If you make changes in this package, please do following: If you make changes in this package, please do following:
- After not important changes (typos, bug fixes, refactoring) do: ``npm version patch`` - After not important changes (typos, bug fixes, refactoring) do: `npm version patch`
- After adding new features do: ``npm version minor`` - After adding new features do: `npm version minor`
- After significant UI redesign do: ``npm version major`` - After significant UI redesign do: `npm version major`
Important: If you have changed versions for ``cvat-core``, ``cvat-canvas``, ``cvat-data``, Important: If you have changed versions for `cvat-core`, `cvat-canvas`, `cvat-data`,
you also need to do ``npm install`` to update ``package-lock.json`` you also need to do `npm install` to update `package-lock.json`
## Commands ## Commands
- Installing dependencies: - Installing dependencies:
```bash ```bash
@ -26,12 +29,12 @@ cd ../cvat-core && npm install && cd - && npm install
npm start npm start
``` ```
- Building the module from sources in the ```dist``` directory: - Building the module from sources in the `dist` directory:
```bash ```bash
npm run build npm run build
npm run build -- --mode=development # without a minification npm run build -- --mode=development # without a minification
``` ```
Important: You also have to run CVAT REST API server (please read ``CONTRIBUTING.md``) Important: You also have to run CVAT REST API server (please read `CONTRIBUTING.md`)
to correct working since UI gets all necessary data (tasks, users, annotations) from there to correct working since UI gets all necessary data (tasks, users, annotations) from there

File diff suppressed because it is too large Load Diff

@ -20,23 +20,19 @@
"@babel/preset-env": "^7.6.0", "@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.6.0", "@babel/preset-typescript": "^7.6.0",
"@typescript-eslint/eslint-plugin": "^2.19.2", "@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^2.19.2", "@typescript-eslint/parser": "^4.5.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-import": "^1.12.2", "babel-plugin-import": "^1.12.2",
"copy-webpack-plugin": "^5.1.2", "copy-webpack-plugin": "^5.1.2",
"css-loader": "^3.2.0", "css-loader": "^3.2.0",
"eslint": "^6.8.0", "eslint": "^7.11.0",
"eslint-config-airbnb-typescript": "^7.0.0", "eslint-config-airbnb-typescript": "^12.0.0",
"eslint-config-prettier": "^6.12.0", "eslint-plugin-import": "^2.22.1",
"eslint-import-resolver-typescript": "^2.0.0", "eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-header": "^3.1.0", "eslint-plugin-react": "^7.21.5",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^1.7.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"lint-staged": "^10.4.0",
"node-sass": "^4.13.0", "node-sass": "^4.13.0",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",

@ -26,9 +26,7 @@ export const getAboutAsync = (): ThunkAction => async (dispatch): Promise<void>
try { try {
const about = await core.server.about(); const about = await core.server.about();
dispatch( dispatch(aboutActions.getAboutSuccess(about));
aboutActions.getAboutSuccess(about),
);
} catch (error) { } catch (error) {
dispatch(aboutActions.getAboutFailed(error)); dispatch(aboutActions.getAboutFailed(error));
} }

@ -2,12 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { import { AnyAction, Dispatch, ActionCreator, Store } from 'redux';
AnyAction,
Dispatch,
ActionCreator,
Store,
} from 'redux';
import { ThunkAction } from 'utils/redux'; import { ThunkAction } from 'utils/redux';
import { import {
@ -54,22 +49,14 @@ function receiveAnnotationsParameters(): AnnotationsParameters {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { const {
annotation: { annotation: {
annotations: { annotations: { filters },
filters,
},
player: { player: {
frame: { frame: { number: frame },
number: frame,
},
},
job: {
instance: jobInstance,
}, },
job: { instance: jobInstance },
}, },
settings: { settings: {
workspace: { workspace: { showAllInterpolationTracks },
showAllInterpolationTracks,
},
}, },
} = state; } = state;
@ -97,11 +84,17 @@ async function jobInfoGenerator(job: any): Promise<Record<string, number>> {
const { total } = await job.annotations.statistics(); const { total } = await job.annotations.statistics();
return { return {
'frame count': job.stopFrame - job.startFrame + 1, 'frame count': job.stopFrame - job.startFrame + 1,
'track count': total.rectangle.shape + total.rectangle.track 'track count':
+ total.polygon.shape + total.polygon.track total.rectangle.shape +
+ total.polyline.shape + total.polyline.track total.rectangle.track +
+ total.points.shape + total.points.track total.polygon.shape +
+ total.cuboid.shape + total.cuboid.track, total.polygon.track +
total.polyline.shape +
total.polyline.track +
total.points.shape +
total.points.track +
total.cuboid.shape +
total.cuboid.track,
'object count': total.total, 'object count': total.total,
'box count': total.rectangle.shape + total.rectangle.track, 'box count': total.rectangle.shape + total.rectangle.track,
'polygon count': total.polygon.shape + total.polygon.track, 'polygon count': total.polygon.shape + total.polygon.track,
@ -241,14 +234,8 @@ export function switchZLayer(cur: number): AnyAction {
export function fetchAnnotationsAsync(): ThunkAction { export function fetchAnnotationsAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { const { filters, frame, showAllInterpolationTracks, jobInstance } = receiveAnnotationsParameters();
filters, const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters);
frame,
showAllInterpolationTracks,
jobInstance,
} = receiveAnnotationsParameters();
const states = await jobInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
dispatch({ dispatch({
@ -358,11 +345,9 @@ export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): Th
const frame = state.annotation.player.frame.number; const frame = state.annotation.player.frame.number;
await job.annotations.upload(file, loader); await job.annotations.upload(file, loader);
await job.logger.log( await job.logger.log(LogType.uploadAnnotations, {
LogType.uploadAnnotations, {
...(await jobInfoGenerator(job)), ...(await jobInfoGenerator(job)),
}, });
);
await job.annotations.clear(true); await job.annotations.clear(true);
await job.actions.clear(); await job.actions.clear();
@ -470,20 +455,14 @@ export function showStatistics(visible: boolean): AnyAction {
}; };
} }
export function propagateObjectAsync( export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction {
sessionInstance: any,
objectState: any,
from: number,
to: number,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const copy = { const copy = {
attributes: objectState.attributes, attributes: objectState.attributes,
points: objectState.points, points: objectState.points,
occluded: objectState.occluded, occluded: objectState.occluded,
objectType: objectState.objectType !== ObjectType.TRACK objectType: objectState.objectType !== ObjectType.TRACK ? objectState.objectType : ObjectType.SHAPE,
? objectState.objectType : ObjectType.SHAPE,
shapeType: objectState.shapeType, shapeType: objectState.shapeType,
label: objectState.label, label: objectState.label,
zOrder: objectState.zOrder, zOrder: objectState.zOrder,
@ -491,9 +470,7 @@ export function propagateObjectAsync(
source: objectState.source, source: objectState.source,
}; };
await sessionInstance.logger.log( await sessionInstance.logger.log(LogType.propagateObject, { count: to - from + 1 });
LogType.propagateObject, { count: to - from + 1 },
);
const states = []; const states = [];
for (let frame = from; frame <= to; frame++) { for (let frame = from; frame <= to; frame++) {
copy.frame = frame; copy.frame = frame;
@ -540,8 +517,7 @@ export function changePropagateFrames(frames: number): AnyAction {
}; };
} }
export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); await sessionInstance.logger.log(LogType.deleteObject, { count: 1 });
@ -602,10 +578,7 @@ export function selectObjects(selectedStatesID: number[]): AnyAction {
}; };
} }
export function activateObject( export function activateObject(activatedStateID: number | null, activatedAttributeID: number | null): AnyAction {
activatedStateID: number | null,
activatedAttributeID: number | null,
): AnyAction {
return { return {
type: AnnotationActionTypes.ACTIVATE_OBJECT, type: AnnotationActionTypes.ACTIVATE_OBJECT,
payload: { payload: {
@ -657,8 +630,7 @@ export function switchPlay(playing: boolean): AnyAction {
}; };
} }
export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { instance: job } = state.annotation.job; const { instance: job } = state.annotation.job;
@ -694,23 +666,21 @@ ThunkAction {
payload: {}, payload: {},
}); });
await job.logger.log( await job.logger.log(LogType.changeFrame, {
LogType.changeFrame, {
from: frame, from: frame,
to: toFrame, to: toFrame,
}, });
);
const data = await job.frames.get(toFrame, fillBuffer, frameStep); const data = await job.frames.get(toFrame, fillBuffer, frameStep);
const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
const currentTime = new Date().getTime(); const currentTime = new Date().getTime();
let frameSpeed; let frameSpeed;
switch (state.settings.player.frameSpeed) { switch (state.settings.player.frameSpeed) {
case (FrameSpeed.Fast): { case FrameSpeed.Fast: {
frameSpeed = (FrameSpeed.Fast as number) / 2; frameSpeed = (FrameSpeed.Fast as number) / 2;
break; break;
} }
case (FrameSpeed.Fastest): { case FrameSpeed.Fastest: {
frameSpeed = (FrameSpeed.Fastest as number) / 3; frameSpeed = (FrameSpeed.Fastest as number) / 3;
break; break;
} }
@ -718,8 +688,10 @@ ThunkAction {
frameSpeed = state.settings.player.frameSpeed as number; frameSpeed = state.settings.player.frameSpeed as number;
} }
} }
const delay = Math.max(0, Math.round(1000 / frameSpeed) const delay = Math.max(
- currentTime + (state.annotation.player.frame.changeTime as number)); 0,
Math.round(1000 / frameSpeed) - currentTime + (state.annotation.player.frame.changeTime as number),
);
dispatch({ dispatch({
type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS,
@ -749,8 +721,7 @@ ThunkAction {
}; };
} }
export function undoActionAsync(sessionInstance: any, frame: number): export function undoActionAsync(sessionInstance: any, frame: number): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state = getStore().getState(); const state = getStore().getState();
@ -758,17 +729,20 @@ ThunkAction {
// TODO: use affected IDs as an optimization // TODO: use affected IDs as an optimization
const [undo] = state.annotation.annotations.history.undo.slice(-1); const [undo] = state.annotation.annotations.history.undo.slice(-1);
const undoLog = await sessionInstance.logger.log(LogType.undoAction, { const undoLog = await sessionInstance.logger.log(
LogType.undoAction,
{
name: undo[0], name: undo[0],
frame: undo[1], frame: undo[1],
count: 1, count: 1,
}, true); },
true,
);
dispatch(changeFrameAsync(undo[1])); dispatch(changeFrameAsync(undo[1]));
await sessionInstance.actions.undo(); await sessionInstance.actions.undo();
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
await undoLog.close(); await undoLog.close();
@ -792,8 +766,7 @@ ThunkAction {
}; };
} }
export function redoActionAsync(sessionInstance: any, frame: number): export function redoActionAsync(sessionInstance: any, frame: number): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state = getStore().getState(); const state = getStore().getState();
@ -801,16 +774,19 @@ ThunkAction {
// TODO: use affected IDs as an optimization // TODO: use affected IDs as an optimization
const [redo] = state.annotation.annotations.history.redo.slice(-1); const [redo] = state.annotation.annotations.history.redo.slice(-1);
const redoLog = await sessionInstance.logger.log(LogType.redoAction, { const redoLog = await sessionInstance.logger.log(
LogType.redoAction,
{
name: redo[0], name: redo[0],
frame: redo[1], frame: redo[1],
count: 1, count: 1,
}, true); },
true,
);
dispatch(changeFrameAsync(redo[1])); dispatch(changeFrameAsync(redo[1]));
await sessionInstance.actions.redo(); await sessionInstance.actions.redo();
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
await redoLog.close(); await redoLog.close();
@ -839,27 +815,20 @@ export function rotateCurrentFrame(rotation: Rotation): AnyAction {
const { const {
annotation: { annotation: {
player: { player: {
frame: { frame: { number: frameNumber },
number: frameNumber,
},
frameAngles, frameAngles,
}, },
job: { job: {
instance: job, instance: job,
instance: { instance: { startFrame },
startFrame,
},
}, },
}, },
settings: { settings: {
player: { player: { rotateAll },
rotateAll,
},
}, },
} = state; } = state;
const frameAngle = (frameAngles[frameNumber - startFrame] const frameAngle = (frameAngles[frameNumber - startFrame] + (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360;
+ (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360;
job.logger.log(LogType.rotateImage, { angle: frameAngle }); job.logger.log(LogType.rotateImage, { angle: frameAngle });
@ -918,12 +887,7 @@ export function closeJob(): ThunkAction {
}; };
} }
export function getJobAsync( export function getJobAsync(tid: number, jid: number, initialFrame: number, initialFilters: string[]): ThunkAction {
tid: number,
jid: number,
initialFrame: number,
initialFilters: string[],
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
@ -938,10 +902,12 @@ export function getJobAsync(
}); });
const loadJobEvent = await logger.log( const loadJobEvent = await logger.log(
LogType.loadJob, { LogType.loadJob,
{
task_id: tid, task_id: tid,
job_id: jid, job_id: jid,
}, true, },
true,
); );
// Check state if the task is already there // Check state if the task is already there
@ -955,8 +921,7 @@ export function getJobAsync(
} }
// Finally get the job from the task // Finally get the job from the task
const job = task.jobs const job = task.jobs.filter((_job: any) => _job.id === jid)[0];
.filter((_job: any) => _job.id === jid)[0];
if (!job) { if (!job) {
throw new Error(`Task ${tid} doesn't contain the job ${jid}`); throw new Error(`Task ${tid} doesn't contain the job ${jid}`);
} }
@ -966,8 +931,7 @@ export function getJobAsync(
// call first getting of frame data before rendering interface // call first getting of frame data before rendering interface
// to load and decode first chunk // to load and decode first chunk
await frameData.data(); await frameData.data();
const states = await job.annotations const states = await job.annotations.get(frameNumber, showAllInterpolationTracks, filters);
.get(frameNumber, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
const colors = [...cvat.enums.colors]; const colors = [...cvat.enums.colors];
@ -999,8 +963,7 @@ export function getJobAsync(
}; };
} }
export function saveAnnotationsAsync(sessionInstance: any): export function saveAnnotationsAsync(sessionInstance: any): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1010,9 +973,7 @@ ThunkAction {
}); });
try { try {
const saveJobEvent = await sessionInstance.logger.log( const saveJobEvent = await sessionInstance.logger.log(LogType.saveJob, {}, true);
LogType.saveJob, {}, true,
);
await sessionInstance.annotations.save((status: string) => { await sessionInstance.annotations.save((status: string) => {
dispatch({ dispatch({
@ -1023,15 +984,11 @@ ThunkAction {
}); });
}); });
await saveJobEvent.close(); await saveJobEvent.close();
await sessionInstance.logger.log( await sessionInstance.logger.log(LogType.sendTaskInfo, await jobInfoGenerator(sessionInstance));
LogType.sendTaskInfo,
await jobInfoGenerator(sessionInstance),
);
dispatch(saveLogsAsync()); dispatch(saveLogsAsync());
const { frame } = receiveAnnotationsParameters(); const { frame } = receiveAnnotationsParameters();
const states = await sessionInstance const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.annotations.get(frame, showAllInterpolationTracks, filters);
dispatch({ dispatch({
type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS,
@ -1120,12 +1077,7 @@ export function splitTrack(enabled: boolean): AnyAction {
export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction { export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const { jobInstance, filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
jobInstance,
filters,
frame,
showAllInterpolationTracks,
} = receiveAnnotationsParameters();
try { try {
if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) { if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) {
@ -1133,8 +1085,7 @@ export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
dispatch(activateObject(null, null)); dispatch(activateObject(null, null));
} }
const promises = statesToUpdate const promises = statesToUpdate.map((objectState: any): Promise<any> => objectState.save());
.map((objectState: any): Promise<any> => objectState.save());
const states = await Promise.all(promises); const states = await Promise.all(promises);
const history = await jobInstance.actions.get(); const history = await jobInstance.actions.get();
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
@ -1149,8 +1100,7 @@ export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
}, },
}); });
} catch (error) { } catch (error) {
const states = await jobInstance.annotations const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
dispatch({ dispatch({
type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED, type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED,
payload: { payload: {
@ -1162,14 +1112,12 @@ export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
}; };
} }
export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
await sessionInstance.annotations.put(statesToCreate); await sessionInstance.annotations.put(statesToCreate);
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -1190,14 +1138,12 @@ ThunkAction {
}; };
} }
export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
await sessionInstance.annotations.merge(statesToMerge); await sessionInstance.annotations.merge(statesToMerge);
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -1225,11 +1171,7 @@ export function resetAnnotationsGroup(): AnyAction {
}; };
} }
export function groupAnnotationsAsync( export function groupAnnotationsAsync(sessionInstance: any, frame: number, statesToGroup: any[]): ThunkAction {
sessionInstance: any,
frame: number,
statesToGroup: any[],
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1242,8 +1184,7 @@ export function groupAnnotationsAsync(
}); });
await sessionInstance.annotations.group(statesToGroup, reset); await sessionInstance.annotations.group(statesToGroup, reset);
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -1264,14 +1205,12 @@ export function groupAnnotationsAsync(
}; };
} }
export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): ThunkAction {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
try { try {
await sessionInstance.annotations.split(stateToSplit, frame); await sessionInstance.annotations.split(stateToSplit, frame);
const states = await sessionInstance.annotations const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters);
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -1292,14 +1231,12 @@ ThunkAction {
}; };
} }
export function changeGroupColorAsync( export function changeGroupColorAsync(group: number, color: string): ThunkAction {
group: number,
color: string,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const groupStates = state.annotation.annotations.states const groupStates = state.annotation.annotations.states.filter(
.filter((_state: any): boolean => _state.group.id === group); (_state: any): boolean => _state.group.id === group,
);
if (groupStates.length) { if (groupStates.length) {
groupStates[0].group.color = color; groupStates[0].group.color = color;
dispatch(updateAnnotationsAsync(groupStates)); dispatch(updateAnnotationsAsync(groupStates));
@ -1309,11 +1246,7 @@ export function changeGroupColorAsync(
}; };
} }
export function searchAnnotationsAsync( export function searchAnnotationsAsync(sessionInstance: any, frameFrom: number, frameTo: number): ThunkAction {
sessionInstance: any,
frameFrom: number,
frameTo: number,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters } = receiveAnnotationsParameters();
@ -1332,11 +1265,7 @@ export function searchAnnotationsAsync(
}; };
} }
export function searchEmptyFrameAsync( export function searchEmptyFrameAsync(sessionInstance: any, frameFrom: number, frameTo: number): ThunkAction {
sessionInstance: any,
frameFrom: number,
frameTo: number,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const frame = await sessionInstance.annotations.searchEmpty(frameFrom, frameTo); const frame = await sessionInstance.annotations.searchEmpty(frameFrom, frameTo);
@ -1357,20 +1286,12 @@ export function searchEmptyFrameAsync(
export function pasteShapeAsync(): ThunkAction { export function pasteShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
canvas: { canvas: { instance: canvasInstance },
instance: canvasInstance, job: { instance: jobInstance },
},
job: {
instance: jobInstance,
},
player: { player: {
frame: { frame: { number: frameNumber },
number: frameNumber,
},
},
drawing: {
activeInitialState: initialState,
}, },
drawing: { activeInitialState: initialState },
} = getStore().getState().annotation; } = getStore().getState().annotation;
if (initialState) { if (initialState) {
@ -1432,21 +1353,13 @@ export function setAIToolsRef(ref: MutableRefObject<any>): AnyAction {
}; };
} }
export function repeatDrawShapeAsync(): ThunkAction { export function repeatDrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
canvas: { canvas: { instance: canvasInstance },
instance: canvasInstance, job: { labels, instance: jobInstance },
},
job: {
labels,
instance: jobInstance,
},
player: { player: {
frame: { frame: { number: frameNumber },
number: frameNumber,
},
}, },
drawing: { drawing: {
activeInteractor, activeInteractor,
@ -1520,18 +1433,12 @@ export function repeatDrawShapeAsync(): ThunkAction {
export function redrawShapeAsync(): ThunkAction { export function redrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
annotations: { annotations: { activatedStateID, states },
activatedStateID, canvas: { instance: canvasInstance },
states,
},
canvas: {
instance: canvasInstance,
},
} = getStore().getState().annotation; } = getStore().getState().annotation;
if (activatedStateID !== null) { if (activatedStateID !== null) {
const [state] = states const [state] = states.filter((_state: any): boolean => _state.clientID === activatedStateID);
.filter((_state: any): boolean => _state.clientID === activatedStateID);
if (state && state.objectType !== ObjectType.TAG) { if (state && state.objectType !== ObjectType.TAG) {
let activeControl = ActiveControl.CURSOR; let activeControl = ActiveControl.CURSOR;
if (state.shapeType === ShapeType.RECTANGLE) { if (state.shapeType === ShapeType.RECTANGLE) {

@ -50,32 +50,22 @@ export const authActions = {
logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }), logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }),
changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD), changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD),
changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS), changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS),
changePasswordFailed: (error: any) => ( changePasswordFailed: (error: any) => createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error }),
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), requestPasswordReset: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET),
requestPasswordResetSuccess: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS), requestPasswordResetSuccess: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS),
requestPasswordResetFailed: (error: any) => ( requestPasswordResetFailed: (error: any) => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED, { error }),
createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED, { error })
),
resetPassword: () => createAction(AuthActionTypes.RESET_PASSWORD), resetPassword: () => createAction(AuthActionTypes.RESET_PASSWORD),
resetPasswordSuccess: () => createAction(AuthActionTypes.RESET_PASSWORD_SUCCESS), resetPasswordSuccess: () => createAction(AuthActionTypes.RESET_PASSWORD_SUCCESS),
resetPasswordFailed: (error: any) => ( resetPasswordFailed: (error: any) => createAction(AuthActionTypes.RESET_PASSWORD_FAILED, { error }),
createAction(AuthActionTypes.RESET_PASSWORD_FAILED, { error })
),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS), loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean, allowResetPassword: boolean) => ( loadServerAuthActionsSuccess: (allowChangePassword: boolean, allowResetPassword: boolean) =>
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, {
allowChangePassword, allowChangePassword,
allowResetPassword, allowResetPassword,
}) }),
), loadServerAuthActionsFailed: (error: any) => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error }),
loadServerAuthActionsFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error })
),
}; };
export type AuthActions = ActionUnion<typeof authActions>; export type AuthActions = ActionUnion<typeof authActions>;
@ -88,14 +78,19 @@ export const registerAsync = (
password1: string, password1: string,
password2: string, password2: string,
confirmations: UserConfirmation[], confirmations: UserConfirmation[],
): ThunkAction => async ( ): ThunkAction => async (dispatch) => {
dispatch,
) => {
dispatch(authActions.register()); dispatch(authActions.register());
try { try {
const user = await cvat.server.register(username, firstName, lastName, email, password1, const user = await cvat.server.register(
password2, confirmations); username,
firstName,
lastName,
email,
password1,
password2,
confirmations,
);
dispatch(authActions.registerSuccess(user)); dispatch(authActions.registerSuccess(user));
} catch (error) { } catch (error) {
@ -142,8 +137,11 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => {
} }
}; };
export const changePasswordAsync = (oldPassword: string, export const changePasswordAsync = (
newPassword1: string, newPassword2: string): ThunkAction => async (dispatch) => { oldPassword: string,
newPassword1: string,
newPassword2: string,
): ThunkAction => async (dispatch) => {
dispatch(authActions.changePassword()); dispatch(authActions.changePassword());
try { try {
@ -189,14 +187,9 @@ export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'), isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'),
isReachable(`${cvat.config.backendAPI}/auth/password/reset`, 'OPTIONS'), isReachable(`${cvat.config.backendAPI}/auth/password/reset`, 'OPTIONS'),
]; ];
const [ const [allowChangePassword, allowResetPassword] = await Promise.all(promises);
allowChangePassword,
allowResetPassword] = await Promise.all(promises);
dispatch(authActions.loadServerAuthActionsSuccess( dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword, allowResetPassword));
allowChangePassword,
allowResetPassword,
));
} catch (error) { } catch (error) {
dispatch(authActions.loadServerAuthActionsFailed(error)); dispatch(authActions.loadServerAuthActionsFailed(error));
} }

@ -2,12 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { import { ActionUnion, createAction, ThunkAction, ThunkDispatch } from 'utils/redux';
ActionUnion,
createAction,
ThunkAction,
ThunkDispatch,
} from 'utils/redux';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { computeZRange } from './annotation-actions'; import { computeZRange } from './annotation-actions';
@ -28,7 +23,8 @@ export const boundariesActions = {
minZ: number, minZ: number,
maxZ: number, maxZ: number,
colors: string[], colors: string[],
) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, { ) =>
createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
job, job,
states, states,
frameNumber, frameNumber,
@ -51,33 +47,16 @@ export function resetAfterErrorAsync(): ThunkAction {
const { showAllInterpolationTracks } = state.settings.workspace; const { showAllInterpolationTracks } = state.settings.workspace;
const frameNumber = Math.max(Math.min(job.stopFrame, currentFrame), job.startFrame); const frameNumber = Math.max(Math.min(job.stopFrame, currentFrame), job.startFrame);
const states = await job.annotations const states = await job.annotations.get(frameNumber, showAllInterpolationTracks, []);
.get(frameNumber, showAllInterpolationTracks, []);
const frameData = await job.frames.get(frameNumber); const frameData = await job.frames.get(frameNumber);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
const colors = [...cvat.enums.colors]; const colors = [...cvat.enums.colors];
await job.logger.log(LogType.restoreJob); await job.logger.log(LogType.restoreJob);
dispatch(boundariesActions.resetAfterError( dispatch(boundariesActions.resetAfterError(job, states, frameNumber, frameData, minZ, maxZ, colors));
job,
states,
frameNumber,
frameData,
minZ,
maxZ,
colors,
));
} else { } else {
dispatch(boundariesActions.resetAfterError( dispatch(boundariesActions.resetAfterError(null, [], 0, null, 0, 0, []));
null,
[],
0,
null,
0,
0,
[],
));
} }
} catch (error) { } catch (error) {
dispatch(boundariesActions.throwResetError()); dispatch(boundariesActions.throwResetError());
@ -85,4 +64,4 @@ export function resetAfterErrorAsync(): ThunkAction {
}; };
} }
export type boundariesActions = ActionUnion<typeof boundariesActions>; export type BoundariesActions = ActionUnion<typeof boundariesActions>;

@ -15,14 +15,11 @@ export enum FormatsActionTypes {
const formatsActions = { const formatsActions = {
getFormats: () => createAction(FormatsActionTypes.GET_FORMATS), getFormats: () => createAction(FormatsActionTypes.GET_FORMATS),
getFormatsSuccess: (annotationFormats: any) => ( getFormatsSuccess: (annotationFormats: any) =>
createAction(FormatsActionTypes.GET_FORMATS_SUCCESS, { createAction(FormatsActionTypes.GET_FORMATS_SUCCESS, {
annotationFormats, annotationFormats,
}) }),
), getFormatsFailed: (error: any) => createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error }),
getFormatsFailed: (error: any) => (
createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error })
),
}; };
export type FormatsActions = ActionUnion<typeof formatsActions>; export type FormatsActions = ActionUnion<typeof formatsActions>;
@ -35,9 +32,7 @@ export function getFormatsAsync(): ThunkAction {
try { try {
annotationFormats = await cvat.server.formats(); annotationFormats = await cvat.server.formats();
dispatch( dispatch(formatsActions.getFormatsSuccess(annotationFormats));
formatsActions.getFormatsSuccess(annotationFormats),
);
} catch (error) { } catch (error) {
dispatch(formatsActions.getFormatsFailed(error)); dispatch(formatsActions.getFormatsFailed(error));
} }

@ -22,52 +22,44 @@ export enum ModelsActionTypes {
export const modelsActions = { export const modelsActions = {
getModels: () => createAction(ModelsActionTypes.GET_MODELS), getModels: () => createAction(ModelsActionTypes.GET_MODELS),
getModelsSuccess: (models: Model[]) => createAction( getModelsSuccess: (models: Model[]) =>
ModelsActionTypes.GET_MODELS_SUCCESS, { createAction(ModelsActionTypes.GET_MODELS_SUCCESS, {
models, models,
}, }),
), getModelsFailed: (error: any) =>
getModelsFailed: (error: any) => createAction( createAction(ModelsActionTypes.GET_MODELS_FAILED, {
ModelsActionTypes.GET_MODELS_FAILED, {
error, error,
}, }),
),
fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }), fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }),
getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction( getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) =>
ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { createAction(ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, {
taskID, taskID,
activeInference, activeInference,
}, }),
), getInferenceStatusFailed: (taskID: number, error: any) =>
getInferenceStatusFailed: (taskID: number, error: any) => createAction( createAction(ModelsActionTypes.GET_INFERENCE_STATUS_FAILED, {
ModelsActionTypes.GET_INFERENCE_STATUS_FAILED, {
taskID, taskID,
error, error,
}, }),
), startInferenceFailed: (taskID: number, error: any) =>
startInferenceFailed: (taskID: number, error: any) => createAction( createAction(ModelsActionTypes.START_INFERENCE_FAILED, {
ModelsActionTypes.START_INFERENCE_FAILED, {
taskID, taskID,
error, error,
}, }),
), cancelInferenceSuccess: (taskID: number) =>
cancelInferenceSuccess: (taskID: number) => createAction( createAction(ModelsActionTypes.CANCEL_INFERENCE_SUCCESS, {
ModelsActionTypes.CANCEL_INFERENCE_SUCCESS, {
taskID, taskID,
}, }),
), cancelInferenceFailed: (taskID: number, error: any) =>
cancelInferenceFailed: (taskID: number, error: any) => createAction( createAction(ModelsActionTypes.CANCEL_INFERENCE_FAILED, {
ModelsActionTypes.CANCEL_INFERENCE_FAILED, {
taskID, taskID,
error, error,
}, }),
),
closeRunModelDialog: () => createAction(ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG), closeRunModelDialog: () => createAction(ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG),
showRunModelDialog: (taskInstance: any) => createAction( showRunModelDialog: (taskInstance: any) =>
ModelsActionTypes.SHOW_RUN_MODEL_DIALOG, { createAction(ModelsActionTypes.SHOW_RUN_MODEL_DIALOG, {
taskInstance, taskInstance,
}, }),
),
}; };
export type ModelsActions = ActionUnion<typeof modelsActions>; export type ModelsActions = ActionUnion<typeof modelsActions>;
@ -87,42 +79,44 @@ export function getModelsAsync(): ThunkAction {
}; };
} }
interface InferenceMeta { interface InferenceMeta {
taskID: number; taskID: number;
requestID: string; requestID: string;
} }
function listen( function listen(inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) => void): void {
inferenceMeta: InferenceMeta,
dispatch: (action: ModelsActions) => void,
): void {
const { taskID, requestID } = inferenceMeta; const { taskID, requestID } = inferenceMeta;
core.lambda.listen(requestID, (status: RQStatus, progress: number, message: string) => { core.lambda
.listen(requestID, (status: RQStatus, progress: number, message: string) => {
if (status === RQStatus.failed || status === RQStatus.unknown) { if (status === RQStatus.failed || status === RQStatus.unknown) {
dispatch(modelsActions.getInferenceStatusFailed( dispatch(
modelsActions.getInferenceStatusFailed(
taskID, taskID,
new Error( new Error(`Inference status for the task ${taskID} is ${status}. ${message}`),
`Inference status for the task ${taskID} is ${status}. ${message}`,
), ),
)); );
return; return;
} }
dispatch(modelsActions.getInferenceStatusSuccess(taskID, { dispatch(
modelsActions.getInferenceStatusSuccess(taskID, {
status, status,
progress, progress,
error: message, error: message,
id: requestID, id: requestID,
})); }),
}).catch((error: Error) => { );
dispatch(modelsActions.getInferenceStatusFailed(taskID, { })
.catch((error: Error) => {
dispatch(
modelsActions.getInferenceStatusFailed(taskID, {
status: 'unknown', status: 'unknown',
progress: 0, progress: 0,
error: error.toString(), error: error.toString(),
id: requestID, id: requestID,
})); }),
);
}); });
} }
@ -148,11 +142,7 @@ export function getInferenceStatusAsync(): ThunkAction {
}; };
} }
export function startInferenceAsync( export function startInferenceAsync(taskInstance: any, model: Model, body: object): ThunkAction {
taskInstance: any,
model: Model,
body: object,
): ThunkAction {
return async (dispatch): Promise<void> => { return async (dispatch): Promise<void> => {
try { try {
const requestID: string = await core.lambda.run(taskInstance, model, body); const requestID: string = await core.lambda.run(taskInstance, model, body);
@ -160,10 +150,13 @@ export function startInferenceAsync(
dispatch(action); dispatch(action);
}; };
listen({ listen(
{
taskID: taskInstance.id, taskID: taskInstance.id,
requestID, requestID,
}, dispatchCallback); },
dispatchCallback,
);
} catch (error) { } catch (error) {
dispatch(modelsActions.startInferenceFailed(taskInstance.id, error)); dispatch(modelsActions.startInferenceFailed(taskInstance.id, error));
} }

@ -16,12 +16,8 @@ export enum PluginsActionTypes {
const pluginActions = { const pluginActions = {
checkPlugins: () => createAction(PluginsActionTypes.GET_PLUGINS), checkPlugins: () => createAction(PluginsActionTypes.GET_PLUGINS),
checkPluginsSuccess: (list: PluginsList) => createAction( checkPluginsSuccess: (list: PluginsList) => createAction(PluginsActionTypes.GET_PLUGINS_SUCCESS, { list }),
PluginsActionTypes.GET_PLUGINS_SUCCESS, { list }, checkPluginsFailed: (error: any) => createAction(PluginsActionTypes.GET_PLUGINS_FAILED, { error }),
),
checkPluginsFailed: (error: any) => createAction(
PluginsActionTypes.GET_PLUGINS_FAILED, { error },
),
}; };
export type PluginActions = ActionUnion<typeof pluginActions>; export type PluginActions = ActionUnion<typeof pluginActions>;

@ -3,10 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { AnyAction } from 'redux'; import { AnyAction } from 'redux';
import { import { GridColor, ColorBy } from 'reducers/interfaces';
GridColor,
ColorBy,
} from 'reducers/interfaces';
export enum SettingsActionTypes { export enum SettingsActionTypes {
SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL', SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL',

@ -17,24 +17,17 @@ export enum ShareActionTypes {
const shareActions = { const shareActions = {
loadShareData: () => createAction(ShareActionTypes.LOAD_SHARE_DATA), loadShareData: () => createAction(ShareActionTypes.LOAD_SHARE_DATA),
loadShareDataSuccess: (values: ShareFileInfo[], directory: string) => ( loadShareDataSuccess: (values: ShareFileInfo[], directory: string) =>
createAction(ShareActionTypes.LOAD_SHARE_DATA_SUCCESS, { createAction(ShareActionTypes.LOAD_SHARE_DATA_SUCCESS, {
values, values,
directory, directory,
}) }),
), loadShareDataFailed: (error: any) => createAction(ShareActionTypes.LOAD_SHARE_DATA_FAILED, { error }),
loadShareDataFailed: (error: any) => (
createAction(ShareActionTypes.LOAD_SHARE_DATA_FAILED, { error })
),
}; };
export type ShareActions = ActionUnion<typeof shareActions>; export type ShareActions = ActionUnion<typeof shareActions>;
export function loadShareDataAsync( export function loadShareDataAsync(directory: string, success: () => void, failure: () => void): ThunkAction {
directory: string,
success: () => void,
failure: () => void,
): ThunkAction {
return async (dispatch): Promise<void> => { return async (dispatch): Promise<void> => {
try { try {
dispatch(shareActions.loadShareData()); dispatch(shareActions.loadShareData());

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

Loading…
Cancel
Save