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

@ -1,5 +1,5 @@
exclude_paths: exclude_paths:
- '**/3rdparty/**' - '**/3rdparty/**'
- '**/engine/js/cvat-core.min.js' - '**/engine/js/cvat-core.min.js'
- '**/engine/js/unzip_imgs.js' - '**/engine/js/unzip_imgs.js'
- CHANGELOG.md - CHANGELOG.md

@ -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,16 +1,16 @@
exports.settings = {bullet: '*', paddedTable: false} exports.settings = { bullet: '*', paddedTable: false };
exports.plugins = [ exports.plugins = [
'remark-preset-lint-recommended', 'remark-preset-lint-recommended',
'remark-preset-lint-consistent', 'remark-preset-lint-consistent',
['remark-preset-lint-markdown-style-guide', 'mixed'], ['remark-preset-lint-markdown-style-guide', 'mixed'],
['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],
] ];

@ -1,20 +1,22 @@
{ {
"extends": "stylelint-config-standard", "extends": "stylelint-config-standard",
"rules": { "rules": {
"indentation": 4, "indentation": 4,
"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": [
"ignoreAtRules": ["extend"] true,
}], {
"selector-type-no-unknown": [true, { "ignoreAtRules": ["extend"]
"ignoreTypes": ["first-child"] }
}] ],
}, "selector-type-no-unknown": [
"ignoreFiles": [ true,
"**/*.js", {
"**/*.ts", "ignoreTypes": ["first-child"]
"**/*.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,11 +9,11 @@ cache:
- ~/.cache - ~/.cache
addons: addons:
firefox: "latest" firefox: 'latest'
chrome: stable chrome: stable
apt: apt:
packages: packages:
- libgconf-2-4 - libgconf-2-4
services: services:
- docker - docker
@ -42,7 +42,7 @@ script:
- cd ./tests && npm install && cd .. - cd ./tests && npm install && cd ..
- if [[ $TRAVIS_EVENT_TYPE == "cron" && $TRAVIS_BRANCH == "develop" ]]; - if [[ $TRAVIS_EVENT_TYPE == "cron" && $TRAVIS_BRANCH == "develop" ]];
then then
cd ./tests && npm run cypress:run:firefox && exit $?; cd ./tests && npm run cypress:run:firefox && exit $?;
fi; fi;
- npm install && npm run coverage - npm install && npm run coverage
- docker-compose up -d --build - docker-compose up -d --build

File diff suppressed because it is too large Load Diff

@ -9,85 +9,96 @@ they should reciprocate that respect in addressing your issue or assessing
patches and features. patches and features.
## Development environment ## Development environment
- Install necessary dependencies:
Ubuntu 18.04 - Install necessary dependencies:
```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
```
```sh
# 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 -
sudo apt-get install -y nodejs
```
MacOS 10.15 Ubuntu 18.04
```sh
brew install git python pyenv redis curl openssl node
```
- Install FFmpeg libraries (libav*) version 4.0 or higher. ```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
- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) ```
for development
- Install CVAT on your local host:
```sh
git clone https://github.com/opencv/cvat
cd cvat && mkdir logs keys
python3 -m venv .env
. .env/bin/activate
pip install -U pip wheel setuptools
pip install -r cvat/requirements/development.txt
python manage.py migrate
python manage.py collectstatic
```
> Note for Mac users
>
> If you have any problems with installing dependencies from
> ```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
- Create a super user for CVAT:
```sh
$ python manage.py createsuperuser
Username (leave blank to use 'django'): ***
Email address: ***
Password: ***
Password (again): ***
```
- Install npm packages for UI and start UI debug server (run the following command from CVAT root directory): ```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)
npm install && \ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
cd cvat-core && npm install && \ sudo apt-get install -y nodejs
cd ../cvat-ui && npm install && npm start ```
```
> Note for Mac users MacOS 10.15
>
> If you faced with error ```sh
> brew install git python pyenv redis curl openssl node
> ```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) - Install FFmpeg libraries (libav\*) version 4.0 or higher.
- Open new terminal (Ctrl + Shift + T), run Visual Studio Code from the virtual environment - Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions)
```sh for development
cd .. && source .env/bin/activate && code
``` - Install CVAT on your local host:
```sh
git clone https://github.com/opencv/cvat
cd cvat && mkdir logs keys
python3 -m venv .env
. .env/bin/activate
pip install -U pip wheel setuptools
pip install -r cvat/requirements/development.txt
python manage.py migrate
python manage.py collectstatic
```
> Note for Mac users
>
> If you have any problems with installing dependencies from
> `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
- Create a super user for CVAT:
- Install following VS Code extensions: ```sh
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) $ python manage.py createsuperuser
- [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) Username (leave blank to use 'django'): ***
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) Email address: ***
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) Password: ***
- [vscode-remark-lint](https://marketplace.visualstudio.com/items?itemName=drewbourne.vscode-remark-lint) Password (again): ***
- [licenser](https://marketplace.visualstudio.com/items?itemName=ymotongpoo.licenser) ```
- [Trailing Spaces](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces)
- Reload Visual Studio Code from virtual environment - Install npm packages for UI and start UI debug server (run the following command from CVAT root directory):
- Select `server: debug` configuration and start it (F5) to run REST server and its workers ```sh
npm install && \
cd cvat-core && npm install && \
cd ../cvat-ui && npm install && npm start
```
> Note for Mac users
>
> If you faced with error
>
> `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)
- Open new terminal (Ctrl + Shift + T), run Visual Studio Code from the virtual environment
```sh
cd .. && source .env/bin/activate && code
```
- Install following VS Code extensions:
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)
- [vscode-remark-lint](https://marketplace.visualstudio.com/items?itemName=drewbourne.vscode-remark-lint)
- [licenser](https://marketplace.visualstudio.com/items?itemName=ymotongpoo.licenser)
- [Trailing Spaces](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces)
- Reload Visual Studio Code from virtual environment
- Select `server: debug` configuration and start it (F5) to run REST server and its workers
You have done! Now it is possible to insert breakpoints and debug server and client of the tool. You have done! Now it is possible to insert breakpoints and debug server and client of the tool.
@ -95,30 +106,32 @@ You have done! Now it is possible to insert breakpoints and debug server and cli
You develop CVAT under WSL (Windows subsystem for Linux) following next steps. You develop CVAT under WSL (Windows subsystem for Linux) following next steps.
- Install WSL using [this guide](https://docs.microsoft.com/ru-ru/windows/wsl/install-win10). - Install WSL using [this guide](https://docs.microsoft.com/ru-ru/windows/wsl/install-win10).
- 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
wsl -d Ubuntu-18.04 ```powershell
``` 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)
for more details. for more details.
- Create `cvat` project inside nuclio dashboard where you will deploy new - Create `cvat` project inside nuclio dashboard where you will deploy new
serverless functions and deploy a couple of DL models. serverless functions and deploy a couple of DL models.
```bash ```bash
nuctl create project cvat nuctl create project cvat
@ -181,7 +194,7 @@ nuctl deploy --project-name cvat \
</details> </details>
- Display a list of running serverless functions using `nuctl` command or see them - Display a list of running serverless functions using `nuctl` command or see them
in nuclio dashboard: in nuclio dashboard:
```bash ```bash
nuctl get function nuctl get function
@ -198,7 +211,7 @@ nuctl get function
</details> </details>
- Test your deployed DL model as a serverless function. The command below - Test your deployed DL model as a serverless function. The command below
should work on Linux and Mac OS. should work on Linux and Mac OS.
```bash ```bash
image=$(curl https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png --output - | base64 | tr -d '\n') image=$(curl https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png --output - | base64 | tr -d '\n')
@ -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).
@ -258,12 +272,12 @@ little exception - we prefer 4 spaces for indentation of nested blocks and state
The project uses [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model). The project uses [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model).
Thus it has a couple of branches. Some of them are described below: Thus it has a couple of branches. Some of them are described below:
- `origin/master` to be the main branch where the source code of - `origin/master` to be the main branch where the source code of
HEAD always reflects a production-ready state HEAD always reflects a production-ready state
- `origin/develop` to be the main branch where the source code of - `origin/develop` to be the main branch where the source code of
HEAD always reflects a state with the latest delivered development HEAD always reflects a state with the latest delivered development
changes for the next release. Some would call this the “integration branch”. changes for the next release. Some would call this the “integration branch”.
## Using the issue tracker ## Using the issue tracker
@ -271,13 +285,14 @@ The issue tracker is the preferred channel for [bug reports](#bugs),
[features requests](#features) and [submitting pull [features requests](#features) and [submitting pull
requests](#pull-requests), but please respect the following restrictions: requests](#pull-requests), but please respect the following restrictions:
- Please **do not** use the issue tracker for personal support requests (use - Please **do not** use the issue tracker for personal support requests (use
[Stack Overflow](http://stackoverflow.com)). [Stack Overflow](http://stackoverflow.com)).
- Please **do not** derail or troll issues. Keep the discussion on topic and - Please **do not** derail or troll issues. Keep the discussion on topic and
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.
@ -286,10 +301,10 @@ Good bug reports are extremely helpful - thank you!
Guidelines for bug reports: Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been 1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported. reported.
1. **Check if the issue has been fixed** &mdash; try to reproduce it using the 1. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `develop` branch in the repository. latest `develop` branch in the repository.
1. **Isolate the problem** &mdash; ideally create a reduced test case. 1. **Isolate the problem** &mdash; ideally create a reduced test case.
@ -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
@ -368,10 +385,10 @@ project:
``` ```
1. Commit your changes in logical chunks. Please adhere to these [git commit 1. Commit your changes in logical chunks. Please adhere to these [git commit
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
or your code is unlikely be merged into the main project. Use Git's or your code is unlikely be merged into the main project. Use Git's
[interactive rebase](https://docs.github.com/en/github/using-git/about-git-rebase) [interactive rebase](https://docs.github.com/en/github/using-git/about-git-rebase)
feature to tidy up your commits before making them public. feature to tidy up your commits before making them public.
1. Locally merge (or rebase) the upstream development branch into your topic branch: 1. Locally merge (or rebase) the upstream development branch into your topic branch:

@ -46,18 +46,18 @@ and Dump annotation buttons.
framework allows additional dataset transformations 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 |
| [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | | [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X |
| Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | | Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X |
| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | | [YOLO](https://pjreddie.com/darknet/yolo/) | X | X |
| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | | [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X |
| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tf_records) | X | X | | [TFrecord](https://www.tensorflow.org/tutorials/load_data/tf_records) | X | X |
| [MOT](https://motchallenge.net/) | X | X | | [MOT](https://motchallenge.net/) | X | X |
| [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X | | [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X |
## Deep learning models for automatic labeling ## Deep learning models for automatic labeling
@ -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,11 +21,12 @@ 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",
"display": "Today", "display": "Today",
"section": 0 "section": 0
} }
## Time picker quick ranges ## Time picker quick ranges

@ -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}
@ -52,7 +67,7 @@ services:
context: ./components/analytics/logstash context: ./components/analytics/logstash
args: args:
ELK_VERSION: 6.4.0 ELK_VERSION: 6.4.0
http_proxy: ${http_proxy} http_proxy: ${http_proxy}
https_proxy: ${https_proxy} https_proxy: ${https_proxy}
depends_on: ['cvat_elasticsearch'] depends_on: ['cvat_elasticsearch']
restart: always restart: always

@ -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,31 +183,32 @@ 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();
console.log('Version ', window.canvas.CanvasVersion); console.log('Version ', window.canvas.CanvasVersion);
console.log('Current mode is ', window.canvas.mode()); console.log('Current mode is ', window.canvas.mode());
// Put canvas to a html container // Put canvas to a html container
htmlContainer.appendChild(canvas.html()); htmlContainer.appendChild(canvas.html());
canvas.fitCanvas(); canvas.fitCanvas();
// Next you can use its API methods. For example: // Next you can use its API methods. For example:
canvas.rotate(270); canvas.rotate(270);
canvas.draw({ canvas.draw({
enabled: true, enabled: true,
shapeType: 'rectangle', shapeType: 'rectangle',
crosshair: true, crosshair: true,
rectDrawingMethod: window.Canvas.RectDrawingMethod.CLASSIC, rectDrawingMethod: window.Canvas.RectDrawingMethod.CLASSIC,
}); });
``` ```
## 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<
click: (event: MouseEvent) => void; number,
dblclick: (event: MouseEvent) => void; Record<
}>>; number,
{
click: (event: MouseEvent) => void;
dblclick: (event: MouseEvent) => void;
}
>
>;
public constructor(frameContent: SVGSVGElement) { public constructor(frameContent: SVGSVGElement) {
this.frameContent = frameContent; this.frameContent = frameContent;
@ -47,12 +53,11 @@ 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(); });
});
group.remove(); group.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,125 +109,125 @@ 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'); circle.classList.add('cvat_canvas_autoborder_point');
circle.classList.add('cvat_canvas_autoborder_point'); circle.setAttribute('fill', shape.color);
circle.setAttribute('fill', shape.color); circle.setAttribute('stroke', 'black');
circle.setAttribute('stroke', 'black'); circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`);
circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`); circle.setAttribute('cx', x);
circle.setAttribute('cx', x); circle.setAttribute('cy', y);
circle.setAttribute('cy', y); circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
const click = (event: MouseEvent): void => {
const click = (event: MouseEvent): void => { event.stopPropagation();
event.stopPropagation();
// another shape was clicked
// another shape was clicked if (this.auxiliaryGroupID !== null && this.auxiliaryGroupID !== groupID) {
if (this.auxiliaryGroupID !== null this.resetAuxiliaryShape();
&& this.auxiliaryGroupID !== groupID
) {
this.resetAuxiliaryShape();
}
this.auxiliaryGroupID = groupID;
// up clicked group for convenience
this.frameContent.appendChild(group);
if (this.auxiliaryClicks[1] === pointID) {
// the second point was clicked twice
this.addPointToCurrentShape(+x, +y);
this.resetAuxiliaryShape();
return;
}
// the first point can not be clicked twice
// just ignore such a click if it is
if (this.auxiliaryClicks[0] !== pointID) {
this.auxiliaryClicks.push(pointID);
} else {
return;
}
// it is the first click
if (this.auxiliaryClicks.length === 1) {
const handler = this.currentShape.remember('_paintHandler');
// draw and remove initial point just to initialize data structures
if (!handler || !handler.startPoint) {
(this.currentShape as any).draw('point', event);
(this.currentShape as any).draw('undo');
} }
this.addPointToCurrentShape(+x, +y); this.auxiliaryGroupID = groupID;
// is is the second click // up clicked group for convenience
} else if (this.auxiliaryClicks.length === 2) { this.frameContent.appendChild(group);
circle.classList.add('cvat_canvas_autoborder_point_direction');
// it is the third click
} else {
// sign defines bypass direction
const landmarks = this.auxiliaryClicks;
const sign = Math.sign(landmarks[2] - landmarks[0])
* Math.sign(landmarks[1] - landmarks[0])
* Math.sign(landmarks[2] - landmarks[1]);
// go via a polygon and get vertexes
// the first vertex has been already drawn
const way = [];
for (let i = landmarks[0] + sign; ; i += sign) {
if (i < 0) {
i = points.length - 1;
} else if (i === points.length) {
i = 0;
}
way.push(points[i]); if (this.auxiliaryClicks[1] === pointID) {
// the second point was clicked twice
if (i === this.auxiliaryClicks[this.auxiliaryClicks.length - 1]) { this.addPointToCurrentShape(+x, +y);
// put the last element twice this.resetAuxiliaryShape();
// specific of svg.draw.js return;
// way.push(points[i]);
break;
}
} }
// remove the latest cursor position from drawing array // the first point can not be clicked twice
for (const wayPoint of way) { // just ignore such a click if it is
const [_x, _y] = wayPoint.split(',') if (this.auxiliaryClicks[0] !== pointID) {
.map((coordinate: string): number => +coordinate); this.auxiliaryClicks.push(pointID);
this.addPointToCurrentShape(_x, _y); } else {
return;
} }
this.resetAuxiliaryShape(); // it is the first click
} if (this.auxiliaryClicks.length === 1) {
}; const handler = this.currentShape.remember('_paintHandler');
// draw and remove initial point just to initialize data structures
if (!handler || !handler.startPoint) {
(this.currentShape as any).draw('point', event);
(this.currentShape as any).draw('undo');
}
this.addPointToCurrentShape(+x, +y);
// is is the second click
} else if (this.auxiliaryClicks.length === 2) {
circle.classList.add('cvat_canvas_autoborder_point_direction');
// it is the third click
} else {
// sign defines bypass direction
const landmarks = this.auxiliaryClicks;
const sign =
Math.sign(landmarks[2] - landmarks[0]) *
Math.sign(landmarks[1] - landmarks[0]) *
Math.sign(landmarks[2] - landmarks[1]);
// go via a polygon and get vertexes
// the first vertex has been already drawn
const way = [];
for (let i = landmarks[0] + sign; ; i += sign) {
if (i < 0) {
i = points.length - 1;
} else if (i === points.length) {
i = 0;
}
way.push(points[i]);
if (i === this.auxiliaryClicks[this.auxiliaryClicks.length - 1]) {
// put the last element twice
// specific of svg.draw.js
// way.push(points[i]);
break;
}
}
const dblclick = (event: MouseEvent): void => { // remove the latest cursor position from drawing array
event.stopPropagation(); for (const wayPoint of way) {
}; const [_x, _y] = wayPoint
.split(',')
.map((coordinate: string): number => +coordinate);
this.addPointToCurrentShape(_x, _y);
}
this.listeners[groupID][pointID] = { this.resetAuxiliaryShape();
click, }
dblclick, };
};
circle.addEventListener('mousedown', this.listeners[groupID][pointID].click); const dblclick = (event: MouseEvent): void => {
circle.addEventListener('dblclick', this.listeners[groupID][pointID].click); event.stopPropagation();
return circle; };
});
this.listeners[groupID][pointID] = {
click,
dblclick,
};
circle.addEventListener('mousedown', this.listeners[groupID][pointID].click);
circle.addEventListener('dblclick', this.listeners[groupID][pointID].click);
return circle;
},
);
group.append(...circles); group.append(...circles);
return group; return group;
}); },
);
this.frameContent.append(...this.groups); this.frameContent.append(...this.groups);
} }
@ -231,55 +237,54 @@ 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 color = shape.getAttribute('fill'); const transformedShapes = shapes
const clientID = shape.getAttribute('clientID'); .map((shape: HTMLElement): TransformedShape | null => {
const color = shape.getAttribute('fill');
if (color === null || clientID === null) return null; const clientID = shape.getAttribute('clientID');
if (+clientID === +currentClientID) {
return null; if (color === null || clientID === null) return null;
} if (+clientID === +currentClientID) {
let points = '';
if (shape.tagName === 'polyline' || shape.tagName === 'polygon') {
points = shape.getAttribute('points');
} else if (shape.tagName === 'rect') {
const x = +shape.getAttribute('x');
const y = +shape.getAttribute('y');
const width = +shape.getAttribute('width');
const height = +shape.getAttribute('height');
if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(x) || Number.isNaN(x)) {
return null; return null;
} }
points = `${x},${y} ${x + width},${y} ${x + width},${y + height} ${x},${y + height}`; let points = '';
} else if (shape.tagName === 'g') { if (shape.tagName === 'polyline' || shape.tagName === 'polygon') {
const polylineID = shape.dataset.polylineId; points = shape.getAttribute('points');
const polyline = this.frameContent.getElementById(polylineID); } else if (shape.tagName === 'rect') {
if (polyline && polyline.getAttribute('points')) { const x = +shape.getAttribute('x');
points = polyline.getAttribute('points'); const y = +shape.getAttribute('y');
} else { const width = +shape.getAttribute('width');
return null; const height = +shape.getAttribute('height');
if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(x) || Number.isNaN(x)) {
return null;
}
points = `${x},${y} ${x + width},${y} ${x + width},${y + height} ${x},${y + height}`;
} else if (shape.tagName === 'g') {
const polylineID = shape.dataset.polylineId;
const polyline = this.frameContent.getElementById(polylineID);
if (polyline && polyline.getAttribute('points')) {
points = polyline.getAttribute('points');
} else {
return null;
}
} }
}
return { return {
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,36 +366,35 @@ 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;
this.notify(UpdateReasons.IMAGE_CHANGED); this.notify(UpdateReasons.IMAGE_CHANGED);
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 => { })
throw exception; .catch((exception: any): void => {
}); 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
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale), .line(0, y, this.canvas.node.clientWidth, y)
}).addClass('cvat_canvas_crosshair'); .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
})
.addClass('cvat_canvas_crosshair');
this.y = this.canvas.line(x, 0, x, this.canvas.node.clientHeight).attr({ this.y = this.canvas
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale), .line(x, 0, x, this.canvas.node.clientHeight)
}).addClass('cvat_canvas_crosshair'); .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
})
.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 {
@ -324,7 +325,7 @@ function sortPointsClockwise(points: any[]): any[] {
let ang = Math.atan2(point.y - center.y, point.x - center.x); let ang = Math.atan2(point.y - center.y, point.x - center.x);
if (!startAng) { if (!startAng) {
startAng = ang; startAng = ang;
// ensure that all points are clockwise of the start point // ensure that all points are clockwise of the start point
} else if (ang < startAng) { } else if (ang < startAng) {
ang += Math.PI * 2; ang += Math.PI * 2;
} }
@ -347,18 +348,18 @@ 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);
// seperate into left and right point // seperate into left and right point
// we pick the first and third point because we know assume they will be on // we pick the first and third point because we know assume they will be on
// opposite corners // opposite corners
if (points[0].x < points[2].x) { if (points[0].x < points[2].x) {
[left,, right] = points; [left, , right] = points;
} else { } else {
[right,, left] = points; [right, , left] = points;
} }
// get other 2 points using the given height // get other 2 points using the given height
@ -408,7 +409,7 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
points.push({ x, y }); points.push({ x, y });
} }
const unsortedPlanePoints = points.slice(0, 3); const unsortedPlanePoints = points.slice(0, 3);
function rotate(array: any[], times: number): void{ function rotate(array: any[], times: number): void {
let t = times; let t = times;
while (t--) { while (t--) {
const temp = array.shift(); const temp = array.shift();
@ -460,28 +461,21 @@ 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) {
cuboidPoints = setupCuboidPoints(points); cuboidPoints = setupCuboidPoints(points);
// left // left
} else if (Math.abs(angle) > Math.PI / 2 + 0.1) { } else if (Math.abs(angle) > Math.PI / 2 + 0.1) {
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,37 +247,48 @@ 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
const bbox = (e.target as SVGRectElement).getBBox(); .on('drawstop', (e: Event): void => {
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const bbox = (e.target as SVGRectElement).getBBox();
const { shapeType, redraw: clientID } = this.drawData; const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
this.release(); const { shapeType, redraw: clientID } = this.drawData;
this.release();
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, {
shapeType, clientID,
points: [xtl, ytl, xbr, ybr], shapeType,
}, Date.now() - this.startTimestamp); points: [xtl, ytl, xbr, ybr],
} },
}).on('drawupdate', (): void => { Date.now() - this.startTimestamp,
this.shapeSizeElement.update(this.drawInstance); );
}).addClass('cvat_canvas_shape_drawing').attr({ }
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, })
}); .on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance);
})
.addClass('cvat_canvas_shape_drawing')
.attr({
'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, {
clientID, shapeType,
points: [xtl, ytl, xbr, ybr], clientID,
}, Date.now() - this.startTimestamp); points: [xtl, ytl, xbr, ybr],
},
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 } =
: this.getFinalPolyshapeCoordinates(targetPoints); shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(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
clientID, ) {
shapeType, this.onDrawDone(
points, {
}, Date.now() - this.startTimestamp); clientID,
} else if (shapeType === 'polyline' shapeType,
&& ((box.xbr - box.xtl) >= consts.SIZE_THRESHOLD points,
|| (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) },
&& points.length >= 2 * 2) { Date.now() - this.startTimestamp,
this.onDrawDone({ );
clientID, } else if (
shapeType, shapeType === 'polyline' &&
points, (box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) &&
}, Date.now() - this.startTimestamp); points.length >= 2 * 2
} else if (shapeType === 'points' ) {
&& (e.target as any).getAttribute('points') !== '0,0') { this.onDrawDone(
this.onDrawDone({ {
clientID, clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); },
Date.now() - this.startTimestamp,
);
} else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') {
this.onDrawDone(
{
clientID,
shapeType,
points,
},
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,18 +472,19 @@ 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, });
});
this.drawPolyshape(); this.drawPolyshape();
} }
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,25 +492,32 @@ 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
const bbox = (e.target as SVGRectElement).getBBox(); .on('drawstop', (e: Event): void => {
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const bbox = (e.target as SVGRectElement).getBBox();
const { shapeType } = this.drawData; const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
this.release(); const { shapeType } = this.drawData;
this.release();
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, {
points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]), shapeType,
}, Date.now() - this.startTimestamp); points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]),
} },
}).on('drawupdate', (): void => { Date.now() - this.startTimestamp,
this.shapeSizeElement.update(this.drawInstance); );
}).addClass('cvat_canvas_shape_drawing').attr({ }
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, })
}); .on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance);
})
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
} }
private pastePolyshape(): void { private pastePolyshape(): void {
@ -489,22 +527,28 @@ 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.getFinalPolyshapeCoordinates(targetPoints); this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(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, {
objectType: this.drawData.initialState.objectType, shapeType: this.drawData.initialState.shapeType,
points, objectType: this.drawData.initialState.objectType,
occluded: this.drawData.initialState.occluded, points,
attributes: { ...this.drawData.initialState.attributes }, occluded: this.drawData.initialState.occluded,
label: this.drawData.initialState.label, attributes: { ...this.drawData.initialState.attributes },
color: this.drawData.initialState.color, label: this.drawData.initialState.label,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); color: this.drawData.initialState.color,
},
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,22 +585,27 @@ export class DrawHandlerImpl implements DrawHandler {
this.release(); this.release();
} }
this.onDrawDone({ this.onDrawDone(
shapeType: this.drawData.initialState.shapeType, {
objectType: this.drawData.initialState.objectType, shapeType: this.drawData.initialState.shapeType,
points: [xtl, ytl, xbr, ybr], objectType: this.drawData.initialState.objectType,
occluded: this.drawData.initialState.occluded, points: [xtl, ytl, xbr, ybr],
attributes: { ...this.drawData.initialState.attributes }, occluded: this.drawData.initialState.occluded,
label: this.drawData.initialState.label, attributes: { ...this.drawData.initialState.attributes },
color: this.drawData.initialState.color, label: this.drawData.initialState.label,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey); color: this.drawData.initialState.color,
},
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,22 +624,19 @@ 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)
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, .cube(points)
'face-stroke': 'black', .addClass('cvat_canvas_shape_drawing')
}); .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'face-stroke': 'black',
});
this.pasteShape(); this.pasteShape();
this.pastePolyshape(); this.pastePolyshape();
} }
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,10 +651,9 @@ 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, });
});
let numOfPoints = initialPoints.split(' ').length; let numOfPoints = initialPoints.split(' ').length;
while (numOfPoints) { while (numOfPoints) {
@ -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)
'pointer-events': 'none', .addClass('cvat_canvas_shape_drawing')
'fill-opacity': 0, .style({
stroke: strokeColor, 'pointer-events': 'none',
}).attr({ 'fill-opacity': 0,
'data-origin-client-id': this.editData.state.clientID, stroke: strokeColor,
}).on('drawstart drawpoint', (e: CustomEvent): void => { })
this.transform(this.geometry); .attr({
lastDrawnPoint.x = e.detail.event.clientX; 'data-origin-client-id': this.editData.state.clientID,
lastDrawnPoint.y = e.detail.event.clientY; })
}).on('drawupdate', (): void => this.transform(this.geometry)) .on('drawstart drawpoint', (e: CustomEvent): void => {
this.transform(this.geometry);
lastDrawnPoint.x = e.detail.event.clientX;
lastDrawnPoint.y = e.detail.event.clientY;
})
.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,19 +264,24 @@ 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(
.attr('fill', this.editedShape.attr('fill')) this.canvas
.attr('fill-opacity', '0.5') .polygon(points.join(' '))
.addClass('cvat_canvas_shape')); .attr('fill', this.editedShape.attr('fill'))
.attr('fill-opacity', '0.5')
.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
clone.addClass('cvat_canvas_shape_splitting'); .on('mouseenter', (): void => {
}).on('mouseleave', (): void => { clone.addClass('cvat_canvas_shape_splitting');
clone.removeClass('cvat_canvas_shape_splitting'); })
}); .on('mouseleave', (): void => {
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,42 +26,46 @@ 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(
if (shape.type === 'circle') { (shape: SVG.Shape): InteractionResult => {
const points = [(shape as SVG.Circle).cx(), (shape as SVG.Circle).cy()]; if (shape.type === 'circle') {
const points = [(shape as SVG.Circle).cx(), (shape as SVG.Circle).cy()];
return {
points: points.map((coord: number): number => coord - this.geometry.offset),
shapeType: 'points',
button: shape.attr('stroke') === 'green' ? 0 : 2,
};
}
const bbox = ((shape.node as any) as SVGRectElement).getBBox();
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: 'points', shapeType: 'rectangle',
button: shape.attr('stroke') === 'green' ? 0 : 2, button: 0,
}; };
} },
);
const bbox = (shape.node as any as SVGRectElement).getBBox();
const points = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height];
return {
points: points.map((coord: number): number => coord - this.geometry.offset),
shapeType: 'rectangle',
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,15 +148,18 @@ 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
this.interactionShapes.push(this.currentInteractionShape); .on('drawstop', (): void => {
this.shapesWereUpdated = true; this.interactionShapes.push(this.currentInteractionShape);
this.shapesWereUpdated = true;
this.canvas.off('mousedown.interaction', eventListener);
this.interact({ enabled: false }); this.canvas.off('mousedown.interaction', eventListener);
}).addClass('cvat_canvas_shape_drawing').attr({ this.interact({ enabled: false });
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, })
}); .addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
} }
private initInteraction(): void { private initInteraction(): void {
@ -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
weight: 'bolder', .text('')
}).fill('white').addClass('cvat_canvas_text'), .font({
update(shape: SVG.Shape): void{ weight: 'bolder',
})
.fill('white')
.addClass('cvat_canvas_text'),
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
const [x, y] = point.split(',').map((coord: string): number => +coord); .trim()
return { x, y }; .split(/\s/)
}); .map(
(point: string): Point => {
const [x, y] = point.split(',').map((coord: string): number => +coord);
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(
this.splitDone = true; 'click.split',
this.onSplitDone(state); (): void => {
}, { this.splitDone = true;
once: true, this.onSplitDone(state);
}); },
{
once: true,
},
);
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -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],

@ -1,21 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"module": "es6", "module": "es6",
"target": "es6", "target": "es6",
"noImplicitAny": true, "noImplicitAny": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"declaration": true, "declaration": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"moduleResolution": "node", "moduleResolution": "node",
"declarationDir": "dist/declaration", "declarationDir": "dist/declaration",
"paths": { "paths": {
"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,33 +23,38 @@ const nodeConfig = {
extensions: ['.ts', '.js', '.json'], extensions: ['.ts', '.js', '.json'],
}, },
module: { module: {
rules: [{ rules: [
test: /\.ts$/, {
exclude: /node_modules/, test: /\.ts$/,
use: { exclude: /node_modules/,
loader: 'babel-loader', use: {
options: { loader: 'babel-loader',
plugins: [ options: {
'@babel/plugin-proposal-class-properties', plugins: [
'@babel/plugin-proposal-optional-chaining' '@babel/plugin-proposal-class-properties',
], '@babel/plugin-proposal-optional-chaining',
presets: [ ],
['@babel/preset-env'], presets: [['@babel/preset-env'], ['@babel/typescript']],
['@babel/typescript'], sourceType: 'unambiguous',
], },
sourceType: 'unambiguous',
}, },
}, },
}, { {
test: /\.(css|scss)$/, test: /\.(css|scss)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['style-loader', { use: [
loader: 'css-loader', 'style-loader',
options: { {
importLoaders: 2, loader: 'css-loader',
}, options: {
}, 'postcss-loader', 'sass-loader'] importLoaders: 2,
}], },
},
'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,32 +88,43 @@ const webConfig = {
extensions: ['.ts', '.js', '.json'], extensions: ['.ts', '.js', '.json'],
}, },
module: { module: {
rules: [{ rules: [
test: /\.ts$/, {
exclude: /node_modules/, test: /\.ts$/,
use: { exclude: /node_modules/,
loader: 'babel-loader', use: {
options: { loader: 'babel-loader',
plugins: ['@babel/plugin-proposal-class-properties'], options: {
presets: [ plugins: ['@babel/plugin-proposal-class-properties'],
['@babel/preset-env', { presets: [
targets: '> 2.5%', // https://github.com/browserslist/browserslist [
}], '@babel/preset-env',
['@babel/typescript'], {
], targets: '> 2.5%', // https://github.com/browserslist/browserslist
sourceType: 'unambiguous', },
],
['@babel/typescript'],
],
sourceType: 'unambiguous',
},
}, },
}, },
}, { {
test: /\.scss$/, test: /\.scss$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['style-loader', { use: [
loader: 'css-loader', 'style-loader',
options: { {
importLoaders: 2, loader: 'css-loader',
}, options: {
}, 'postcss-loader', 'sass-loader'] importLoaders: 2,
}], },
},
'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": [ plugins: ['security', 'no-unsanitized', 'no-unsafe-innerhtml'],
"security", extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'],
"no-unsanitized", rules: {
"no-unsafe-innerhtml", 'no-await-in-loop': [0],
], 'global-require': [0],
"extends": [ 'no-new': [0],
"eslint:recommended", 'class-methods-use-this': [0],
"plugin:security/recommended", 'no-restricted-properties': [
"plugin:no-unsanitized/DOM", 0,
"airbnb-base", {
], object: 'Math',
"rules": { property: 'pow',
"no-await-in-loop": [0], },
"global-require": [0], ],
"no-new": [0], 'no-plusplus': [0],
"class-methods-use-this": [0], 'no-param-reassign': [0],
"no-restricted-properties": [0, { 'no-underscore-dangle': ['error', { allowAfterThis: true }],
"object": "Math", 'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
"property": "pow", 'no-continue': [0],
}], 'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
"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,14 +1,13 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
* Class representing an annotation loader * Class representing an annotation loader
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Loader { class Loader {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -21,42 +20,42 @@
Object.defineProperties(this, { Object.defineProperties(this, {
name: { name: {
/** /**
* @name name * @name name
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.name, get: () => data.name,
}, },
format: { format: {
/** /**
* @name format * @name format
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.format, get: () => data.format,
}, },
version: { version: {
/** /**
* @name version * @name version
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.version, get: () => data.version,
}, },
enabled: { enabled: {
/** /**
* @name enabled * @name enabled
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.enabled, get: () => data.enabled,
}, },
}); });
@ -64,10 +63,10 @@
} }
/** /**
* Class representing an annotation dumper * Class representing an annotation dumper
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Dumper { class Dumper {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -80,42 +79,42 @@
Object.defineProperties(this, { Object.defineProperties(this, {
name: { name: {
/** /**
* @name name * @name name
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Dumper
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.name, get: () => data.name,
}, },
format: { format: {
/** /**
* @name format * @name format
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Dumper
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.format, get: () => data.format,
}, },
version: { version: {
/** /**
* @name version * @name version
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Dumper
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.version, get: () => data.version,
}, },
enabled: { enabled: {
/** /**
* @name enabled * @name enabled
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.enabled, get: () => data.enabled,
}, },
}); });
@ -123,10 +122,10 @@
} }
/** /**
* Class representing an annotation format * Class representing an annotation format
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class AnnotationFormats { class AnnotationFormats {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -138,22 +137,22 @@
Object.defineProperties(this, { Object.defineProperties(this, {
loaders: { loaders: {
/** /**
* @name loaders * @name loaders
* @type {module:API.cvat.classes.Loader[]} * @type {module:API.cvat.classes.Loader[]}
* @memberof module:API.cvat.classes.AnnotationFormats * @memberof module:API.cvat.classes.AnnotationFormats
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => [...data.importers], get: () => [...data.importers],
}, },
dumpers: { dumpers: {
/** /**
* @name dumpers * @name dumpers
* @type {module:API.cvat.classes.Dumper[]} * @type {module:API.cvat.classes.Dumper[]}
* @memberof module:API.cvat.classes.AnnotationFormats * @memberof module:API.cvat.classes.AnnotationFormats
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => [...data.exporters], get: () => [...data.exporters],
}, },
}); });

@ -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) {
@ -48,31 +34,28 @@
let shapeModel = null; let shapeModel = null;
switch (type) { switch (type) {
case 'rectangle': case 'rectangle':
shapeModel = new RectangleShape(shapeData, clientID, color, injection); shapeModel = new RectangleShape(shapeData, clientID, color, injection);
break; break;
case 'polygon': case 'polygon':
shapeModel = new PolygonShape(shapeData, clientID, color, injection); shapeModel = new PolygonShape(shapeData, clientID, color, injection);
break; break;
case 'polyline': case 'polyline':
shapeModel = new PolylineShape(shapeData, clientID, color, injection); shapeModel = new PolylineShape(shapeData, clientID, color, injection);
break; break;
case 'points': case 'points':
shapeModel = new PointsShape(shapeData, clientID, color, injection); shapeModel = new PointsShape(shapeData, clientID, color, injection);
break; break;
case 'cuboid': case 'cuboid':
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];
@ -80,25 +63,23 @@
let trackModel = null; let trackModel = null;
switch (type) { switch (type) {
case 'rectangle': case 'rectangle':
trackModel = new RectangleTrack(trackData, clientID, color, injection); trackModel = new RectangleTrack(trackData, clientID, color, injection);
break; break;
case 'polygon': case 'polygon':
trackModel = new PolygonTrack(trackData, clientID, color, injection); trackModel = new PolygonTrack(trackData, clientID, color, injection);
break; break;
case 'polyline': case 'polyline':
trackModel = new PolylineTrack(trackData, clientID, color, injection); trackModel = new PolylineTrack(trackData, clientID, color, injection);
break; break;
case 'points': case 'points':
trackModel = new PointsTrack(trackData, clientID, color, injection); trackModel = new PointsTrack(trackData, clientID, color, injection);
break; break;
case 'cuboid': case 'cuboid':
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)
accumulator.push(...value); .reduce((accumulator, value) => {
return accumulator; accumulator.push(...value);
}, []).filter((tag) => !tag.removed) return accumulator;
}, [])
.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,22 +371,24 @@
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, value: objectStates[0].attributes[attrID],
value: objectStates[0].attributes[attrID], });
}); }
}
return accumulator; return accumulator;
}, []), }, []),
}; };
const trackModel = trackFactory(track, clientID, this.injection); const trackModel = trackFactory(track, clientID, this.injection);
@ -426,20 +400,23 @@
object.removed = true; object.removed = true;
} }
this.history.do(HistoryActions.MERGED_OBJECTS, () => { this.history.do(
trackModel.removed = true; HistoryActions.MERGED_OBJECTS,
for (const object of objectsForMerge) { () => {
object.removed = false; trackModel.removed = true;
} for (const object of objectsForMerge) {
}, () => { object.removed = false;
trackModel.removed = false; }
for (const object of objectsForMerge) { },
object.removed = true; () => {
} trackModel.removed = false;
}, [ for (const object of objectsForMerge) {
...objectsForMerge object.removed = true;
.map((object) => object.clientID), trackModel.clientID, }
], objectStates[0].frame); },
[...objectsForMerge.map((object) => object.clientID), trackModel.clientID],
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,17 +449,16 @@
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, value: objectState.attributes[attrID],
value: objectState.attributes[attrID], });
}); }
}
return accumulator; return accumulator;
}, []), }, []),
frame, frame,
}; };
@ -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(
object.removed = false; HistoryActions.SPLITTED_TRACK,
prevTrack.removed = true; () => {
nextTrack.removed = true; object.removed = false;
}, () => { prevTrack.removed = true;
object.removed = true; nextTrack.removed = true;
prevTrack.removed = false; },
nextTrack.removed = false; () => {
}, [object.clientID, prevTrack.clientID, nextTrack.clientID], frame); object.removed = true;
prevTrack.removed = false;
nextTrack.removed = false;
},
[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(
objectsForGroup.forEach((object, idx) => { HistoryActions.GROUPED_OBJECTS,
object.group = undoGroups[idx]; () => {
}); objectsForGroup.forEach((object, idx) => {
}, () => { object.group = undoGroups[idx];
objectsForGroup.forEach((object, idx) => { });
object.group = redoGroups[idx]; },
}); () => {
}, objectsForGroup.map((object) => object.clientID), objectStates[0].frame); objectsForGroup.forEach((object, idx) => {
object.group = redoGroups[idx];
});
},
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(
importedArray.forEach((object) => { HistoryActions.CREATED_OBJECTS,
object.removed = true; () => {
}); importedArray.forEach((object) => {
}, () => { object.removed = true;
importedArray.forEach((object) => { });
object.removed = false; },
}); () => {
}, importedArray.map((object) => object.clientID), objectStates[0].frame); importedArray.forEach((object) => {
object.removed = false;
});
},
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,11 +140,10 @@ 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; }, {});
}, {});
let xtl = Number.MAX_SAFE_INTEGER; let xtl = Number.MAX_SAFE_INTEGER;
let xbr = Number.MIN_SAFE_INTEGER; let xbr = Number.MIN_SAFE_INTEGER;
@ -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;

File diff suppressed because it is too large Load Diff

@ -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;

File diff suppressed because it is too large Load Diff

@ -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,33 +1,32 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
* Share files types * Share files types
* @enum {string} * @enum {string}
* @name ShareFileType * @name ShareFileType
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} DIR 'DIR' * @property {string} DIR 'DIR'
* @property {string} REG 'REG' * @property {string} REG 'REG'
* @readonly * @readonly
*/ */
const ShareFileType = Object.freeze({ const ShareFileType = Object.freeze({
DIR: 'DIR', DIR: 'DIR',
REG: 'REG', REG: 'REG',
}); });
/** /**
* Task statuses * Task statuses
* @enum {string} * @enum {string}
* @name TaskStatus * @name TaskStatus
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation' * @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation' * @property {string} VALIDATION 'validation'
* @property {string} COMPLETED 'completed' * @property {string} COMPLETED 'completed'
* @readonly * @readonly
*/ */
const TaskStatus = Object.freeze({ const TaskStatus = Object.freeze({
ANNOTATION: 'annotation', ANNOTATION: 'annotation',
VALIDATION: 'validation', VALIDATION: 'validation',
@ -35,17 +34,17 @@
}); });
/** /**
* List of RQ statuses * List of RQ statuses
* @enum {string} * @enum {string}
* @name RQStatus * @name RQStatus
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} QUEUED 'queued' * @property {string} QUEUED 'queued'
* @property {string} STARTED 'started' * @property {string} STARTED 'started'
* @property {string} FINISHED 'finished' * @property {string} FINISHED 'finished'
* @property {string} FAILED 'failed' * @property {string} FAILED 'failed'
* @property {string} UNKNOWN 'unknown' * @property {string} UNKNOWN 'unknown'
* @readonly * @readonly
*/ */
const RQStatus = Object.freeze({ const RQStatus = Object.freeze({
QUEUED: 'queued', QUEUED: 'queued',
STARTED: 'started', STARTED: 'started',
@ -55,31 +54,31 @@
}); });
/** /**
* Task modes * Task modes
* @enum {string} * @enum {string}
* @name TaskMode * @name TaskMode
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation' * @property {string} ANNOTATION 'annotation'
* @property {string} INTERPOLATION 'interpolation' * @property {string} INTERPOLATION 'interpolation'
* @readonly * @readonly
*/ */
const TaskMode = Object.freeze({ const TaskMode = Object.freeze({
ANNOTATION: 'annotation', ANNOTATION: 'annotation',
INTERPOLATION: 'interpolation', INTERPOLATION: 'interpolation',
}); });
/** /**
* Attribute types * Attribute types
* @enum {string} * @enum {string}
* @name AttributeType * @name AttributeType
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} CHECKBOX 'checkbox' * @property {string} CHECKBOX 'checkbox'
* @property {string} SELECT 'select' * @property {string} SELECT 'select'
* @property {string} RADIO 'radio' * @property {string} RADIO 'radio'
* @property {string} NUMBER 'number' * @property {string} NUMBER 'number'
* @property {string} TEXT 'text' * @property {string} TEXT 'text'
* @readonly * @readonly
*/ */
const AttributeType = Object.freeze({ const AttributeType = Object.freeze({
CHECKBOX: 'checkbox', CHECKBOX: 'checkbox',
RADIO: 'radio', RADIO: 'radio',
@ -89,15 +88,15 @@
}); });
/** /**
* Object types * Object types
* @enum {string} * @enum {string}
* @name ObjectType * @name ObjectType
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} TAG 'tag' * @property {string} TAG 'tag'
* @property {string} SHAPE 'shape' * @property {string} SHAPE 'shape'
* @property {string} TRACK 'track' * @property {string} TRACK 'track'
* @readonly * @readonly
*/ */
const ObjectType = Object.freeze({ const ObjectType = Object.freeze({
TAG: 'tag', TAG: 'tag',
SHAPE: 'shape', SHAPE: 'shape',
@ -105,17 +104,17 @@
}); });
/** /**
* Object shapes * Object shapes
* @enum {string} * @enum {string}
* @name ObjectShape * @name ObjectShape
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} RECTANGLE 'rectangle' * @property {string} RECTANGLE 'rectangle'
* @property {string} POLYGON 'polygon' * @property {string} POLYGON 'polygon'
* @property {string} POLYLINE 'polyline' * @property {string} POLYLINE 'polyline'
* @property {string} POINTS 'points' * @property {string} POINTS 'points'
* @property {string} CUBOID 'cuboid' * @property {string} CUBOID 'cuboid'
* @readonly * @readonly
*/ */
const ObjectShape = Object.freeze({ const ObjectShape = Object.freeze({
RECTANGLE: 'rectangle', RECTANGLE: 'rectangle',
POLYGON: 'polygon', POLYGON: 'polygon',
@ -125,14 +124,14 @@
}); });
/** /**
* Annotation type * Annotation type
* @enum {string} * @enum {string}
* @name Source * @name Source
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} MANUAL 'manual' * @property {string} MANUAL 'manual'
* @property {string} AUTO 'auto' * @property {string} AUTO 'auto'
* @readonly * @readonly
*/ */
const Source = Object.freeze({ const Source = Object.freeze({
MANUAL: 'manual', MANUAL: 'manual',
AUTO: 'auto', AUTO: 'auto',
@ -211,27 +210,27 @@
}); });
/** /**
* Types of actions with annotations * Types of actions with annotations
* @enum {string} * @enum {string}
* @name HistoryActions * @name HistoryActions
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @property {string} CHANGED_LABEL Changed label * @property {string} CHANGED_LABEL Changed label
* @property {string} CHANGED_ATTRIBUTES Changed attributes * @property {string} CHANGED_ATTRIBUTES Changed attributes
* @property {string} CHANGED_POINTS Changed points * @property {string} CHANGED_POINTS Changed points
* @property {string} CHANGED_OUTSIDE Changed outside * @property {string} CHANGED_OUTSIDE Changed outside
* @property {string} CHANGED_OCCLUDED Changed occluded * @property {string} CHANGED_OCCLUDED Changed occluded
* @property {string} CHANGED_ZORDER Changed z-order * @property {string} CHANGED_ZORDER Changed z-order
* @property {string} CHANGED_LOCK Changed lock * @property {string} CHANGED_LOCK Changed lock
* @property {string} CHANGED_COLOR Changed color * @property {string} CHANGED_COLOR Changed color
* @property {string} CHANGED_HIDDEN Changed hidden * @property {string} CHANGED_HIDDEN Changed hidden
* @property {string} CHANGED_SOURCE Changed source * @property {string} CHANGED_SOURCE Changed source
* @property {string} MERGED_OBJECTS Merged objects * @property {string} MERGED_OBJECTS Merged objects
* @property {string} SPLITTED_TRACK Splitted track * @property {string} SPLITTED_TRACK Splitted track
* @property {string} GROUPED_OBJECTS Grouped objects * @property {string} GROUPED_OBJECTS Grouped objects
* @property {string} CREATED_OBJECTS Created objects * @property {string} CREATED_OBJECTS Created objects
* @property {string} REMOVED_OBJECT Removed object * @property {string} REMOVED_OBJECT Removed object
* @readonly * @readonly
*/ */
const HistoryActions = Object.freeze({ const HistoryActions = Object.freeze({
CHANGED_LABEL: 'Changed label', CHANGED_LABEL: 'Changed label',
CHANGED_ATTRIBUTES: 'Changed attributes', CHANGED_ATTRIBUTES: 'Changed attributes',
@ -265,18 +264,43 @@
}; };
/** /**
* Array of hex colors * Array of hex colors
* @name colors * @name colors
* @memberof module:API.cvat.enums * @memberof module:API.cvat.enums
* @type {string[]} * @type {string[]}
* @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');
@ -13,15 +8,15 @@
const config = require('./config'); const config = require('./config');
/** /**
* Base exception class * Base exception class
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends Error * @extends Error
* @ignore * @ignore
*/ */
class Exception extends Error { class Exception extends Error {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
*/ */
constructor(message) { constructor(message) {
super(message); super(message);
@ -32,126 +27,125 @@
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(
system: { this,
/** Object.freeze({
* @name system system: {
* @type {string} /**
* @memberof module:API.cvat.exceptions.Exception * @name system
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => system, * @instance
}, */
client: { get: () => system,
/** },
* @name client client: {
* @type {string} /**
* @memberof module:API.cvat.exceptions.Exception * @name client
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => client, * @instance
}, */
time: { get: () => client,
/** },
* @name time time: {
* @type {string} /**
* @memberof module:API.cvat.exceptions.Exception * @name time
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => time, * @instance
}, */
jobID: { get: () => time,
/** },
* @name jobID jobID: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name jobID
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => jobID, * @instance
}, */
taskID: { get: () => jobID,
/** },
* @name taskID taskID: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name taskID
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => taskID, * @instance
}, */
projID: { get: () => taskID,
/** },
* @name projID projID: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name projID
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => projID, * @instance
}, */
clientID: { get: () => projID,
/** },
* @name clientID clientID: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name clientID
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => clientID, * @instance
}, */
filename: { get: () => clientID,
/** },
* @name filename filename: {
* @type {string} /**
* @memberof module:API.cvat.exceptions.Exception * @name filename
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => filename, * @instance
}, */
line: { get: () => filename,
/** },
* @name line line: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name line
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => line, * @instance
}, */
column: { get: () => line,
/** },
* @name column column: {
* @type {integer} /**
* @memberof module:API.cvat.exceptions.Exception * @name column
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.exceptions.Exception
*/ * @readonly
get: () => column, * @instance
}, */
})); get: () => column,
},
}),
);
} }
/** /**
* Save an exception on a server * Save an exception on a server
* @name save * @name save
* @method * @method
* @memberof Exception * @memberof Exception
* @instance * @instance
* @async * @async
*/ */
async save() { async save() {
const exceptionObject = { const exceptionObject = {
system: this.system, system: this.system,
@ -178,86 +172,89 @@
} }
/** /**
* Exceptions are referred with arguments data * Exceptions are referred with arguments data
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception * @extends module:API.cvat.exceptions.Exception
*/ */
class ArgumentError extends Exception { class ArgumentError extends Exception {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
*/ */
constructor(message) { constructor(message) {
super(message); super(message);
} }
} }
/** /**
* Unexpected problems with data which are not connected with a user input * Unexpected problems with data which are not connected with a user input
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception * @extends module:API.cvat.exceptions.Exception
*/ */
class DataError extends Exception { class DataError extends Exception {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
*/ */
constructor(message) { constructor(message) {
super(message); super(message);
} }
} }
/** /**
* Unexpected situations in code * Unexpected situations in code
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception * @extends module:API.cvat.exceptions.Exception
*/ */
class ScriptingError extends Exception { class ScriptingError extends Exception {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
*/ */
constructor(message) { constructor(message) {
super(message); super(message);
} }
} }
/** /**
* Plugin-referred exceptions * Plugin-referred exceptions
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception * @extends module:API.cvat.exceptions.Exception
*/ */
class PluginError extends Exception { class PluginError extends Exception {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
*/ */
constructor(message) { constructor(message) {
super(message); super(message);
} }
} }
/** /**
* Exceptions in interaction with a server * Exceptions in interaction with a server
* @memberof module:API.cvat.exceptions * @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception * @extends module:API.cvat.exceptions.Exception
*/ */
class ServerError extends Exception { class ServerError extends Exception {
/** /**
* @param {string} message - Exception message * @param {string} message - Exception message
* @param {(string|integer)} code - Response code * @param {(string|integer)} code - Response code
*/ */
constructor(message, code) { constructor(message, code) {
super(message); super(message);
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
/** this,
* @name code Object.freeze({
* @type {(string|integer)} /**
* @memberof module:API.cvat.exceptions.ServerError * @name code
* @readonly * @type {(string|integer)}
* @instance * @memberof module:API.cvat.exceptions.ServerError
*/ * @readonly
code: { * @instance
get: () => code, */
}, 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');
@ -19,100 +13,93 @@
const frameDataCache = {}; const frameDataCache = {};
/** /**
* Class provides meta information about specific frame and frame itself * Class provides meta information about specific frame and frame itself
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @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, * @name filename
startFrame, * @type {string}
stopFrame, * @memberof module:API.cvat.classes.FrameData
decodeForward, * @readonly
}) { * @instance
Object.defineProperties(this, Object.freeze({ */
/** filename: {
* @name filename value: name,
* @type {string} writable: false,
* @memberof module:API.cvat.classes.FrameData },
* @readonly /**
* @instance * @name width
*/ * @type {integer}
filename: { * @memberof module:API.cvat.classes.FrameData
value: name, * @readonly
writable: false, * @instance
}, */
/** width: {
* @name width value: width,
* @type {integer} writable: false,
* @memberof module:API.cvat.classes.FrameData },
* @readonly /**
* @instance * @name height
*/ * @type {integer}
width: { * @memberof module:API.cvat.classes.FrameData
value: width, * @readonly
writable: false, * @instance
}, */
/** height: {
* @name height value: height,
* @type {integer} writable: false,
* @memberof module:API.cvat.classes.FrameData },
* @readonly tid: {
* @instance value: taskID,
*/ writable: false,
height: { },
value: height, /**
writable: false, * @name number
}, * @type {integer}
tid: { * @memberof module:API.cvat.classes.FrameData
value: taskID, * @readonly
writable: false, * @instance
}, */
/** number: {
* @name number value: frameNumber,
* @type {integer} writable: false,
* @memberof module:API.cvat.classes.FrameData },
* @readonly startFrame: {
* @instance value: startFrame,
*/ writable: false,
number: { },
value: frameNumber, stopFrame: {
writable: false, value: stopFrame,
}, writable: false,
startFrame: { },
value: startFrame, decodeForward: {
writable: false, value: decodeForward,
}, writable: false,
stopFrame: { },
value: stopFrame, }),
writable: false, );
},
decodeForward: {
value: decodeForward,
writable: false,
},
}));
} }
/** /**
* Method returns URL encoded image which can be placed in the img tag * Method returns URL encoded image which can be placed in the img tag
* @method data * @method data
* @returns {string} * @returns {string}
* @memberof module:API.cvat.classes.FrameData * @memberof module:API.cvat.classes.FrameData
* @instance * @instance
* @async * @async
* @param {function} [onServerRequest = () => {}] * @param {function} [onServerRequest = () => {}]
* callback which will be called if data absences local * callback which will be called if data absences local
* @throws {module:API.cvat.exception.ServerError} * @throws {module:API.cvat.exception.ServerError}
* @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,147 +160,160 @@
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)
frameDataCache[this.tid].activeChunkRequest.completed = true; .then((chunk) => {
if (!taskDataCache.nextChunkRequest) { frameDataCache[this.tid].activeChunkRequest.completed = true;
provider.requestDecodeBlock(chunk, if (!taskDataCache.nextChunkRequest) {
taskDataCache.activeChunkRequest.start, provider.requestDecodeBlock(
taskDataCache.activeChunkRequest.stop, chunk,
taskDataCache.activeChunkRequest.onDecodeAll, taskDataCache.activeChunkRequest.start,
taskDataCache.activeChunkRequest.rejectRequestAll); taskDataCache.activeChunkRequest.stop,
} taskDataCache.activeChunkRequest.onDecodeAll,
}).catch((exception) => { taskDataCache.activeChunkRequest.rejectRequestAll,
if (exception instanceof Exception) { );
reject(exception); }
} else { })
reject(new Exception(exception.message)); .catch((exception) => {
} if (exception instanceof Exception) {
}).finally(() => { reject(exception);
if (taskDataCache.nextChunkRequest) { } else {
if (taskDataCache.activeChunkRequest) { reject(new Exception(exception.message));
for (const r of taskDataCache.activeChunkRequest.callbacks) { }
r.reject(r.frameNumber); })
.finally(() => {
if (taskDataCache.nextChunkRequest) {
if (taskDataCache.activeChunkRequest) {
for (const r of taskDataCache.activeChunkRequest.callbacks) {
r.reject(r.frameNumber);
}
} }
taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest;
taskDataCache.nextChunkRequest = null;
makeActiveRequest();
} }
taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest; });
taskDataCache.nextChunkRequest = null;
makeActiveRequest();
}
});
}; };
if (isNode) { if (isNode) {
resolve('Dummy data'); resolve('Dummy data');
} else if (isBrowser) { } else if (isBrowser) {
provider.frame(this.number).then((frame) => { provider
if (frame === null) { .frame(this.number)
onServerRequest(); .then((frame) => {
const activeRequest = frameDataCache[this.tid].activeChunkRequest; if (frame === null) {
if (!provider.isChunkCached(start, stop)) { onServerRequest();
if (!activeRequest const activeRequest = frameDataCache[this.tid].activeChunkRequest;
|| (activeRequest if (!provider.isChunkCached(start, stop)) {
&& activeRequest.completed if (
&& activeRequest.chunkNumber !== chunkNumber)) { !activeRequest ||
if (activeRequest && activeRequest.rejectRequestAll) { (activeRequest &&
activeRequest.rejectRequestAll(); activeRequest.completed &&
} activeRequest.chunkNumber !== chunkNumber)
frameDataCache[this.tid].activeChunkRequest = { ) {
request: null, if (activeRequest && activeRequest.rejectRequestAll) {
chunkNumber, activeRequest.rejectRequestAll();
start, }
stop, frameDataCache[this.tid].activeChunkRequest = {
onDecodeAll, request: null,
rejectRequestAll, chunkNumber,
completed: false, start,
callbacks: [{ stop,
onDecodeAll,
rejectRequestAll,
completed: false,
callbacks: [
{
resolve: resolveWrapper,
reject,
frameNumber: this.number,
},
],
};
makeActiveRequest();
} else if (activeRequest.chunkNumber === chunkNumber) {
if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) {
activeRequest.onDecodeAll = onDecodeAll;
activeRequest.rejectRequestAll = rejectRequestAll;
}
activeRequest.callbacks.push({
resolve: resolveWrapper, resolve: resolveWrapper,
reject, reject,
frameNumber: this.number, frameNumber: this.number,
}], });
}; } else {
makeActiveRequest(); if (frameDataCache[this.tid].nextChunkRequest) {
} else if (activeRequest.chunkNumber === chunkNumber) { const { callbacks } = frameDataCache[this.tid].nextChunkRequest;
if (!activeRequest.onDecodeAll for (const r of callbacks) {
&& !activeRequest.rejectRequestAll) { r.reject(r.frameNumber);
activeRequest.onDecodeAll = onDecodeAll; }
activeRequest.rejectRequestAll = rejectRequestAll; }
frameDataCache[this.tid].nextChunkRequest = {
request: null,
chunkNumber,
start,
stop,
onDecodeAll,
rejectRequestAll,
completed: false,
callbacks: [
{
resolve: resolveWrapper,
reject,
frameNumber: this.number,
},
],
};
} }
} else {
activeRequest.callbacks.push({ activeRequest.callbacks.push({
resolve: resolveWrapper, resolve: resolveWrapper,
reject, reject,
frameNumber: this.number, frameNumber: this.number,
}); });
} else { provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll);
if (frameDataCache[this.tid].nextChunkRequest) {
const { callbacks } = frameDataCache[this.tid].nextChunkRequest;
for (const r of callbacks) {
r.reject(r.frameNumber);
}
}
frameDataCache[this.tid].nextChunkRequest = {
request: null,
chunkNumber,
start,
stop,
onDecodeAll,
rejectRequestAll,
completed: false,
callbacks: [{
resolve: resolveWrapper,
reject,
frameNumber: this.number,
}],
};
} }
} else { } else {
activeRequest.callbacks.push({ if (
resolve: resolveWrapper, this.number % chunkSize > chunkSize / 4 &&
reject, provider.decodedBlocksCacheSize > 1 &&
frameNumber: this.number, this.decodeForward &&
}); !provider.isNextChunkExists(this.number)
provider.requestDecodeBlock(null, start, stop, ) {
onDecodeAll, rejectRequestAll); const nextChunkNumber = Math.floor(this.number / chunkSize) + 1;
} if (nextChunkNumber * chunkSize < this.stopFrame) {
} else { provider.setReadyToLoading(nextChunkNumber);
if (this.number % chunkSize > chunkSize / 4 const nextStart = nextChunkNumber * chunkSize;
&& provider.decodedBlocksCacheSize > 1 const nextStop = (nextChunkNumber + 1) * chunkSize - 1;
&& this.decodeForward if (!provider.isChunkCached(nextStart, nextStop)) {
&& !provider.isNextChunkExists(this.number)) { if (!frameDataCache[this.tid].activeChunkRequest) {
const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; frameDataCache[this.tid].activeChunkRequest = {
if (nextChunkNumber * chunkSize < this.stopFrame) { request: null,
provider.setReadyToLoading(nextChunkNumber); chunkNumber: nextChunkNumber,
const nextStart = nextChunkNumber * chunkSize; start: nextStart,
const nextStop = (nextChunkNumber + 1) * chunkSize - 1; stop: nextStop,
if (!provider.isChunkCached(nextStart, nextStop)) { onDecodeAll: null,
if (!frameDataCache[this.tid].activeChunkRequest) { rejectRequestAll: null,
frameDataCache[this.tid].activeChunkRequest = { completed: false,
request: null, callbacks: [],
chunkNumber: nextChunkNumber, };
start: nextStart, makeActiveRequest();
stop: nextStop, }
onDecodeAll: null, } else {
rejectRequestAll: null, provider.requestDecodeBlock(null, nextStart, nextStop, null, null);
completed: false,
callbacks: [],
};
makeActiveRequest();
} }
} else {
provider.requestDecodeBlock(null, nextStart, nextStop,
null, null);
} }
} }
resolveWrapper(frame);
} }
resolveWrapper(frame); })
} .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)); }
} });
});
} }
}); });
}; };
@ -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,23 +374,28 @@
decodeForward: false, decodeForward: false,
}); });
frameData.data().then(() => { frameData
if (!(chunkIdx in this._requestedChunks) .data()
|| !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)) { .then(() => {
reject(chunkIdx); if (
} else { !(chunkIdx in this._requestedChunks) ||
this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame)
this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; ) {
if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { reject(chunkIdx);
const bufferedframes = Object.keys( } else {
this._requestedChunks[chunkIdx].buffer, this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame);
).map((f) => +f); this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData;
this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) {
const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map(
(f) => +f,
);
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,38 +536,39 @@
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
if (isNode) { .getPreview(taskID)
resolve(global.Buffer.from(result, 'binary').toString('base64')); .then((result) => {
} else if (isBrowser) { if (isNode) {
const reader = new FileReader(); // eslint-disable-next-line no-undef
reader.onload = () => { resolve(global.Buffer.from(result, 'binary').toString('base64'));
resolve(reader.result); } else if (isBrowser) {
}; const reader = new FileReader();
reader.readAsDataURL(result); reader.onload = () => {
} resolve(reader.result);
}).catch((error) => { };
reject(error); reader.readAsDataURL(result);
}); }
})
.catch((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,23 +1,16 @@
/* // 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');
/** /**
* Class representing an attribute * Class representing an attribute
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Attribute { class Attribute {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -42,73 +35,74 @@
} }
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,
* @name id Object.freeze({
* @type {integer} /**
* @memberof module:API.cvat.classes.Attribute * @name id
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
id: { * @instance
get: () => data.id, */
}, id: {
/** get: () => data.id,
* @name defaultValue },
* @type {(string|integer|boolean)} /**
* @memberof module:API.cvat.classes.Attribute * @name defaultValue
* @readonly * @type {(string|integer|boolean)}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
defaultValue: { * @instance
get: () => data.default_value, */
}, defaultValue: {
/** get: () => data.default_value,
* @name inputType },
* @type {module:API.cvat.enums.AttributeType} /**
* @memberof module:API.cvat.classes.Attribute * @name inputType
* @readonly * @type {module:API.cvat.enums.AttributeType}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
inputType: { * @instance
get: () => data.input_type, */
}, inputType: {
/** get: () => data.input_type,
* @name mutable },
* @type {boolean} /**
* @memberof module:API.cvat.classes.Attribute * @name mutable
* @readonly * @type {boolean}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
mutable: { * @instance
get: () => data.mutable, */
}, mutable: {
/** get: () => data.mutable,
* @name name },
* @type {string} /**
* @memberof module:API.cvat.classes.Attribute * @name name
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
name: { * @instance
get: () => data.name, */
}, name: {
/** get: () => data.name,
* @name values },
* @type {(string[]|integer[]|boolean[])} /**
* @memberof module:API.cvat.classes.Attribute * @name values
* @readonly * @type {(string[]|integer[]|boolean[])}
* @instance * @memberof module:API.cvat.classes.Attribute
*/ * @readonly
values: { * @instance
get: () => [...data.values], */
}, 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;
} }
@ -129,10 +123,10 @@
} }
/** /**
* Class representing a label * Class representing a label
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Label { class Label {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -151,62 +145,67 @@
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,
* @name id Object.freeze({
* @type {integer} /**
* @memberof module:API.cvat.classes.Label * @name id
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.classes.Label
*/ * @readonly
id: { * @instance
get: () => data.id, */
}, id: {
/** get: () => data.id,
* @name name },
* @type {string} /**
* @memberof module:API.cvat.classes.Label * @name name
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.Label
*/ * @readonly
name: { * @instance
get: () => data.name, */
}, name: {
/** get: () => data.name,
* @name color },
* @type {string} /**
* @memberof module:API.cvat.classes.Label * @name color
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.Label
*/ * @readonly
color: { * @instance
get: () => data.color, */
set: (color) => { color: {
if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) { get: () => data.color,
data.color = color; set: (color) => {
} else { if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) {
throw new ArgumentError('Trying to set wrong color format'); data.color = color;
} } else {
throw new ArgumentError('Trying to set wrong color format');
}
},
},
/**
* @name attributes
* @type {module:API.cvat.classes.Attribute[]}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
attributes: {
get: () => [...data.attributes],
}, },
}, }),
/** );
* @name attributes
* @type {module:API.cvat.classes.Attribute[]}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
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(
...model, new MLModel({
type: model.kind, ...model,
})); 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,20 +2,16 @@
// //
// 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');
const { LogType } = require('./enums'); const { LogType } = require('./enums');
/** /**
* Class representing a single log * Class representing a single log
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Log { class Log {
constructor(logType, payload) { constructor(logType, payload) {
this.onCloseCallback = null; this.onCloseCallback = null;
@ -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');
} }
@ -63,22 +59,21 @@ class Log {
} }
/** /**
* Method saves a durable log in a storage <br> * Method saves a durable log in a storage <br>
* Note then you can call close() multiple times <br> * Note then you can call close() multiple times <br>
* Log duration will be computed based on the latest call <br> * Log duration will be computed based on the latest call <br>
* All payloads will be shallowly combined (all top level properties will exist) * All payloads will be shallowly combined (all top level properties will exist)
* @method close * @method close
* @memberof module:API.cvat.classes.Log * @memberof module:API.cvat.classes.Log
* @param {object} [payload] part of payload can be added when close a log * @param {object} [payload] part of payload can be added when close a log
* @readonly * @readonly
* @instance * @instance
* @async * @async
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @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,12 +1,11 @@
/* // 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
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
*/ */
class MLModel { class MLModel {
constructor(data) { constructor(data) {
this._id = data.id; this._id = data.id;
@ -25,7 +24,7 @@ class MLModel {
/** /**
* @returns {string} * @returns {string}
* @readonly * @readonly
*/ */
get id() { get id() {
return this._id; return this._id;
} }
@ -33,7 +32,7 @@ class MLModel {
/** /**
* @returns {string} * @returns {string}
* @readonly * @readonly
*/ */
get name() { get name() {
return this._name; return this._name;
} }
@ -41,7 +40,7 @@ class MLModel {
/** /**
* @returns {string[]} * @returns {string[]}
* @readonly * @readonly
*/ */
get labels() { get labels() {
if (Array.isArray(this._labels)) { if (Array.isArray(this._labels)) {
return [...this._labels]; return [...this._labels];
@ -53,7 +52,7 @@ class MLModel {
/** /**
* @returns {string} * @returns {string}
* @readonly * @readonly
*/ */
get framework() { get framework() {
return this._framework; return this._framework;
} }
@ -61,7 +60,7 @@ class MLModel {
/** /**
* @returns {string} * @returns {string}
* @readonly * @readonly
*/ */
get description() { get description() {
return this._description; return this._description;
} }
@ -69,7 +68,7 @@ class MLModel {
/** /**
* @returns {module:API.cvat.enums.ModelType} * @returns {module:API.cvat.enums.ModelType}
* @readonly * @readonly
*/ */
get type() { get type() {
return this._type; return this._type;
} }
@ -77,7 +76,7 @@ class MLModel {
/** /**
* @returns {object} * @returns {object}
* @readonly * @readonly
*/ */
get params() { get params() {
return { return {
canvas: { ...this._params.canvas }, canvas: { ...this._params.canvas },

@ -1,31 +1,26 @@
/* // 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');
/** /**
* Class representing a state of an object on a specific frame * Class representing a state of an object on a specific frame
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
*/ */
class ObjectState { class ObjectState {
/** /**
* @param {Object} serialized - is an dictionary which contains * @param {Object} serialized - is an dictionary which contains
* initial information about an ObjectState; * initial information about an ObjectState;
* </br> Necessary fields: objectType, shapeType, frame, updated, group * </br> Necessary fields: objectType, shapeType, frame, updated, group
* </br> Optional fields: keyframes, clientID, serverID * </br> Optional fields: keyframes, clientID, serverID
* </br> Optional fields which can be set later: points, zOrder, outside, * </br> Optional fields which can be set later: points, zOrder, outside,
* occluded, hidden, attributes, lock, label, color, keyframe, source * occluded, hidden, attributes, lock, label, color, keyframe, source
*/ */
constructor(serialized) { constructor(serialized) {
const data = { const data = {
label: null, label: null,
@ -77,282 +72,289 @@ const { Source } = require('./enums');
writable: false, writable: false,
}); });
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
// Internal property. We don't need document it. this,
updateFlags: { Object.freeze({
get: () => data.updateFlags, // Internal property. We don't need document it.
}, updateFlags: {
frame: { get: () => data.updateFlags,
/**
* @name frame
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.frame,
},
objectType: {
/**
* @name objectType
* @type {module:API.cvat.enums.ObjectType}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.objectType,
},
shapeType: {
/**
* @name shapeType
* @type {module:API.cvat.enums.ObjectShape}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.shapeType,
},
source: {
/**
* @name source
* @type {module:API.cvat.enums.Source}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.source,
},
clientID: {
/**
* @name clientID
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.clientID,
},
serverID: {
/**
* @name serverID
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.serverID,
},
label: {
/**
* @name shape
* @type {module:API.cvat.classes.Label}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.label,
set: (labelInstance) => {
data.updateFlags.label = true;
data.label = labelInstance;
}, },
}, frame: {
color: { /**
/** * @name frame
* @name color * @type {integer}
* @type {string} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @readonly
* @instance * @instance
*/ */
get: () => data.color, get: () => data.frame,
set: (color) => {
data.updateFlags.color = true;
data.color = color;
}, },
}, objectType: {
hidden: { /**
/** * @name objectType
* @name hidden * @type {module:API.cvat.enums.ObjectType}
* @type {boolean} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @readonly
* @instance * @instance
*/ */
get: () => data.hidden, get: () => data.objectType,
set: (hidden) => {
data.updateFlags.hidden = true;
data.hidden = hidden;
}, },
}, shapeType: {
points: { /**
/** * @name shapeType
* @name points * @type {module:API.cvat.enums.ObjectShape}
* @type {number[]} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @readonly
* @throws {module:API.cvat.exceptions.ArgumentError} * @instance
* @instance */
*/ get: () => data.shapeType,
get: () => data.points,
set: (points) => {
if (Array.isArray(points)) {
data.updateFlags.points = true;
data.points = [...points];
} else {
throw new ArgumentError(
'Points are expected to be an array '
+ `but got ${typeof (points) === 'object'
? points.constructor.name : typeof (points)}`,
);
}
}, },
}, source: {
group: { /**
/** * @name source
* Object with short group info { color, id } * @type {module:API.cvat.enums.Source}
* @name group * @memberof module:API.cvat.classes.ObjectState
* @type {object} * @readonly
* @memberof module:API.cvat.classes.ObjectState * @instance
* @instance */
* @readonly get: () => data.source,
*/
get: () => data.group,
},
zOrder: {
/**
* @name zOrder
* @type {integer | null}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.zOrder,
set: (zOrder) => {
data.updateFlags.zOrder = true;
data.zOrder = zOrder;
}, },
}, clientID: {
outside: { /**
/** * @name clientID
* @name outside * @type {integer}
* @type {boolean} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @readonly
* @instance * @instance
*/ */
get: () => data.outside, get: () => data.clientID,
set: (outside) => {
data.updateFlags.outside = true;
data.outside = outside;
}, },
}, serverID: {
keyframe: { /**
/** * @name serverID
* @name keyframe * @type {integer}
* @type {boolean} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @readonly
* @instance * @instance
*/ */
get: () => data.keyframe, get: () => data.serverID,
set: (keyframe) => {
data.updateFlags.keyframe = true;
data.keyframe = keyframe;
}, },
}, label: {
keyframes: { /**
/** * @name shape
* Object of keyframes { first, prev, next, last } * @type {module:API.cvat.classes.Label}
* @name keyframes * @memberof module:API.cvat.classes.ObjectState
* @type {object | null} * @instance
* @memberof module:API.cvat.classes.ObjectState */
* @readonly get: () => data.label,
* @instance set: (labelInstance) => {
*/ data.updateFlags.label = true;
get: () => { data.label = labelInstance;
if (typeof (data.keyframes) === 'object') { },
return { ...data.keyframes }; },
} color: {
/**
* @name color
* @type {string}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.color,
set: (color) => {
data.updateFlags.color = true;
data.color = color;
},
},
hidden: {
/**
* @name hidden
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.hidden,
set: (hidden) => {
data.updateFlags.hidden = true;
data.hidden = hidden;
},
},
points: {
/**
* @name points
* @type {number[]}
* @memberof module:API.cvat.classes.ObjectState
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
*/
get: () => data.points,
set: (points) => {
if (Array.isArray(points)) {
data.updateFlags.points = true;
data.points = [...points];
} else {
throw new ArgumentError(
'Points are expected to be an array ' +
`but got ${
typeof points === 'object' ? points.constructor.name : typeof points
}`,
);
}
},
},
group: {
/**
* Object with short group info { color, id }
* @name group
* @type {object}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @readonly
*/
get: () => data.group,
},
zOrder: {
/**
* @name zOrder
* @type {integer | null}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.zOrder,
set: (zOrder) => {
data.updateFlags.zOrder = true;
data.zOrder = zOrder;
},
},
outside: {
/**
* @name outside
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.outside,
set: (outside) => {
data.updateFlags.outside = true;
data.outside = outside;
},
},
keyframe: {
/**
* @name keyframe
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.keyframe,
set: (keyframe) => {
data.updateFlags.keyframe = true;
data.keyframe = keyframe;
},
},
keyframes: {
/**
* Object of keyframes { first, prev, next, last }
* @name keyframes
* @type {object | null}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => {
if (typeof data.keyframes === 'object') {
return { ...data.keyframes };
}
return null; return null;
},
}, },
}, occluded: {
occluded: { /**
/** * @name occluded
* @name occluded * @type {boolean}
* @type {boolean} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @instance
* @instance */
*/ get: () => data.occluded,
get: () => data.occluded, set: (occluded) => {
set: (occluded) => { data.updateFlags.occluded = true;
data.updateFlags.occluded = true; data.occluded = occluded;
data.occluded = occluded; },
}, },
}, lock: {
lock: { /**
/** * @name lock
* @name lock * @type {boolean}
* @type {boolean} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @instance
* @instance */
*/ get: () => data.lock,
get: () => data.lock, set: (lock) => {
set: (lock) => { data.updateFlags.lock = true;
data.updateFlags.lock = true; data.lock = lock;
data.lock = lock; },
}, },
}, pinned: {
pinned: { /**
/** * @name pinned
* @name pinned * @type {boolean | null}
* @type {boolean | null} * @memberof module:API.cvat.classes.ObjectState
* @memberof module:API.cvat.classes.ObjectState * @instance
* @instance */
*/ get: () => {
get: () => { if (typeof data.pinned === 'boolean') {
if (typeof (data.pinned) === 'boolean') { return data.pinned;
return data.pinned; }
}
return null; return null;
},
set: (pinned) => {
data.updateFlags.pinned = true;
data.pinned = pinned;
},
}, },
set: (pinned) => { updated: {
data.updateFlags.pinned = true; /**
data.pinned = pinned; * Timestamp of the latest updated of the object
* @name updated
* @type {number}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @readonly
*/
get: () => data.updated,
}, },
}, attributes: {
updated: { /**
/** * Object is id:value pairs where "id" is an integer
* Timestamp of the latest updated of the object * attribute identifier and "value" is an attribute value
* @name updated * @name attributes
* @type {number} * @type {Object}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @throws {module:API.cvat.exceptions.ArgumentError}
* @readonly * @instance
*/ */
get: () => data.updated, get: () => data.attributes,
}, set: (attributes) => {
attributes: { if (typeof attributes !== 'object') {
/** throw new ArgumentError(
* Object is id:value pairs where "id" is an integer 'Attributes are expected to be an object ' +
* attribute identifier and "value" is an attribute value `but got ${
* @name attributes typeof attributes === 'object'
* @type {Object} ? attributes.constructor.name
* @memberof module:API.cvat.classes.ObjectState : typeof attributes
* @throws {module:API.cvat.exceptions.ArgumentError} }`,
* @instance );
*/ }
get: () => data.attributes,
set: (attributes) => {
if (typeof (attributes) !== 'object') {
throw new ArgumentError(
'Attributes are expected to be an object '
+ `but got ${typeof (attributes) === 'object'
? attributes.constructor.name : typeof (attributes)}`,
);
}
for (const attrID of Object.keys(attributes)) { for (const attrID of Object.keys(attributes)) {
data.updateFlags.attributes = true; data.updateFlags.attributes = true;
data.attributes[attrID] = attributes[attrID]; data.attributes[attrID] = attributes[attrID];
} }
},
}, },
}, }),
})); );
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;
} }
@ -392,38 +394,36 @@ const { Source } = require('./enums');
} }
/** /**
* Method saves/updates an object state in a collection * Method saves/updates an object state in a collection
* @method save * @method save
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @readonly * @readonly
* @instance * @instance
* @async * @async
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
* @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;
} }
/** /**
* Method deletes an object from a collection * Method deletes an object from a collection
* @method delete * @method delete
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @readonly * @readonly
* @instance * @instance
* @param {integer} frame current frame number * @param {integer} frame current frame number
* @param {boolean} [force=false] delete object even if it is locked * @param {boolean} [force=false] delete object even if it is locked
* @async * @async
* @returns {boolean} true if object has been deleted * @returns {boolean} true if object has been deleted
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @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(
get: { this,
value: get, Object.freeze({
writable: false, get: {
}, value: get,
})); 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`, proxy: config.proxy,
authenticationData, { });
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 (
url, await Axios({
...data, url,
})).data; ...data,
})
).data;
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }
@ -372,10 +360,9 @@
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) {
setTimeout(request, 3000); setTimeout(request, 3000);
} else { } else {
@ -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(
`Unknown task state has been received: ${response.data.state}`, new ServerError(
500, `Unknown task state has been received: ${response.data.state}`,
)); 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,17 +678,19 @@
async function request() { async function request() {
Axios.get(`${url}`, { Axios.get(`${url}`, {
proxy: config.proxy, proxy: config.proxy,
}).then((response) => { })
if (response.status === 202) { .then((response) => {
setTimeout(request, 3000); if (response.status === 202) {
} else { setTimeout(request, 3000);
query = `${query}&action=download`; } else {
url = `${baseURL}?${query}`; query = `${query}&action=download`;
resolve(url); url = `${baseURL}?${query}`;
} resolve(url);
}).catch((errorData) => { }
reject(generateError(errorData)); })
}); .catch((errorData) => {
reject(generateError(errorData));
});
} }
setTimeout(request); setTimeout(request);
@ -739,13 +729,12 @@
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', },
}, });
});
return response.data; return response.data;
} catch (errorData) { } catch (errorData) {
@ -757,13 +746,12 @@
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', },
}, });
});
return response.data; return response.data;
} catch (errorData) { } catch (errorData) {
@ -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,92 +810,95 @@
} }
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
server: { this,
value: Object.freeze({ Object.freeze({
about, server: {
share, value: Object.freeze({
formats, about,
exception, share,
login, formats,
logout, exception,
changePassword, login,
requestPasswordReset, logout,
resetPassword, changePassword,
authorized, requestPasswordReset,
register, resetPassword,
request: serverRequest, authorized,
userAgreements, register,
installedApps, request: serverRequest,
}), userAgreements,
writable: false, installedApps,
}, }),
writable: false,
tasks: { },
value: Object.freeze({
getTasks, tasks: {
saveTask, value: Object.freeze({
createTask, getTasks,
deleteTask, saveTask,
exportDataset, createTask,
}), deleteTask,
writable: false, exportDataset,
}, }),
writable: false,
jobs: { },
value: Object.freeze({
getJob, jobs: {
saveJob, value: Object.freeze({
}), getJob,
writable: false, saveJob,
}, }),
writable: false,
users: { },
value: Object.freeze({
getUsers, users: {
getSelf, value: Object.freeze({
}), getUsers,
writable: false, getSelf,
}, }),
writable: false,
frames: { },
value: Object.freeze({
getData, frames: {
getMeta, value: Object.freeze({
getPreview, getData,
}), getMeta,
writable: false, getPreview,
}, }),
writable: false,
annotations: { },
value: Object.freeze({
updateAnnotations, annotations: {
getAnnotations, value: Object.freeze({
dumpAnnotations, updateAnnotations,
uploadAnnotations, getAnnotations,
}), dumpAnnotations,
writable: false, uploadAnnotations,
}, }),
writable: false,
logs: { },
value: Object.freeze({
save: saveLogs, logs: {
}), value: Object.freeze({
writable: false, save: saveLogs,
}, }),
writable: false,
lambda: { },
value: Object.freeze({
list: getLambdaFunctions, lambda: {
status: getRequestStatus, value: Object.freeze({
requests: getLambdaRequests, list: getLambdaFunctions,
run: runLambdaRequest, status: getRequestStatus,
call: callLambdaFunction, requests: getLambdaRequests,
cancel: cancelLambdaRequest, run: runLambdaRequest,
}), call: callLambdaFunction,
writable: false, cancel: cancelLambdaRequest,
}, }),
})); writable: false,
},
}),
);
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,97 +1,98 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
* Class representing collection statistics * Class representing collection statistics
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class Statistics { class Statistics {
constructor(label, total) { constructor(label, total) {
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
/** this,
* Statistics by labels with a structure: Object.freeze({
* @example /**
* { * Statistics by labels with a structure:
* label: { * @example
* boxes: { * {
* tracks: 10, * label: {
* shapes: 11, * boxes: {
* }, * tracks: 10,
* polygons: { * shapes: 11,
* tracks: 13, * },
* shapes: 14, * polygons: {
* }, * tracks: 13,
* polylines: { * shapes: 14,
* tracks: 16, * },
* shapes: 17, * polylines: {
* }, * tracks: 16,
* points: { * shapes: 17,
* tracks: 19, * },
* shapes: 20, * points: {
* }, * tracks: 19,
* cuboids: { * shapes: 20,
* tracks: 21, * },
* shapes: 22, * cuboids: {
* }, * tracks: 21,
* tags: 66, * shapes: 22,
* manually: 186, * },
* interpolated: 500, * tags: 66,
* total: 608, * manually: 186,
* } * interpolated: 500,
* } * total: 608,
* @name label * }
* @type {Object} * }
* @memberof module:API.cvat.classes.Statistics * @name label
* @readonly * @type {Object}
* @instance * @memberof module:API.cvat.classes.Statistics
*/ * @readonly
label: { * @instance
get: () => JSON.parse(JSON.stringify(label)), */
}, label: {
/** get: () => JSON.parse(JSON.stringify(label)),
* Total statistics (covers all labels) with a structure: },
* @example /**
* { * Total statistics (covers all labels) with a structure:
* boxes: { * @example
* tracks: 10, * {
* shapes: 11, * boxes: {
* }, * tracks: 10,
* polygons: { * shapes: 11,
* tracks: 13, * },
* shapes: 14, * polygons: {
* }, * tracks: 13,
* polylines: { * shapes: 14,
* tracks: 16, * },
* shapes: 17, * polylines: {
* }, * tracks: 16,
* points: { * shapes: 17,
* tracks: 19, * },
* shapes: 20, * points: {
* }, * tracks: 19,
* cuboids: { * shapes: 20,
* tracks: 21, * },
* shapes: 22, * cuboids: {
* }, * tracks: 21,
* tags: 66, * shapes: 22,
* manually: 186, * },
* interpolated: 500, * tags: 66,
* total: 608, * manually: 186,
* } * interpolated: 500,
* @name total * total: 608,
* @type {Object} * }
* @memberof module:API.cvat.classes.Statistics * @name total
* @readonly * @type {Object}
* @instance * @memberof module:API.cvat.classes.Statistics
*/ * @readonly
total: { * @instance
get: () => JSON.parse(JSON.stringify(total)), */
}, total: {
})); get: () => JSON.parse(JSON.stringify(total)),
},
}),
);
} }
} }

