Reformatted (#2349)

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

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

@ -1,53 +1,23 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
module.exports = {
"env": {
"node": false,
"browser": true,
"es6": true,
"jquery": true,
"qunit": true,
module.exports = {
env: {
node: false,
browser: true,
es6: true,
jquery: true,
qunit: true,
'jest/globals': true,
'cypress/globals': true,
},
"parserOptions": {
"sourceType": "script",
parserOptions: {
sourceType: 'script',
},
"plugins": [
"security",
"no-unsanitized",
"no-unsafe-innerhtml",
],
"extends": [
"eslint:recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"airbnb-base",
],
"rules": {
"no-await-in-loop": [0],
"global-require": [0],
"no-new": [0],
"class-methods-use-this": [0],
"no-restricted-properties": [0, {
"object": "Math",
"property": "pow",
}],
"no-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],
plugins: ['requirejs', 'jest', 'cypress', 'eslint-plugin-header'],
extends: ['eslint:recommended', 'plugin:requirejs/recommended', 'prettier'],
rules: {
'header/header': [2, '.header.js'],
},
};

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

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

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

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

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

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

@ -9,12 +9,15 @@ they should reciprocate that respect in addressing your issue or assessing
patches and features.
## Development environment
- Install necessary dependencies:
Ubuntu 18.04
```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 -
@ -22,16 +25,18 @@ patches and features.
```
MacOS 10.15
```sh
brew install git python pyenv redis curl openssl node
```
- Install FFmpeg libraries (libav*) version 4.0 or higher.
- Install FFmpeg libraries (libav\*) version 4.0 or higher.
- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions)
for development
for development
- Install CVAT on your local host:
```sh
git clone https://github.com/opencv/cvat
cd cvat && mkdir logs keys
@ -42,13 +47,15 @@ for development
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
> `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'): ***
@ -58,25 +65,29 @@ for development
```
- Install npm packages for UI and start UI debug server (run the following command from CVAT root directory):
```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)```
> `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)
@ -100,25 +111,27 @@ You develop CVAT under WSL (Windows subsystem for Linux) following next steps.
- Following this guide install Ubuntu 18.04 Linux distribution for WSL.
- Run Ubuntu using start menu link or execute next command
```powershell
wsl -d Ubuntu-18.04
```
- Run all commands from this isntallation guide in WSL Ubuntu shell.
## Setup additional components in development environment
### DL models as serverless functions
Install [nuclio platform](https://github.com/nuclio/nuclio):
- You have to install `nuctl` command line tool to build and deploy serverless
functions. Download [the latest release](
https://github.com/nuclio/nuclio/blob/development/docs/reference/nuctl/nuctl.md#download).
functions. Download [the latest release](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)
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)
for more details.
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)
for more details.
- 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
nuctl create project cvat
@ -181,7 +194,7 @@ nuctl deploy --project-name cvat \
</details>
- Display a list of running serverless functions using `nuctl` command or see them
in nuclio dashboard:
in nuclio dashboard:
```bash
nuctl get function
@ -198,7 +211,7 @@ nuctl get function
</details>
- 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
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>
### Run Cypress tests
- Install Сypress as described in the [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress.html).
@ -259,11 +273,11 @@ The project uses [a successful Git branching model](https://nvie.com/posts/a-suc
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
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
HEAD always reflects a state with the latest delivered development
changes for the next release. Some would call this the “integration branch”.
HEAD always reflects a state with the latest delivered development
changes for the next release. Some would call this the “integration branch”.
## Using the issue tracker
@ -278,6 +292,7 @@ requests](#pull-requests), but please respect the following restrictions:
respect the opinions of others.
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
@ -316,6 +331,7 @@ Example:
> merits).
<a name="features"></a>
## Feature requests
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.
<a name="pull-requests"></a>
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic

@ -47,7 +47,7 @@ framework allows additional dataset transformations
via its command line tool and Python library.
| Annotation format | Import | Export |
| ------------------------------------------------------------------------------------------ | ------ | ------ |
| ----------------------------------------------------------------------------- | ------ | ------ |
| [CVAT for images](cvat/apps/documentation/xml_format.md#annotation) | X | X |
| [CVAT for a video](cvat/apps/documentation/xml_format.md#interpolation) | X | X |
| [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X |
@ -79,19 +79,21 @@ Try it online without local installation. Only own or assigned tasks
are visible to users.
Disabled features:
- [Analytics: management and monitoring of data annotation team](/components/analytics/README.md)
Limitations:
- No more than 10 tasks per user
- Uploaded data is limited to 500Mb
## REST API
Automatically generated Swagger documentation for Django REST API is
available on ``<cvat_origin>/api/swagger``
(default: ``localhost:8080/api/swagger``).
available on `<cvat_origin>/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
@ -105,16 +107,18 @@ contributors and other users.
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
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,
[Gitter chat](https://gitter.im/opencv-cvat) is the way to go.
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
- [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)
- [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:
### Build docker image
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml build
```
### Run docker container
```bash
# From project root directory
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.
## Time picker default
{
"from": "now/d",
"to": "now/d",
"display": "Today",
"section": 0
"from": "now/d",
"to": "now/d",
"display": "Today",
"section": 0
}
## Time picker quick ranges

@ -35,9 +35,24 @@ services:
volumes: ['./components/analytics/kibana:/home/django/kibana:ro']
depends_on: ['cvat']
working_dir: '/home/django'
entrypoint: ['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']
entrypoint:
[
'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:
no_proxy: elasticsearch,kibana,${no_proxy}

@ -1,3 +1,3 @@
http.host: 0.0.0.0
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",
"_source": {
"hits": 0,
"sort": [
"@timestamp",
"desc"
],
"sort": ["@timestamp", "desc"],
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"index\":\"ec510550-c238-11e8-8e1b-758ef07f6de8\",\"highlightAll\":true,\"version\":true,\"query\":{\"language\":\"lucene\",\"query\":\"event:\\\"Send exception\\\"\"},\"filter\":[]}"
},
"columns": [
"task",
"type",
"userid",
"stack"
],
"columns": ["task", "type", "userid", "stack"],
"description": "",
"title": "Table with exceptions",
"version": 1

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

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

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

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

@ -1,18 +1,21 @@
# Module CVAT-CANVAS
## Description
The CVAT module written in TypeScript language.
It presents a canvas to viewing, drawing and editing of annotations.
## Versioning
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 changing API (backward compatible new features) do: ``npm version minor``
- After changing API (changes that break backward compatibility) do: ``npm version major``
- 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 (changes that break backward compatibility) do: `npm version major`
## Commands
- Building of the module from sources in the ```dist``` directory:
- Building of the module from sources in the `dist` directory:
```bash
npm run build
@ -22,6 +25,7 @@ npm run build -- --mode=development # without a minification
## Using
Canvas itself handles:
- Shape context menu (PKM)
- Image moving (mousedrag)
- Image resizing (mousewheel)
@ -136,22 +140,23 @@ Canvas itself handles:
### API CSS
- All drawn objects (shapes, tracks) have an id ```cvat_canvas_shape_{objectState.clientID}```
- Drawn shapes and tracks have classes ```cvat_canvas_shape```,
```cvat_canvas_shape_activated```,
```cvat_canvas_shape_grouping```,
```cvat_canvas_shape_merging```,
```cvat_canvas_shape_drawing```,
```cvat_canvas_shape_occluded```
- Drawn texts have the class ```cvat_canvas_text```
- Tags have the class ```cvat_canvas_tag```
- Canvas image has ID ```cvat_canvas_image```
- Grid on the canvas has ID ```cvat_canvas_grid``` and ```cvat_canvas_grid_pattern```
- Crosshair during a draw has class ```cvat_canvas_crosshair```
- All drawn objects (shapes, tracks) have an id `cvat_canvas_shape_{objectState.clientID}`
- Drawn shapes and tracks have classes `cvat_canvas_shape`,
`cvat_canvas_shape_activated`,
`cvat_canvas_shape_grouping`,
`cvat_canvas_shape_merging`,
`cvat_canvas_shape_drawing`,
`cvat_canvas_shape_occluded`
- Drawn texts have the class `cvat_canvas_text`
- Tags have the class `cvat_canvas_tag`
- Canvas image has ID `cvat_canvas_image`
- Grid on the canvas has ID `cvat_canvas_grid` and `cvat_canvas_grid_pattern`
- Crosshair during a draw has class `cvat_canvas_crosshair`
### Events
Standard JS events are used.
```js
- canvas.setup
- canvas.activated => {state: ObjectState}
@ -178,31 +183,32 @@ Standard JS events are used.
```
### WEB
```js
// Create an instance of a canvas
const canvas = new window.canvas.Canvas();
// Create an instance of a canvas
const canvas = new window.canvas.Canvas();
console.log('Version ', window.canvas.CanvasVersion);
console.log('Current mode is ', window.canvas.mode());
console.log('Version ', window.canvas.CanvasVersion);
console.log('Current mode is ', window.canvas.mode());
// Put canvas to a html container
htmlContainer.appendChild(canvas.html());
canvas.fitCanvas();
// Put canvas to a html container
htmlContainer.appendChild(canvas.html());
canvas.fitCanvas();
// Next you can use its API methods. For example:
canvas.rotate(270);
canvas.draw({
// Next you can use its API methods. For example:
canvas.rotate(270);
canvas.draw({
enabled: true,
shapeType: 'rectangle',
crosshair: true,
rectDrawingMethod: window.Canvas.RectDrawingMethod.CLASSIC,
});
});
```
## API Reaction
| | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS | INTERACT |
|--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------|----------|
| ------------ | ---- | ----- | ----- | ---- | ----- | ---- | ---- | ------ | ----------- | ----------- | -------- |
| setup() | + | + | + | +/- | + | +/- | +/- | +/- | + | + | + |
| activate() | + | - | - | - | - | - | - | - | - | - | - |
| rotate() | + | + | + | + | + | + | + | + | + | + | + |

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

@ -41,7 +41,7 @@ polyline.cvat_shape_drawing_opacity {
font-size: 1.2em;
fill: white;
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;
user-select: none;
pointer-events: none;

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

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

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

@ -25,13 +25,19 @@ export default class Crosshair {
}
this.canvas = canvas;
this.x = this.canvas.line(0, y, this.canvas.node.clientWidth, y).attr({
this.x = this.canvas
.line(0, y, this.canvas.node.clientWidth, y)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
}).addClass('cvat_canvas_crosshair');
})
.addClass('cvat_canvas_crosshair');
this.y = this.canvas.line(x, 0, x, this.canvas.node.clientHeight).attr({
this.y = this.canvas
.line(x, 0, x, this.canvas.node.clientHeight)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (2 * scale),
}).addClass('cvat_canvas_crosshair');
})
.addClass('cvat_canvas_crosshair');
}
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';
export interface Point {
@ -26,9 +17,7 @@ function line(p1: Point, p2: Point): number[] {
return [a, b, c];
}
function intersection(
p1: Point, p2: Point, p3: Point, p4: Point,
): Point | null {
function intersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null {
const L1 = line(p1, p2);
const L2 = line(p3, p4);
@ -246,8 +235,20 @@ export class CuboidModel {
this.rb = new Edge([3, 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.rt, this.dt, this.fb, this.lb, this.rb, this.db];
this.edgeList = [
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 {
@ -347,8 +348,8 @@ function setupCuboidPoints(points: Point[]): any[] {
let p3;
let p4;
const height = Math.abs(points[0].x - points[1].x)
< Math.abs(points[1].x - points[2].x)
const height =
Math.abs(points[0].x - points[1].x) < Math.abs(points[1].x - points[2].x)
? Math.abs(points[1].y - points[0].y)
: Math.abs(points[1].y - points[2].y);
@ -356,9 +357,9 @@ function setupCuboidPoints(points: Point[]): any[] {
// we pick the first and third point because we know assume they will be on
// opposite corners
if (points[0].x < points[2].x) {
[left,, right] = points;
[left, , right] = points;
} else {
[right,, left] = points;
[right, , left] = points;
}
// get other 2 points using the given height
@ -408,7 +409,7 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
points.push({ x, y });
}
const unsortedPlanePoints = points.slice(0, 3);
function rotate(array: any[], times: number): void{
function rotate(array: any[], times: number): void {
let t = times;
while (t--) {
const temp = array.shift();
@ -460,7 +461,6 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y };
plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y };
let cuboidPoints;
// right
if (Math.abs(angle) < Math.PI / 2 - 0.1) {
@ -470,18 +470,12 @@ export function cuboidFrom4Points(flattenedPoints: any[]): any[] {
cuboidPoints = setupCuboidPoints(points);
// down
} else if (angle > 0) {
cuboidPoints = [
plane1.p1, plane2.p1, plane1.p2, plane2.p2,
plane1.p3, plane2.p3, plane1.p4, plane2.p4,
];
cuboidPoints = [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3, plane1.p4, plane2.p4];
cuboidPoints[0].y += 0.1;
cuboidPoints[4].y += 0.1;
// up
} else {
cuboidPoints = [
plane2.p1, plane1.p1, plane2.p2, plane1.p2,
plane2.p3, plane1.p3, plane2.p4, plane1.p4,
];
cuboidPoints = [plane2.p1, plane1.p1, plane2.p2, plane1.p2, plane2.p3, plane1.p3, plane2.p4, plane1.p4];
cuboidPoints[0].y += 0.1;
cuboidPoints[4].y += 0.1;
}

@ -18,13 +18,7 @@ import {
} from './shared';
import Crosshair from './crosshair';
import consts from './consts';
import {
DrawData,
Geometry,
RectDrawingMethod,
Configuration,
CuboidDrawingMethod,
} from './canvasModel';
import { DrawData, Geometry, RectDrawingMethod, Configuration, CuboidDrawingMethod } from './canvasModel';
import { cuboidFrom4Points } from './cuboid';
@ -64,8 +58,9 @@ export class DrawHandlerImpl implements DrawHandler {
const frameHeight = this.geometry.image.height;
const { offset } = this.geometry;
let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height]
.map((coord: number): number => coord - offset);
let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height].map(
(coord: number): number => coord - offset,
);
xtl = Math.min(Math.max(xtl, 0), frameWidth);
xbr = Math.min(Math.max(xbr, 0), frameWidth);
@ -75,7 +70,9 @@ export class DrawHandlerImpl implements DrawHandler {
return [xtl, ytl, xbr, ybr];
}
private getFinalPolyshapeCoordinates(targetPoints: number[]): {
private getFinalPolyshapeCoordinates(
targetPoints: number[],
): {
points: number[];
box: Box;
} {
@ -106,7 +103,9 @@ export class DrawHandlerImpl implements DrawHandler {
};
}
private getFinalCuboidCoordinates(targetPoints: number[]): {
private getFinalCuboidCoordinates(
targetPoints: number[],
): {
points: number[];
box: Box;
} {
@ -133,8 +132,7 @@ export class DrawHandlerImpl implements DrawHandler {
for (let i = 0; i < points.length - 1; i += 2) {
const [x, y] = points.slice(i);
if (x >= offset && x <= offset + frameWidth
&& y >= offset && y <= offset + frameHeight) continue;
if (x >= offset && x <= offset + frameWidth && y >= offset && y <= offset + frameHeight) continue;
let xOffset = 0;
let yOffset = 0;
@ -156,9 +154,8 @@ export class DrawHandlerImpl implements DrawHandler {
if (cuboidOffsets.length === points.length / 2) {
cuboidOffsets.forEach((offsetCoords: number[]): void => {
if (Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2))
< minCuboidOffset.d) {
minCuboidOffset.d = Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2));
if (Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2) < minCuboidOffset.d) {
minCuboidOffset.d = Math.sqrt(offsetCoords[0] ** 2 + offsetCoords[1] ** 2);
[minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords;
}
});
@ -215,8 +212,10 @@ export class DrawHandlerImpl implements DrawHandler {
// Or when no drawn points, but we call cancel() drawing
// We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) {
if (this.drawData.shapeType !== 'rectangle'
&& this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC) {
if (
this.drawData.shapeType !== 'rectangle' &&
this.drawData.cuboidDrawingMethod !== CuboidDrawingMethod.CLASSIC
) {
// Check for unsaved drawn shapes
this.drawInstance.draw('done');
}
@ -248,7 +247,8 @@ export class DrawHandlerImpl implements DrawHandler {
private drawBox(): void {
this.drawInstance = this.canvas.rect();
this.drawInstance.on('drawstop', (e: Event): void => {
this.drawInstance
.on('drawstop', (e: Event): void => {
const bbox = (e.target as SVGRectElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType, redraw: clientID } = this.drawData;
@ -256,29 +256,39 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({
this.onDrawDone(
{
clientID,
shapeType,
points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp);
},
Date.now() - this.startTimestamp,
);
}
}).on('drawupdate', (): void => {
})
.on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance);
}).addClass('cvat_canvas_shape_drawing').attr({
})
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
}
private drawBoxBy4Points(): void {
let numberOfPoints = 0;
this.drawInstance = (this.canvas as any).polygon()
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polygon()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': 0,
opacity: 0,
}).on('drawstart', (): void => {
})
.on('drawstart', (): void => {
// init numberOfPoints as one on drawstart
numberOfPoints = 1;
}).on('drawpoint', (e: CustomEvent): void => {
})
.on('drawpoint', (e: CustomEvent): void => {
// increase numberOfPoints by one on drawpoint
numberOfPoints += 1;
@ -290,14 +300,18 @@ export class DrawHandlerImpl implements DrawHandler {
this.cancel();
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({
this.onDrawDone(
{
shapeType,
clientID,
points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp);
},
Date.now() - this.startTimestamp,
);
}
}
}).on('undopoint', (): void => {
})
.on('undopoint', (): void => {
if (numberOfPoints > 0) {
numberOfPoints -= 1;
}
@ -351,10 +365,7 @@ export class DrawHandlerImpl implements DrawHandler {
} else {
this.drawInstance.draw('update', e);
const deltaTreshold = 15;
const delta = Math.sqrt(
((e.clientX - lastDrawnPoint.x) ** 2)
+ ((e.clientY - lastDrawnPoint.y) ** 2),
);
const delta = Math.sqrt((e.clientX - lastDrawnPoint.x) ** 2 + (e.clientY - lastDrawnPoint.y) ** 2);
if (delta > deltaTreshold) {
this.drawInstance.draw('point', e);
}
@ -375,50 +386,67 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points'));
const { shapeType, redraw: clientID } = this.drawData;
const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints)
const { points, box } =
shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
this.release();
if (this.canceled) return;
if (shapeType === 'polygon'
&& ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD)
&& points.length >= 3 * 2) {
this.onDrawDone({
if (
shapeType === 'polygon' &&
(box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD &&
points.length >= 3 * 2
) {
this.onDrawDone(
{
clientID,
shapeType,
points,
}, Date.now() - this.startTimestamp);
} else if (shapeType === 'polyline'
&& ((box.xbr - box.xtl) >= consts.SIZE_THRESHOLD
|| (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD)
&& points.length >= 2 * 2) {
this.onDrawDone({
},
Date.now() - this.startTimestamp,
);
} else if (
shapeType === 'polyline' &&
(box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) &&
points.length >= 2 * 2
) {
this.onDrawDone(
{
clientID,
shapeType,
points,
}, Date.now() - this.startTimestamp);
} else if (shapeType === 'points'
&& (e.target as any).getAttribute('points') !== '0,0') {
this.onDrawDone({
},
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);
},
Date.now() - this.startTimestamp,
);
// TODO: think about correct constraign for cuboids
} else if (shapeType === 'cuboid'
&& points.length === 4 * 2) {
this.onDrawDone({
} else if (shapeType === 'cuboid' && points.length === 4 * 2) {
this.onDrawDone(
{
clientID,
shapeType,
points: cuboidFrom4Points(points),
}, Date.now() - this.startTimestamp);
},
Date.now() - this.startTimestamp,
);
}
});
}
private drawPolygon(): void {
this.drawInstance = (this.canvas as any).polygon()
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polygon()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
@ -429,8 +457,10 @@ export class DrawHandlerImpl implements DrawHandler {
}
private drawPolyline(): void {
this.drawInstance = (this.canvas as any).polyline()
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polyline()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': 0,
});
@ -442,8 +472,7 @@ export class DrawHandlerImpl implements DrawHandler {
}
private drawPoints(): void {
this.drawInstance = (this.canvas as any).polygon()
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any).polygon().addClass('cvat_canvas_shape_drawing').attr({
'stroke-width': 0,
opacity: 0,
});
@ -452,8 +481,10 @@ export class DrawHandlerImpl implements DrawHandler {
}
private drawCuboidBy4Points(): void {
this.drawInstance = (this.canvas as any).polyline()
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polyline()
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
this.drawPolyshape();
@ -461,7 +492,8 @@ export class DrawHandlerImpl implements DrawHandler {
private drawCuboid(): void {
this.drawInstance = this.canvas.rect();
this.drawInstance.on('drawstop', (e: Event): void => {
this.drawInstance
.on('drawstop', (e: Event): void => {
const bbox = (e.target as SVGRectElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType } = this.drawData;
@ -470,14 +502,20 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
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]),
}, Date.now() - this.startTimestamp);
},
Date.now() - this.startTimestamp,
);
}
}).on('drawupdate', (): void => {
})
.on('drawupdate', (): void => {
this.shapeSizeElement.update(this.drawInstance);
}).addClass('cvat_canvas_shape_drawing').attr({
})
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
}
@ -489,14 +527,17 @@ export class DrawHandlerImpl implements DrawHandler {
.split(/[,\s]/g)
.map((coord: string): number => +coord);
const { points } = this.drawData.initialState.shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints)
const { points } =
this.drawData.initialState.shapeType === 'cuboid'
? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints);
if (!e.detail.originalEvent.ctrlKey) {
this.release();
}
this.onDrawDone({
this.onDrawDone(
{
shapeType: this.drawData.initialState.shapeType,
objectType: this.drawData.initialState.objectType,
points,
@ -504,7 +545,10 @@ export class DrawHandlerImpl implements DrawHandler {
attributes: { ...this.drawData.initialState.attributes },
label: this.drawData.initialState.label,
color: this.drawData.initialState.color,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey);
},
Date.now() - this.startTimestamp,
e.detail.originalEvent.ctrlKey,
);
});
}
@ -525,9 +569,11 @@ export class DrawHandlerImpl implements DrawHandler {
}
private pasteBox(box: BBox): void {
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)
.addClass('cvat_canvas_shape_drawing').attr({
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
this.pasteShape();
@ -539,7 +585,8 @@ export class DrawHandlerImpl implements DrawHandler {
this.release();
}
this.onDrawDone({
this.onDrawDone(
{
shapeType: this.drawData.initialState.shapeType,
objectType: this.drawData.initialState.objectType,
points: [xtl, ytl, xbr, ybr],
@ -547,14 +594,18 @@ export class DrawHandlerImpl implements DrawHandler {
attributes: { ...this.drawData.initialState.attributes },
label: this.drawData.initialState.label,
color: this.drawData.initialState.color,
}, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey);
},
Date.now() - this.startTimestamp,
e.detail.originalEvent.ctrlKey,
);
});
}
private pastePolygon(points: string): void {
this.drawInstance = (this.canvas as any).polygon(points)
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polygon(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
this.pasteShape();
@ -562,8 +613,10 @@ export class DrawHandlerImpl implements DrawHandler {
}
private pastePolyline(points: string): void {
this.drawInstance = (this.canvas as any).polyline(points)
.addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.polyline(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
this.pasteShape();
@ -571,7 +624,10 @@ export class DrawHandlerImpl implements DrawHandler {
}
private pasteCuboid(points: string): void {
this.drawInstance = (this.canvas as any).cube(points).addClass('cvat_canvas_shape_drawing').attr({
this.drawInstance = (this.canvas as any)
.cube(points)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'face-stroke': 'black',
});
@ -580,13 +636,7 @@ export class DrawHandlerImpl implements DrawHandler {
}
private pastePoints(initialPoints: string): void {
function moveShape(
shape: SVG.PolyLine,
group: SVG.G,
x: number,
y: number,
scale: number,
): void {
function moveShape(shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void {
const bbox = shape.bbox();
shape.move(x - bbox.width / 2, y - bbox.height / 2);
@ -601,8 +651,7 @@ export class DrawHandlerImpl implements DrawHandler {
const { x: initialX, y: initialY } = this.cursorPosition;
this.pointsGroup = this.canvas.group();
this.drawInstance = (this.canvas as any).polyline(initialPoints)
.addClass('cvat_canvas_shape_drawing').style({
this.drawInstance = (this.canvas as any).polyline(initialPoints).addClass('cvat_canvas_shape_drawing').style({
'stroke-width': 0,
});
@ -617,15 +666,11 @@ export class DrawHandlerImpl implements DrawHandler {
});
}
moveShape(
this.drawInstance, this.pointsGroup, initialX, initialY, this.geometry.scale,
);
moveShape(this.drawInstance, this.pointsGroup, initialX, initialY, this.geometry.scale);
this.canvas.on('mousemove.draw', (): void => {
const { x, y } = this.cursorPosition; // was computer in another callback
moveShape(
this.drawInstance, this.pointsGroup, x, y, this.geometry.scale,
);
moveShape(this.drawInstance, this.pointsGroup, x, y, this.geometry.scale);
});
this.pastePolyshape();
@ -659,8 +704,9 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.drawData.initialState) {
const { offset } = this.geometry;
if (this.drawData.shapeType === 'rectangle') {
const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points
.map((coord: number): number => coord + offset);
const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points.map(
(coord: number): number => coord + offset,
);
this.pasteBox({
x: xtl,
@ -669,8 +715,7 @@ export class DrawHandlerImpl implements DrawHandler {
height: ybr - ytl,
});
} else {
const points = this.drawData.initialState.points
.map((coord: number): number => coord + offset);
const points = this.drawData.initialState.points.map((coord: number): number => coord + offset);
const stringifiedPoints = stringifyPoints(points);
if (this.drawData.shapeType === 'polygon') {
@ -741,10 +786,7 @@ export class DrawHandlerImpl implements DrawHandler {
};
this.canvas.on('mousemove.crosshair', (e: MouseEvent): void => {
const [x, y] = translateToSVG(
this.canvas.node as any as SVGSVGElement,
[e.clientX, e.clientY],
);
const [x, y] = translateToSVG((this.canvas.node as any) as SVGSVGElement, [e.clientX, e.clientY]);
this.cursorPosition = { x, y };
if (this.crosshair) {
this.crosshair.move(x, y);
@ -753,15 +795,11 @@ export class DrawHandlerImpl implements DrawHandler {
}
public configurate(configuration: Configuration): void {
if (typeof (configuration.autoborders) === 'boolean') {
if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders;
if (this.drawInstance) {
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(
true,
this.drawInstance,
this.drawData.redraw,
);
this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
} else {
this.autoborderHandler.autoborder(false);
}
@ -798,14 +836,8 @@ export class DrawHandlerImpl implements DrawHandler {
const paintHandler = this.drawInstance.remember('_paintHandler');
for (const point of (paintHandler as any).set.members) {
point.attr(
'stroke-width',
`${consts.POINTS_STROKE_WIDTH / geometry.scale}`,
);
point.attr(
'r',
`${consts.BASE_POINT_SIZE / geometry.scale}`,
);
point.attr('stroke-width', `${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;
const { offset } = this.geometry;
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);
if (points.length >= minimumPoints * 2) {
@ -63,7 +64,7 @@ export class EditHandlerImpl implements EditHandler {
private startEdit(): void {
// get started coordinates
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(','),
);
@ -92,10 +93,7 @@ export class EditHandlerImpl implements EditHandler {
(this.editLine as any).draw('point', e);
} else {
const deltaTreshold = 15;
const delta = Math.sqrt(
((e.clientX - lastDrawnPoint.x) ** 2)
+ ((e.clientY - lastDrawnPoint.y) ** 2),
);
const delta = Math.sqrt((e.clientX - lastDrawnPoint.x) ** 2 + (e.clientY - lastDrawnPoint.y) ** 2);
if (delta > deltaTreshold) {
(this.editLine as any).draw('point', e);
}
@ -112,17 +110,22 @@ export class EditHandlerImpl implements EditHandler {
}
const strokeColor = this.editedShape.attr('stroke');
(this.editLine as any).addClass('cvat_canvas_shape_drawing').style({
(this.editLine as any)
.addClass('cvat_canvas_shape_drawing')
.style({
'pointer-events': 'none',
'fill-opacity': 0,
stroke: strokeColor,
}).attr({
})
.attr({
'data-origin-client-id': this.editData.state.clientID,
}).on('drawstart drawpoint', (e: CustomEvent): void => {
})
.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))
})
.on('drawupdate', (): void => this.transform(this.geometry))
.draw(dummyEvent, { snapToGrid: 0.1 });
if (this.editData.state.shapeType === 'points') {
@ -141,9 +144,7 @@ export class EditHandlerImpl implements EditHandler {
if (e.button === 0 && !e.altKey) {
(this.editLine as any).draw('point', e);
} else if (e.button === 2 && this.editLine) {
if (this.editData.state.shapeType === 'points'
|| this.editLine.attr('points').split(' ').length > 2
) {
if (this.editData.state.shapeType === 'points' || this.editLine.attr('points').split(' ').length > 2) {
(this.editLine as any).draw('undo');
}
}
@ -152,8 +153,7 @@ export class EditHandlerImpl implements EditHandler {
private selectPolygon(shape: SVG.Polygon): void {
const { offset } = this.geometry;
const points = pointsToNumberArray(shape.attr('points'))
.map((coord: number): number => coord - offset);
const points = pointsToNumberArray(shape.attr('points')).map((coord: number): number => coord - offset);
const { state } = this.editData;
this.edit({
@ -168,8 +168,7 @@ export class EditHandlerImpl implements EditHandler {
}
// Get stop point and all points
const stopPointID = Array.prototype.indexOf
.call((e.target as HTMLElement).parentElement.children, e.target);
const stopPointID = Array.prototype.indexOf.call((e.target as HTMLElement).parentElement.children, e.target);
const oldPoints = this.editedShape.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
const [start, stop] = [this.editData.pointID, stopPointID]
.sort((a, b): number => +a - +b);
const [start, stop] = [this.editData.pointID, stopPointID].sort((a, b): number => +a - +b);
if (this.editData.state.shapeType !== 'polygon') {
let points = null;
@ -190,15 +188,15 @@ export class EditHandlerImpl implements EditHandler {
if (start !== this.editData.pointID) {
linePoints.reverse();
}
points = oldPoints.slice(0, start)
points = oldPoints
.slice(0, start)
.concat(linePoints)
.concat(oldPoints.slice(stop + 1));
} else {
points = oldPoints.concat(linePoints.slice(0, -1));
}
points = pointsToNumberArray(points.join(' '))
.map((coord: number): number => coord - offset);
points = pointsToNumberArray(points.join(' ')).map((coord: number): number => coord - offset);
const { state } = this.editData;
this.edit({
@ -209,21 +207,23 @@ export class EditHandlerImpl implements EditHandler {
return;
}
const cutIndexes1 = 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 cutIndexes1 = 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 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[]): number[] => [+point[0], +point[1]]);
let length = 0;
for (let i = 1; i < points.length; i++) {
length += Math.sqrt(
((points[i][0] - points[i - 1][0]) ** 2)
+ ((points[i][1] - points[i - 1][1]) ** 2),
);
length += Math.sqrt((points[i][0] - points[i - 1][0]) ** 2 + (points[i][1] - points[i - 1][1]) ** 2);
}
return length;
@ -236,11 +236,11 @@ export class EditHandlerImpl implements EditHandler {
linePoints.reverse();
}
const firstPart = oldPoints.slice(0, start)
const firstPart = oldPoints
.slice(0, start)
.concat(linePoints)
.concat(oldPoints.slice(stop + 1));
const secondPart = oldPoints.slice(start, stop)
.concat(linePoints.slice(1).reverse());
const secondPart = oldPoints.slice(start, stop).concat(linePoints.slice(1).reverse());
if (firstPart.length < 3 || secondPart.length < 3) {
this.cancel();
@ -264,17 +264,22 @@ export class EditHandlerImpl implements EditHandler {
this.selectPolygon(this.clones[0]);
} else {
for (const points of [firstPart, secondPart]) {
this.clones.push(this.canvas.polygon(points.join(' '))
this.clones.push(
this.canvas
.polygon(points.join(' '))
.attr('fill', this.editedShape.attr('fill'))
.attr('fill-opacity', '0.5')
.addClass('cvat_canvas_shape'));
.addClass('cvat_canvas_shape'),
);
}
for (const clone of this.clones) {
clone.on('click', (): void => this.selectPolygon(clone));
clone.on('mouseenter', (): void => {
clone
.on('mouseenter', (): void => {
clone.addClass('cvat_canvas_shape_splitting');
}).on('mouseleave', (): void => {
})
.on('mouseleave', (): void => {
clone.removeClass('cvat_canvas_shape_splitting');
});
}
@ -288,7 +293,7 @@ export class EditHandlerImpl implements EditHandler {
if (enabled) {
(this.editedShape as any).selectize(true, {
deepSelect: true,
pointSize: 2 * consts.BASE_POINT_SIZE / self.geometry.scale,
pointSize: (2 * consts.BASE_POINT_SIZE) / self.geometry.scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
@ -354,9 +359,7 @@ export class EditHandlerImpl implements EditHandler {
}
private initEditing(): void {
this.editedShape = this.canvas
.select(`#cvat_canvas_shape_${this.editData.state.clientID}`)
.first().clone();
this.editedShape = this.canvas.select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first().clone();
this.setupPoints(true);
this.startEdit();
// 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 {
if (typeof (configuration.autoborders) === 'boolean') {
if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders;
if (this.editLine) {
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(
true,
this.editLine,
this.editData.state.clientID,
);
this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID);
} else {
this.autoborderHandler.autoborder(false);
}
@ -442,14 +441,8 @@ export class EditHandlerImpl implements EditHandler {
const paintHandler = this.editLine.remember('_paintHandler');
for (const point of (paintHandler as any).set.members) {
point.attr(
'stroke-width',
`${consts.POINTS_STROKE_WIDTH / geometry.scale}`,
);
point.attr(
'r',
`${consts.BASE_POINT_SIZE / geometry.scale}`,
);
point.attr('stroke-width', `${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 { GroupData } from './canvasModel';
import {
translateToSVG,
} from './shared';
import { translateToSVG } from './shared';
export interface GroupHandler {
group(groupData: GroupData): void;
@ -35,16 +32,15 @@ export class GroupHandlerImpl implements GroupHandler {
private statesToBeGroupped: any[];
private highlightedShapes: Record<number, SVG.Shape>;
private getSelectionBox(event: MouseEvent): {
private getSelectionBox(
event: MouseEvent,
): {
xtl: number;
ytl: number;
xbr: number;
ybr: number;
} {
const point = translateToSVG(
(this.canvas.node as any as SVGSVGElement),
[event.clientX, event.clientY],
);
const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
const stopSelectionPoint = {
x: point[0],
y: point[1],
@ -60,10 +56,7 @@ export class GroupHandlerImpl implements GroupHandler {
private onSelectStart(event: MouseEvent): void {
if (!this.selectionRect) {
const point = translateToSVG(
this.canvas.node as any as SVGSVGElement,
[event.clientX, event.clientY],
);
const point = translateToSVG((this.canvas.node as any) as SVGSVGElement, [event.clientX, event.clientY]);
this.startSelectionPoint = {
x: point[0],
y: point[1],
@ -102,12 +95,16 @@ export class GroupHandlerImpl implements GroupHandler {
// TODO: Doesn't work properly for groups
const bbox = shape.bbox();
const clientID = shape.attr('clientID');
if (bbox.x > box.xtl && bbox.y > box.ytl
&& bbox.x + bbox.width < box.xbr
&& bbox.y + bbox.height < box.ybr
&& !(clientID in this.highlightedShapes)) {
const objectState = this.getStates()
.filter((state: any): boolean => state.clientID === clientID)[0];
if (
bbox.x > box.xtl &&
bbox.y > box.ytl &&
bbox.x + bbox.width < box.xbr &&
bbox.y + bbox.height < box.ybr &&
!(clientID in this.highlightedShapes)
) {
const objectState = this.getStates().filter(
(state: any): boolean => state.clientID === clientID,
)[0];
if (objectState) {
this.statesToBeGroupped.push(objectState);

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

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

@ -83,22 +83,25 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
return output;
}
export function displayShapeSize(
shapesContainer: SVG.Container,
textContainer: SVG.Container,
): ShapeSizeElement {
export function displayShapeSize(shapesContainer: SVG.Container, textContainer: SVG.Container): ShapeSizeElement {
const shapeSize: ShapeSizeElement = {
sizeElement: textContainer.text('').font({
sizeElement: textContainer
.text('')
.font({
weight: 'bolder',
}).fill('white').addClass('cvat_canvas_text'),
update(shape: SVG.Shape): void{
})
.fill('white')
.addClass('cvat_canvas_text'),
update(shape: SVG.Shape): void {
const bbox = shape.bbox();
const text = `${bbox.width.toFixed(1)}x${bbox.height.toFixed(1)}`;
const [x, y]: number[] = translateToSVG(
textContainer.node as any as SVGSVGElement,
translateFromSVG((shapesContainer.node as any as SVGSVGElement), [bbox.x, bbox.y]),
(textContainer.node as any) as SVGSVGElement,
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);
},
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);
}
@ -138,14 +143,19 @@ export function parsePoints(source: string | number[]): Point[] {
}, []);
}
return source.trim().split(/\s/).map((point: string): Point => {
return source
.trim()
.split(/\s/)
.map(
(point: string): Point => {
const [x, y] = point.split(',').map((coord: string): number => +coord);
return { x, y };
});
},
);
}
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 => {
if (idx % 2) {
return `${acc},${val}`;
@ -166,5 +176,5 @@ export function scalarProduct(a: Vector2D, b: 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.addClass('cvat_canvas_shape_splitting');
this.canvas.node.append(this.highlightedShape.node);
this.highlightedShape.on('click.split', (): void => {
this.highlightedShape.on(
'click.split',
(): void => {
this.splitDone = true;
this.onSplitDone(state);
}, {
},
{
once: true,
});
},
);
}
}
}

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

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

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

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

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

@ -1,40 +1,47 @@
# Module CVAT-CORE
## Description
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.
## Versioning
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 changing API (backward compatible new features) do: ``npm version minor``
- After changing API (changes that break backward compatibility) do: ``npm version major``
- 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 (changes that break backward compatibility) do: `npm version major`
### Commands
- Dependencies installation
```bash
npm install
```
- Building the module from sources in the ```dist``` directory:
- Building the module from sources in the `dist` directory:
```bash
npm run build
npm run build -- --mode=development # without a minification
```
- Building the documentation in the ```docs``` directory:
- Building the documentation in the `docs` directory:
```bash
npm run-script docs
```
- Running of tests:
```bash
npm run-script test
```
- Updating of a module version:
```bash
npm version patch # updated after minor fixes
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:
- cvat.js debug starts debugging with entrypoint api.js
- cvat.js test builds library and runs entrypoint tests.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,7 +1,6 @@
/*
* Copyright (C) 2019-2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
(() => {
/**
@ -272,11 +271,36 @@
* @readonly
*/
const colors = [
'#33ddff', '#fa3253', '#34d1b7', '#ff007c', '#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',
'#33ddff',
'#fa3253',
'#34d1b7',
'#ff007c',
'#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 = {

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

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

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

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

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

@ -2,10 +2,6 @@
//
// SPDX-License-Identifier: MIT
/* global
require:false
*/
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const logFactory = require('./log');
@ -41,8 +37,10 @@ class LoggerStorage {
this.ignoreRules[LogType.changeAttribute] = {
lastLog: null,
ignore(previousLog, currentPayload) {
return currentPayload.object_id === previousLog.payload.object_id
&& currentPayload.id === previousLog.payload.id;
return (
currentPayload.object_id === previousLog.payload.object_id &&
currentPayload.id === previousLog.payload.id
);
},
};
}
@ -57,32 +55,28 @@ class LoggerStorage {
}
async configure(isActiveChecker, activityHelper) {
const result = await PluginRegistry
.apiWrapper.call(
this, LoggerStorage.prototype.configure,
isActiveChecker, activityHelper,
const result = await PluginRegistry.apiWrapper.call(
this,
LoggerStorage.prototype.configure,
isActiveChecker,
activityHelper,
);
return result;
}
async log(logType, payload = {}, wait = false) {
const result = await PluginRegistry
.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
return result;
}
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, LoggerStorage.prototype.save);
const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save);
return result;
}
}
LoggerStorage.prototype.configure.implementation = function (
isActiveChecker,
userActivityCallback,
) {
if (typeof (isActiveChecker) !== 'function') {
LoggerStorage.prototype.configure.implementation = function (isActiveChecker, userActivityCallback) {
if (typeof isActiveChecker !== 'function') {
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) {
if (typeof (payload) !== 'object') {
if (typeof payload !== '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');
}

@ -1,12 +1,11 @@
/*
* Copyright (C) 2019-2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/**
* Class representing a machine learning model
* @memberof module:API.cvat.classes
*/
*/
class MLModel {
constructor(data) {
this._id = data.id;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

@ -7,8 +7,9 @@ npm run server # run debug server
```
## Versioning
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 changing API (backward compatible new features) do: ``npm version minor``
- After changing API (changes that break backward compatibility) do: ``npm version major``
- 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 (changes that break backward compatibility) do: `npm version major`

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

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

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

@ -13,17 +13,13 @@ module.exports = {
ecmaVersion: 6,
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'import', 'eslint-plugin-header'],
ignorePatterns: ['*.svg', '*.scss'],
plugins: ['@typescript-eslint', 'import'],
extends: [
'plugin:@typescript-eslint/recommended',
'airbnb-typescript',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'prettier',
'prettier/@typescript-eslint',
'prettier/react',
],
rules: {
'@typescript-eslint/indent': ['warn', 4],
@ -38,12 +34,29 @@ module.exports = {
'no-plusplus': [0],
'lines-between-class-members': 0,
'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: {
'import/resolver': {
typescript: {
directory: './tsconfig.json',
node: {
paths: ['src'],
},
},
},

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

File diff suppressed because it is too large Load Diff

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

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

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

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

@ -2,12 +2,7 @@
//
// SPDX-License-Identifier: MIT
import {
ActionUnion,
createAction,
ThunkAction,
ThunkDispatch,
} from 'utils/redux';
import { ActionUnion, createAction, ThunkAction, ThunkDispatch } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { LogType } from 'cvat-logger';
import { computeZRange } from './annotation-actions';
@ -28,7 +23,8 @@ export const boundariesActions = {
minZ: number,
maxZ: number,
colors: string[],
) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
) =>
createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
job,
states,
frameNumber,
@ -51,33 +47,16 @@ export function resetAfterErrorAsync(): ThunkAction {
const { showAllInterpolationTracks } = state.settings.workspace;
const frameNumber = Math.max(Math.min(job.stopFrame, currentFrame), job.startFrame);
const states = await job.annotations
.get(frameNumber, showAllInterpolationTracks, []);
const states = await job.annotations.get(frameNumber, showAllInterpolationTracks, []);
const frameData = await job.frames.get(frameNumber);
const [minZ, maxZ] = computeZRange(states);
const colors = [...cvat.enums.colors];
await job.logger.log(LogType.restoreJob);
dispatch(boundariesActions.resetAfterError(
job,
states,
frameNumber,
frameData,
minZ,
maxZ,
colors,
));
dispatch(boundariesActions.resetAfterError(job, states, frameNumber, frameData, minZ, maxZ, colors));
} else {
dispatch(boundariesActions.resetAfterError(
null,
[],
0,
null,
0,
0,
[],
));
dispatch(boundariesActions.resetAfterError(null, [], 0, null, 0, 0, []));
}
} catch (error) {
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 = {
getFormats: () => createAction(FormatsActionTypes.GET_FORMATS),
getFormatsSuccess: (annotationFormats: any) => (
getFormatsSuccess: (annotationFormats: any) =>
createAction(FormatsActionTypes.GET_FORMATS_SUCCESS, {
annotationFormats,
})
),
getFormatsFailed: (error: any) => (
createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error })
),
}),
getFormatsFailed: (error: any) => createAction(FormatsActionTypes.GET_FORMATS_FAILED, { error }),
};
export type FormatsActions = ActionUnion<typeof formatsActions>;
@ -35,9 +32,7 @@ export function getFormatsAsync(): ThunkAction {
try {
annotationFormats = await cvat.server.formats();
dispatch(
formatsActions.getFormatsSuccess(annotationFormats),
);
dispatch(formatsActions.getFormatsSuccess(annotationFormats));
} catch (error) {
dispatch(formatsActions.getFormatsFailed(error));
}

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

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

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

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

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

Loading…
Cancel
Save