@ -1,14 +1,13 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2019 Intel Corporation //
* SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
*/
(() => { (() => {
/** /**
* Class representing a user * Class representing a user
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class User { class User {
constructor(initialData) { constructor(initialData) {
const data = { const data = {
@ -27,134 +26,136 @@
}; };
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(
id: { this,
/** Object.freeze({
* @name id id: {
* @type {integer} /**
* @memberof module:API.cvat.classes.User * @name id
* @readonly * @type {integer}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.id, * @instance
}, */
username: { get: () => data.id,
/** },
* @name username username: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name username
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.username, * @instance
}, */
email: { get: () => data.username,
/** },
* @name email email: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name email
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.email, * @instance
}, */
firstName: { get: () => data.email,
/** },
* @name firstName firstName: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name firstName
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.first_name, * @instance
}, */
lastName: { get: () => data.first_name,
/** },
* @name lastName lastName: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name lastName
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.last_name, * @instance
}, */
groups: { get: () => data.last_name,
/** },
* @name groups groups: {
* @type {string[]} /**
* @memberof module:API.cvat.classes.User * @name groups
* @readonly * @type {string[]}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => JSON.parse(JSON.stringify(data.groups)), * @instance
}, */
lastLogin: { get: () => JSON.parse(JSON.stringify(data.groups)),
/** },
* @name lastLogin lastLogin: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name lastLogin
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.last_login, * @instance
}, */
dateJoined: { get: () => data.last_login,
/** },
* @name dateJoined dateJoined: {
* @type {string} /**
* @memberof module:API.cvat.classes.User * @name dateJoined
* @readonly * @type {string}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.date_joined, * @instance
}, */
isStaff: { get: () => data.date_joined,
/** },
* @name isStaff isStaff: {
* @type {boolean} /**
* @memberof module:API.cvat.classes.User * @name isStaff
* @readonly * @type {boolean}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.is_staff, * @instance
}, */
isSuperuser: { get: () => data.is_staff,
/** },
* @name isSuperuser isSuperuser: {
* @type {boolean} /**
* @memberof module:API.cvat.classes.User * @name isSuperuser
* @readonly * @type {boolean}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.is_superuser, * @instance
}, */
isActive: { get: () => data.is_superuser,
/** },
* @name isActive isActive: {
* @type {boolean} /**
* @memberof module:API.cvat.classes.User * @name isActive
* @readonly * @type {boolean}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => data.is_active, * @instance
}, */
isVerified: { get: () => data.is_active,
/** },
* @name isVerified isVerified: {
* @type {boolean} /**
* @memberof module:API.cvat.classes.User * @name isVerified
* @readonly * @type {boolean}
* @instance * @memberof module:API.cvat.classes.User
*/ * @readonly
get: () => !data.email_verification_required, * @instance
}, */
})); 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(
taskID: 1, window.cvat.jobs.get({
jobID: 1, taskID: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); jobID: 1,
}),
).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(
jobID: '1', window.cvat.jobs.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); jobID: '1',
}),
).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(
taskID: '1', window.cvat.jobs.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); taskID: '1',
}),
).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(
unknown: 50, window.cvat.jobs.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); unknown: 50,
}),
).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(
id: '50', window.cvat.tasks.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); id: '50',
}),
).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(
unknown: '5', window.cvat.tasks.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); unknown: '5',
}),
).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', {
input_type: 'checkbox', default_value: 'false',
mutable: true, input_type: 'checkbox',
name: 'parked', mutable: true,
values: ['false'], name: 'parked',
}], 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",
default_value: 'false', attributes: [
input_type: 'checkbox', {
mutable: true, default_value: 'false',
name: 'parked', input_type: 'checkbox',
values: ['false'], mutable: true,
}], name: 'parked',
}], 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(
unknown: '50', window.cvat.users.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); unknown: '50',
}),
).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(
self: 1, window.cvat.users.get({
})).rejects.toThrow(window.cvat.exceptions.ArgumentError); self: 1,
}),
).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))))) ',
['label==["car `mazda`"]'], ]);
'&', expect(groups).toEqual([
['attr["sunglass ( help ) es"]==true', '|', [
['width > 150', '|', 'height > 150', '&', [
['label==["car `mazda`"]'],
'&',
[ [
'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,47 +129,55 @@ 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
for (const segment of task.segments) { .reduce((acc, task) => {
for (const job of segment.jobs) { for (const segment of task.segments) {
const copy = JSON.parse(JSON.stringify(job)); for (const job of segment.jobs) {
copy.start_frame = segment.start_frame; const copy = JSON.parse(JSON.stringify(job));
copy.stop_frame = segment.stop_frame; copy.start_frame = segment.start_frame;
copy.task_id = task.id; copy.stop_frame = segment.stop_frame;
copy.task_id = task.id;
acc.push(copy);
acc.push(copy);
}
} }
}
return acc; return acc;
}, []).filter(job => job.id === jobID); }, [])
.filter((job) => job.id === jobID);
return jobs[0] || { return (
detail: 'Not found.', jobs[0] || {
}; detail: 'Not found.',
}
);
} }
async function saveJob(id, jobData) { async function saveJob(id, jobData) {
const object = tasksDummyData.results.reduce((acc, task) => { const object = tasksDummyData.results
for (const segment of task.segments) { .reduce((acc, task) => {
for (const job of segment.jobs) { for (const segment of task.segments) {
acc.push(job); for (const job of segment.jobs) {
acc.push(job);
}
} }
}
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,10 +223,13 @@ 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
el.id = ++idGenerator; .concat(data.tags)
return el; .concat(data.shapes)
}); .map((el) => {
el.id = ++idGenerator;
return el;
});
return data; return data;
} }
@ -242,63 +245,66 @@ class ServerProxy {
return null; return null;
} }
Object.defineProperties(this, Object.freeze({ Object.defineProperties(
server: { this,
value: Object.freeze({ Object.freeze({
about, server: {
share, value: Object.freeze({
formats, about,
exception, share,
login, formats,
logout, exception,
}), login,
writable: false, logout,
}, }),
writable: false,
tasks: { },
value: Object.freeze({
getTasks, tasks: {
saveTask, value: Object.freeze({
createTask, getTasks,
deleteTask, saveTask,
}), createTask,
writable: false, deleteTask,
}, }),
writable: false,
jobs: { },
value: Object.freeze({
getJob, jobs: {
saveJob, value: Object.freeze({
}), getJob,
writable: false, saveJob,
}, }),
writable: false,
users: { },
value: Object.freeze({
getUsers, users: {
getSelf, value: Object.freeze({
}), getUsers,
writable: false, getSelf,
}, }),
writable: false,
frames: { },
value: Object.freeze({
getData, frames: {
getMeta, value: Object.freeze({
getPreview, getData,
}), getMeta,
writable: false, getPreview,
}, }),
writable: false,
annotations: { },
value: {
updateAnnotations, annotations: {
getAnnotations, value: {
updateAnnotations,
getAnnotations,
},
// To implement on of important tests
writable: true,
}, },
// To implement on of important tests }),
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?$/, {
exclude: /node_modules/, test: /.js?$/,
}], exclude: /node_modules/,
},
],
}, },
stats: { stats: {
warnings: false, warnings: false,
@ -40,40 +45,46 @@ const webConfig = {
libraryTarget: 'window', libraryTarget: 'window',
}, },
module: { module: {
rules: [{ rules: [
test: /.js?$/, {
exclude: /node_modules/, test: /.js?$/,
use: { exclude: /node_modules/,
loader: 'babel-loader', use: {
options: { loader: 'babel-loader',
presets: [ options: {
['@babel/preset-env', { presets: [
targets: '> 2.5%', [
}], '@babel/preset-env',
], {
sourceType: 'unambiguous', targets: '> 2.5%',
},
],
],
sourceType: 'unambiguous',
},
}, },
}, },
}, { {
test: /3rdparty\/.*\.worker\.js$/, test: /3rdparty\/.*\.worker\.js$/,
use: { use: {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/static/engine/js/3rdparty/', publicPath: '/static/engine/js/3rdparty/',
name: '[name].[contenthash].js', name: '[name].[contenthash].js',
},
}, },
}, },
}, { {
test: /\.worker\.js$/, test: /\.worker\.js$/,
exclude: /3rdparty/, exclude: /3rdparty/,
use: { use: {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/static/engine/js/', publicPath: '/static/engine/js/',
name: '[name].[contenthash].js', name: '[name].[contenthash].js',
},
}, },
}, },
},
], ],
}, },
}; };

@ -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": [ plugins: ['security', 'no-unsanitized', 'no-unsafe-innerhtml'],
"security", extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'],
"no-unsanitized", rules: {
"no-unsafe-innerhtml", 'no-await-in-loop': [0],
], 'global-require': [0],
"extends": [ 'no-new': [0],
"eslint:recommended", 'class-methods-use-this': [0],
"plugin:security/recommended", 'no-restricted-properties': [
"plugin:no-unsanitized/DOM", 0,
"airbnb-base", {
], object: 'Math',
"rules": { property: 'pow',
"no-await-in-loop": [0], },
"global-require": [0], ],
"no-new": [0], 'no-plusplus': [0],
"class-methods-use-this": [0], 'no-param-reassign': [0],
"no-restricted-properties": [0, { 'no-underscore-dangle': ['error', { allowAfterThis: true }],
"object": "Math", 'no-restricted-syntax': [0, { selector: 'ForOfStatement' }],
"property": "pow", 'no-continue': [0],
}], 'no-unsafe-innerhtml/no-unsafe-innerhtml': 1,
"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) {
@ -302,10 +296,10 @@ class FrameProvider {
// but document.createElement doesn't work in worker // but document.createElement doesn't work in worker
// 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);
}); });
img.src = URL.createObjectURL(blob); img.src = URL.createObjectURL(blob);
@ -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,24 +14,27 @@ 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)
if (self.createImageBitmap) { .async('blob')
createImageBitmap(fileData).then((img) => { .then((fileData) => {
// eslint-disable-next-line no-restricted-globals
if (self.createImageBitmap) {
createImageBitmap(fileData).then((img) => {
postMessage({
fileName: relativePath,
index: fileIndex,
data: img,
});
});
} else {
postMessage({ postMessage({
fileName: relativePath, fileName: relativePath,
index: fileIndex, index: fileIndex,
data: img, data: fileData,
isRaw: true,
}); });
}); }
} else { });
postMessage({
fileName: relativePath,
index: fileIndex,
data: fileData,
isRaw: 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');
@ -27,14 +30,18 @@ const cvatData = {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
['@babel/preset-env', { [
targets: '> 2.5%', // https://github.com/browserslist/browserslist '@babel/preset-env',
}], {
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

@ -1,86 +1,82 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.9.14", "version": "1.9.14",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
"build": "webpack --config ./webpack.config.js", "build": "webpack --config ./webpack.config.js",
"start": "REACT_APP_API_URL=http://localhost:7000 webpack-dev-server --config ./webpack.config.js --mode=development", "start": "REACT_APP_API_URL=http://localhost:7000 webpack-dev-server --config ./webpack.config.js --mode=development",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"type-check:watch": "npm run type-check -- --watch", "type-check:watch": "npm run type-check -- --watch",
"lint": "eslint './src/**/*.{ts,tsx}'", "lint": "eslint './src/**/*.{ts,tsx}'",
"lint:fix": "eslint './src/**/*.{ts,tsx}' --fix" "lint:fix": "eslint './src/**/*.{ts,tsx}' --fix"
}, },
"author": "Intel", "author": "Intel",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.6.0", "@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5", "@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-optional-chaining": "^7.11.0", "@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@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", "html-webpack-plugin": "^3.2.0",
"eslint-plugin-react": "^7.17.0", "node-sass": "^4.13.0",
"eslint-plugin-react-hooks": "^1.7.0", "postcss-loader": "^3.0.0",
"html-webpack-plugin": "^3.2.0", "postcss-preset-env": "^6.7.0",
"lint-staged": "^10.4.0", "react-svg-loader": "^3.0.3",
"node-sass": "^4.13.0", "sass-loader": "^8.0.0",
"postcss-loader": "^3.0.0", "style-loader": "^1.0.0",
"postcss-preset-env": "^6.7.0", "tsconfig-paths-webpack-plugin": "^3.2.0",
"react-svg-loader": "^3.0.3", "typescript": "^3.7.3",
"sass-loader": "^8.0.0", "webpack": "^4.44.2",
"style-loader": "^1.0.0", "webpack-cli": "^3.3.8",
"tsconfig-paths-webpack-plugin": "^3.2.0", "webpack-dev-server": "^3.8.0",
"typescript": "^3.7.3", "worker-loader": "^2.0.0"
"webpack": "^4.44.2", },
"webpack-cli": "^3.3.8", "dependencies": {
"webpack-dev-server": "^3.8.0", "@types/platform": "^1.3.3",
"worker-loader": "^2.0.0" "@types/react": "^16.9.50",
}, "@types/react-color": "^3.0.4",
"dependencies": { "@types/react-dom": "^16.9.0",
"@types/platform": "^1.3.3", "@types/react-redux": "^7.1.2",
"@types/react": "^16.9.50", "@types/react-router": "^5.0.5",
"@types/react-color": "^3.0.4", "@types/react-router-dom": "^5.1.0",
"@types/react-dom": "^16.9.0", "@types/react-share": "^3.0.3",
"@types/react-redux": "^7.1.2", "@types/redux-logger": "^3.0.8",
"@types/react-router": "^5.0.5", "antd": "^3.26.18",
"@types/react-router-dom": "^5.1.0", "copy-to-clipboard": "^3.3.1",
"@types/react-share": "^3.0.3", "cvat-canvas": "file:../cvat-canvas",
"@types/redux-logger": "^3.0.8", "cvat-core": "file:../cvat-core",
"antd": "^3.26.18", "dotenv-webpack": "^1.8.0",
"copy-to-clipboard": "^3.3.1", "error-stack-parser": "^2.0.6",
"cvat-canvas": "file:../cvat-canvas", "moment": "^2.29.0",
"cvat-core": "file:../cvat-core", "platform": "^1.3.6",
"dotenv-webpack": "^1.8.0", "prop-types": "^15.7.2",
"error-stack-parser": "^2.0.6", "react": "^16.13.1",
"moment": "^2.29.0", "react-color": "^2.18.1",
"platform": "^1.3.6", "react-cookie": "^4.0.3",
"prop-types": "^15.7.2", "react-dom": "^16.13.1",
"react": "^16.13.1", "react-hotkeys": "^2.0.0",
"react-color": "^2.18.1", "react-redux": "^7.1.1",
"react-cookie": "^4.0.3", "react-router": "^5.1.0",
"react-dom": "^16.13.1", "react-router-dom": "^5.1.0",
"react-hotkeys": "^2.0.0", "react-share": "^3.0.1",
"react-redux": "^7.1.1", "redux": "^4.0.5",
"react-router": "^5.1.0", "redux-devtools-extension": "^2.13.8",
"react-router-dom": "^5.1.0", "redux-logger": "^3.0.6",
"react-share": "^3.0.1", "redux-thunk": "^2.3.0"
"redux": "^4.0.5", }
"redux-devtools-extension": "^2.13.8",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.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(
name: undo[0], LogType.undoAction,
frame: undo[1], {
count: 1, name: undo[0],
}, true); frame: undo[1],
count: 1,
},
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(
name: redo[0], LogType.redoAction,
frame: redo[1], {
count: 1, name: redo[0],
}, true); frame: redo[1],
count: 1,
},
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,15 +23,16 @@ export const boundariesActions = {
minZ: number, minZ: number,
maxZ: number, maxZ: number,
colors: string[], colors: string[],
) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, { ) =>
job, createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
states, job,
frameNumber, states,
frameData, frameNumber,
minZ, frameData,
maxZ, minZ,
colors, maxZ,
}), colors,
}),
throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR), throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR),
}; };
@ -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,43 +79,45 @@ 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
if (status === RQStatus.failed || status === RQStatus.unknown) { .listen(requestID, (status: RQStatus, progress: number, message: string) => {
dispatch(modelsActions.getInferenceStatusFailed( if (status === RQStatus.failed || status === RQStatus.unknown) {
taskID, dispatch(
new Error( modelsActions.getInferenceStatusFailed(
`Inference status for the task ${taskID} is ${status}. ${message}`, taskID,
), new Error(`Inference status for the task ${taskID} is ${status}. ${message}`),
)); ),
);
return;
} return;
}
dispatch(modelsActions.getInferenceStatusSuccess(taskID, {
status, dispatch(
progress, modelsActions.getInferenceStatusSuccess(taskID, {
error: message, status,
id: requestID, progress,
})); error: message,
}).catch((error: Error) => { id: requestID,
dispatch(modelsActions.getInferenceStatusFailed(taskID, { }),
status: 'unknown', );
progress: 0, })
error: error.toString(), .catch((error: Error) => {
id: requestID, dispatch(
})); modelsActions.getInferenceStatusFailed(taskID, {
}); status: 'unknown',
progress: 0,
error: error.toString(),
id: requestID,
}),
);
});
} }
export function getInferenceStatusAsync(): ThunkAction { export function getInferenceStatusAsync(): ThunkAction {
@ -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, {
requestID, taskID: taskInstance.id,
}, dispatchCallback); requestID,
},
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',

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

Loading…
Cancel
Save