Merge remote-tracking branch 'upstream/develop' into dkru/cypress_test_case14_appearance_features

main
Kruchinin 5 years ago
commit 48f736b2df

@ -7,3 +7,4 @@ datumaro/
keys/ keys/
logs/ logs/
static/ static/
templates/

@ -4,14 +4,13 @@
module.exports = { module.exports = {
env: { env: {
node: false, node: true,
browser: true, browser: true,
es6: true, es6: true,
jquery: true,
qunit: true,
}, },
parserOptions: { parserOptions: {
sourceType: 'script', sourceType: 'module',
ecmaVersion: 2018,
}, },
plugins: ['eslint-plugin-header'], plugins: ['eslint-plugin-header'],
extends: ['eslint:recommended', 'prettier'], extends: ['eslint:recommended', 'prettier'],

@ -7,3 +7,4 @@ datumaro/
keys/ keys/
logs/ logs/
static/ static/
templates/

@ -1,27 +1,22 @@
{ {
"python.pythonPath": ".env/bin/python", "python.pythonPath": ".env/bin/python",
"eslint.enable": true, "eslint.enable": true,
"eslint.validate": [ "eslint.probe": [
"javascript", "javascript",
"typescript", "typescript",
"typescriptreact", "typescriptreact"
], ],
"eslint.onIgnoredFiles": "warn",
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{ {
"directory": "./cvat-core", "directory": "${cwd}",
"changeProcessCWD": true
}, },
{ {
"directory": "./cvat-canvas", "pattern": "cvat-*"
"changeProcessCWD": true
}, },
{ {
"directory": "./cvat-ui", "directory": "tests",
"changeProcessCWD": true "!cwd": true
},
{
"directory": ".",
"changeProcessCWD": true
} }
], ],
"python.linting.pylintEnabled": true, "python.linting.pylintEnabled": true,

@ -5,11 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.0] - Unreleased ## [1.2.0-beta] - Unreleased
### Added
-
### Changed
-
### Deprecated
-
### Removed
-
### Fixed
- Django templates for email and user guide (<https://github.com/openvinotoolkit/cvat/pull/2412>)
### Security
-
## [1.2.0-alpha] - 2020-11-09
### Added ### 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>) - Ability to login into CVAT-UI with token from api/v1/auth/login (<https://github.com/openvinotoolkit/cvat/pull/2234>)
- Added layout grids toggling ('ctrl + alt + Enter') - Added layout grids toggling ('ctrl + alt + Enter')
- Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>) - Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>)
@ -29,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability to upload prepared meta information along with a video when creating a task (<https://github.com/openvinotoolkit/cvat/pull/2217>) - Ability to upload prepared meta information along with a video when creating a task (<https://github.com/openvinotoolkit/cvat/pull/2217>)
- Optional chaining plugin for cvat-canvas and cvat-ui (<https://github.com/openvinotoolkit/cvat/pull/2249>) - Optional chaining plugin for cvat-canvas and cvat-ui (<https://github.com/openvinotoolkit/cvat/pull/2249>)
- MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>) - MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>)
- Ability to correct upload video with a rotation record in the metadata (<https://github.com/openvinotoolkit/cvat/pull/2218>)
- User search field for assignee fields (<https://github.com/openvinotoolkit/cvat/pull/2370>)
### Changed ### Changed
@ -42,13 +69,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Track frames in skips between annotation is presented in MOT and MOTS formats are marked `outside` (<https://github.com/openvinotoolkit/cvat/pull/2198>) - Track frames in skips between annotation is presented in MOT and MOTS formats are marked `outside` (<https://github.com/openvinotoolkit/cvat/pull/2198>)
- UI packages installation with `npm ci` instead of `npm install` (<https://github.com/openvinotoolkit/cvat/pull/2350>) - UI packages installation with `npm ci` instead of `npm install` (<https://github.com/openvinotoolkit/cvat/pull/2350>)
### Deprecated
-
### Removed ### Removed
- - Removed Z-Order flag from task creation process
### Fixed ### Fixed
@ -72,10 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 'List of tasks' Kibana visualization (<https://github.com/openvinotoolkit/cvat/pull/2361>) - 'List of tasks' Kibana visualization (<https://github.com/openvinotoolkit/cvat/pull/2361>)
- An error on exporting not `jpg` or `png` images in TF Detection API format (<https://github.com/openvinotoolkit/datumaro/issues/35>) - An error on exporting not `jpg` or `png` images in TF Detection API format (<https://github.com/openvinotoolkit/datumaro/issues/35>)
### Security
-
## [1.1.0] - 2020-08-31 ## [1.1.0] - 2020-08-31
### Added ### Added

@ -122,3 +122,7 @@ Other ways to ask questions and get our support:
- [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) - [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat)
- [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation) - [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation)
- [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/) - [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/)
## Projects using CVAT
- [Onepanel](https://github.com/onepanelio/core) - Onepanel is an open source vision AI platform that fully integrates CVAT with scalable data processing and parallelized training pipelines.

@ -4,11 +4,9 @@
module.exports = { module.exports = {
env: { env: {
node: false, node: true,
browser: true, browser: true,
es6: true, es6: true,
jquery: true,
qunit: true,
'jest/globals': true, 'jest/globals': true,
}, },
parserOptions: { parserOptions: {
@ -16,7 +14,7 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2018, ecmaVersion: 2018,
}, },
plugins: ['security', 'jest', 'no-unsanitized', 'no-unsafe-innerhtml'], plugins: ['security', 'jest', 'no-unsafe-innerhtml'],
extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'], extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'],
rules: { rules: {
'no-await-in-loop': [0], 'no-await-in-loop': [0],

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.8.1", "version": "3.9.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -2822,6 +2822,10 @@
}, },
"cvat-data": { "cvat-data": {
"version": "file:../cvat-data", "version": "file:../cvat-data",
"requires": {
"async-mutex": "^0.2.4",
"jszip": "3.5.0"
},
"dependencies": { "dependencies": {
"@babel/cli": { "@babel/cli": {
"version": "7.6.4", "version": "7.6.4",
@ -3932,9 +3936,19 @@
"integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ=="
}, },
"async-mutex": { "async-mutex": {
"version": "0.1.4", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.4.tgz", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.4.tgz",
"integrity": "sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw==" "integrity": "sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg==",
"requires": {
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
}
}
}, },
"atob": { "atob": {
"version": "2.1.2", "version": "2.1.2",
@ -5068,11 +5082,6 @@
"event-emitter": "~0.3.5" "event-emitter": "~0.3.5"
} }
}, },
"es6-promise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y="
},
"es6-set": { "es6-set": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
@ -6781,22 +6790,14 @@
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
}, },
"jszip": { "jszip": {
"version": "3.1.5", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
"integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
"requires": { "requires": {
"core-js": "~2.3.0", "lie": "~3.3.0",
"es6-promise": "~3.0.2",
"lie": "~3.1.0",
"pako": "~1.0.2", "pako": "~1.0.2",
"readable-stream": "~2.0.6" "readable-stream": "~2.3.6",
}, "set-immediate-shim": "~1.0.1"
"dependencies": {
"core-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
"integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU="
}
} }
}, },
"kind-of": { "kind-of": {
@ -6822,9 +6823,9 @@
} }
}, },
"lie": { "lie": {
"version": "3.1.1", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"requires": { "requires": {
"immediate": "~3.0.5" "immediate": "~3.0.5"
} }
@ -7699,9 +7700,9 @@
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
}, },
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.7", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
}, },
"progress": { "progress": {
"version": "2.0.3", "version": "2.0.3",
@ -7891,15 +7892,16 @@
} }
}, },
"readable-stream": { "readable-stream": {
"version": "2.0.6", "version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": { "requires": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.1", "inherits": "~2.0.3",
"isarray": "~1.0.0", "isarray": "~1.0.0",
"process-nextick-args": "~1.0.6", "process-nextick-args": "~2.0.0",
"string_decoder": "~0.10.x", "safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1" "util-deprecate": "~1.0.1"
} }
}, },
@ -8243,6 +8245,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
}, },
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
},
"set-value": { "set-value": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@ -8620,9 +8627,12 @@
} }
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.8.1", "version": "3.9.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration", "description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js", "main": "babel.config.js",
"scripts": { "scripts": {

@ -17,26 +17,6 @@
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
const { Task } = require('./session'); const { Task } = require('./session');
function attachUsers(task, users) {
if (task.assignee !== null) {
[task.assignee] = users.filter((user) => user.id === task.assignee);
}
for (const segment of task.segments) {
for (const job of segment.jobs) {
if (job.assignee !== null) {
[job.assignee] = users.filter((user) => user.id === job.assignee);
}
}
}
if (task.owner !== null) {
[task.owner] = users.filter((user) => user.id === task.owner);
}
return task;
}
function implementAPI(cvat) { function implementAPI(cvat) {
cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat);
@ -122,7 +102,10 @@
cvat.users.get.implementation = async (filter) => { cvat.users.get.implementation = async (filter) => {
checkFilter(filter, { checkFilter(filter, {
id: isInteger,
self: isBoolean, self: isBoolean,
search: isString,
limit: isInteger,
}); });
let users = null; let users = null;
@ -130,7 +113,13 @@
users = await serverProxy.users.getSelf(); users = await serverProxy.users.getSelf();
users = [users]; users = [users];
} else { } else {
users = await serverProxy.users.getUsers(); const searchParams = {};
for (const key in filter) {
if (filter[key] && key !== 'self') {
searchParams[key] = filter[key];
}
}
users = await serverProxy.users.getUsers(new URLSearchParams(searchParams).toString());
} }
users = users.map((user) => new User(user)); users = users.map((user) => new User(user));
@ -163,8 +152,7 @@
// If task was found by its id, then create task instance and get Job instance from it // If task was found by its id, then create task instance and get Job instance from it
if (tasks !== null && tasks.length) { if (tasks !== null && tasks.length) {
const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData)); const task = new Task(tasks[0]);
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;
} }
@ -203,9 +191,8 @@
} }
} }
const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData));
const tasksData = await serverProxy.tasks.getTasks(searchParams.toString()); 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) => new Task(task));
tasks.count = tasksData.count; tasks.count = tasksData.count;

@ -500,20 +500,14 @@
} }
} }
async function getUsers(id = null) { async function getUsers(filter = 'page_size=all') {
const { backendAPI } = config; const { backendAPI } = config;
let response = null; let response = null;
try { try {
if (id === null) { response = await Axios.get(`${backendAPI}/users?${filter}`, {
response = await Axios.get(`${backendAPI}/users?page_size=all`, { proxy: config.proxy,
proxy: config.proxy, });
});
} else {
response = await Axios.get(`${backendAPI}/users/${id}`, {
proxy: config.proxy,
});
}
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }

@ -686,6 +686,8 @@
} }
} }
if (data.assignee) data.assignee = new User(data.assignee);
Object.defineProperties( Object.defineProperties(
this, this,
Object.freeze({ Object.freeze({
@ -883,6 +885,9 @@
} }
} }
if (data.assignee) data.assignee = new User(data.assignee);
if (data.owner) data.owner = new User(data.owner);
data.labels = []; data.labels = [];
data.jobs = []; data.jobs = [];
data.files = Object.freeze({ data.files = Object.freeze({
@ -1440,7 +1445,7 @@
if (this.id) { if (this.id) {
const jobData = { const jobData = {
status: this.status, status: this.status,
assignee: this.assignee ? this.assignee.id : null, assignee_id: this.assignee ? this.assignee.id : null,
}; };
await serverProxy.jobs.saveJob(this.id, jobData); await serverProxy.jobs.saveJob(this.id, jobData);
@ -1649,7 +1654,7 @@
if (typeof this.id !== 'undefined') { if (typeof this.id !== 'undefined') {
// If the task has been already created, we update it // If the task has been already created, we update it
const taskData = { const taskData = {
assignee: this.assignee ? this.assignee.id : null, assignee_id: this.assignee ? this.assignee.id : null,
name: this.name, name: this.name,
bug_tracker: this.bugTracker, bug_tracker: this.bugTracker,
labels: [...this.labels.map((el) => el.toJSON())], labels: [...this.labels.map((el) => el.toJSON())],

@ -154,7 +154,11 @@ const tasksDummyData = {
name: 'Test', name: 'Test',
size: 1, size: 1,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-09-05T11:59:22.987942Z', created_date: '2019-09-05T11:59:22.987942Z',
@ -194,7 +198,11 @@ const tasksDummyData = {
name: 'Image Task', name: 'Image Task',
size: 9, size: 9,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-06-18T13:05:08.941304+03:00', created_date: '2019-06-18T13:05:08.941304+03:00',
@ -239,7 +247,11 @@ const tasksDummyData = {
name: 'Video Task', name: 'Video Task',
size: 5002, size: 5002,
mode: 'interpolation', mode: 'interpolation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-06-21T16:34:49.199691+03:00', created_date: '2019-06-21T16:34:49.199691+03:00',
@ -558,7 +570,11 @@ const tasksDummyData = {
name: 'Test Task', name: 'Test Task',
size: 5002, size: 5002,
mode: 'interpolation', mode: 'interpolation',
owner: 2, owner: {
url: 'http://localhost:7000/api/v1/users/2',
id: 2,
username: 'bsekache',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-05-16T13:08:00.621747+03:00', created_date: '2019-05-16T13:08:00.621747+03:00',
@ -767,7 +783,11 @@ const tasksDummyData = {
name: 'Video', name: 'Video',
size: 75, size: 75,
mode: 'interpolation', mode: 'interpolation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-05-15T11:40:19.487999+03:00', created_date: '2019-05-15T11:40:19.487999+03:00',
@ -964,7 +984,11 @@ const tasksDummyData = {
name: 'Labels Set', name: 'Labels Set',
size: 9, size: 9,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: 'http://bugtracker.com/issue12345', bug_tracker: 'http://bugtracker.com/issue12345',
created_date: '2019-05-13T15:35:29.871003+03:00', created_date: '2019-05-13T15:35:29.871003+03:00',

@ -94,8 +94,8 @@ class ServerProxy {
const object = tasksDummyData.results.filter((task) => task.id === id)[0]; const object = tasksDummyData.results.filter((task) => task.id === id)[0];
for (const prop in taskData) { for (const prop in taskData) {
if ( if (
Object.prototype.hasOwnProperty.call(taskData, prop) && Object.prototype.hasOwnProperty.call(taskData, prop)
Object.prototype.hasOwnProperty.call(object, prop) && Object.prototype.hasOwnProperty.call(object, prop)
) { ) {
object[prop] = taskData[prop]; object[prop] = taskData[prop];
} }
@ -110,7 +110,10 @@ class ServerProxy {
name: taskData.name, name: taskData.name,
size: 5000, size: 5000,
mode: 'interpolation', mode: 'interpolation',
owner: 2, owner: {
id: 2,
username: 'bsekache',
},
assignee: null, assignee: null,
bug_tracker: taskData.bug_tracker, bug_tracker: taskData.bug_tracker,
created_date: '2019-05-16T13:08:00.621747+03:00', created_date: '2019-05-16T13:08:00.621747+03:00',
@ -175,8 +178,8 @@ class ServerProxy {
for (const prop in jobData) { for (const prop in jobData) {
if ( if (
Object.prototype.hasOwnProperty.call(jobData, prop) && Object.prototype.hasOwnProperty.call(jobData, prop)
Object.prototype.hasOwnProperty.call(object, prop) && Object.prototype.hasOwnProperty.call(object, prop)
) { ) {
object[prop] = jobData[prop]; object[prop] = jobData[prop];
} }

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.9.14", "version": "1.10.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1179,6 +1179,11 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true "dev": true
}, },
"@types/lodash": {
"version": "4.14.165",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz",
"integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg=="
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -1208,9 +1213,9 @@
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "16.9.52", "version": "16.9.53",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.52.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz",
"integrity": "sha512-EHRjmnxiNivwhGdMh9sz1Yw9AUxTSZFxKqdBWAAzyZx3sufWwx6ogqHYh/WB1m/I4ZpjkoZLExF5QTy2ekVi/Q==", "integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@ -17047,7 +17052,7 @@
}, },
"fs-minipass": { "fs-minipass": {
"version": "1.2.7", "version": "1.2.7",
"resolved": false, "resolved": "",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17062,7 +17067,7 @@
}, },
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": false, "resolved": "",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17078,7 +17083,7 @@
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": false, "resolved": "",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17116,7 +17121,7 @@
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": false, "resolved": "",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17168,7 +17173,7 @@
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"resolved": false, "resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17178,7 +17183,7 @@
}, },
"minizlib": { "minizlib": {
"version": "1.3.3", "version": "1.3.3",
"resolved": false, "resolved": "",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17213,7 +17218,7 @@
}, },
"node-pre-gyp": { "node-pre-gyp": {
"version": "0.14.0", "version": "0.14.0",
"resolved": false, "resolved": "",
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17267,7 +17272,7 @@
}, },
"npmlog": { "npmlog": {
"version": "4.1.2", "version": "4.1.2",
"resolved": false, "resolved": "",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17291,7 +17296,7 @@
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": false, "resolved": "",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17361,7 +17366,7 @@
}, },
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": false, "resolved": "",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17441,7 +17446,7 @@
}, },
"tar": { "tar": {
"version": "4.4.13", "version": "4.4.13",
"resolved": false, "resolved": "",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17471,13 +17476,13 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": false, "resolved": "",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true "optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": false, "resolved": "",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true "optional": true
} }
@ -25949,9 +25954,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.19", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}, },
"lodash._reinterpolate": { "lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",
@ -28850,9 +28855,9 @@
} }
}, },
"react": { "react": {
"version": "16.13.1", "version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -28883,9 +28888,9 @@
} }
}, },
"react-dom": { "react-dom": {
"version": "16.13.1", "version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.9.14", "version": "1.10.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
@ -47,8 +47,9 @@
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"@types/lodash": "^4.14.165",
"@types/platform": "^1.3.3", "@types/platform": "^1.3.3",
"@types/react": "^16.9.52", "@types/react": "^16.9.53",
"@types/react-color": "^3.0.4", "@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.2", "@types/react-redux": "^7.1.2",
@ -62,13 +63,14 @@
"cvat-core": "file:../cvat-core", "cvat-core": "file:../cvat-core",
"dotenv-webpack": "^1.8.0", "dotenv-webpack": "^1.8.0",
"error-stack-parser": "^2.0.6", "error-stack-parser": "^2.0.6",
"lodash": "^4.17.20",
"moment": "^2.29.1", "moment": "^2.29.1",
"platform": "^1.3.6", "platform": "^1.3.6",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.13.1", "react": "^16.14.0",
"react-color": "^2.18.1", "react-color": "^2.18.1",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-dom": "^16.13.1", "react-dom": "^16.14.0",
"react-hotkeys": "^2.0.0", "react-hotkeys": "^2.0.0",
"react-redux": "^7.1.1", "react-redux": "^7.1.1",
"react-router": "^5.1.0", "react-router": "^5.1.0",

@ -1,36 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
const core = getCore();
export enum UsersActionTypes {
GET_USERS = 'GET_USERS',
GET_USERS_SUCCESS = 'GET_USERS_SUCCESS',
GET_USERS_FAILED = 'GET_USERS_FAILED',
}
const usersActions = {
getUsers: () => createAction(UsersActionTypes.GET_USERS),
getUsersSuccess: (users: any[]) => createAction(UsersActionTypes.GET_USERS_SUCCESS, { users }),
getUsersFailed: (error: any) => createAction(UsersActionTypes.GET_USERS_FAILED, { error }),
};
export type UsersActions = ActionUnion<typeof usersActions>;
export function getUsersAsync(): ThunkAction {
return async (dispatch): Promise<void> => {
dispatch(usersActions.getUsers());
try {
const users = await core.users.get();
const wrappedUsers = users.map((userData: any): any => new core.classes.User(userData));
dispatch(usersActions.getUsersSuccess(wrappedUsers));
} catch (error) {
dispatch(usersActions.getUsersFailed(error));
}
};
}

@ -34,7 +34,6 @@ import '../styles.scss';
interface CVATAppProps { interface CVATAppProps {
loadFormats: () => void; loadFormats: () => void;
loadUsers: () => void;
loadAbout: () => void; loadAbout: () => void;
verifyAuthorized: () => void; verifyAuthorized: () => void;
loadUserAgreements: () => void; loadUserAgreements: () => void;
@ -54,8 +53,6 @@ interface CVATAppProps {
modelsFetching: boolean; modelsFetching: boolean;
formatsInitialized: boolean; formatsInitialized: boolean;
formatsFetching: boolean; formatsFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean; aboutInitialized: boolean;
aboutFetching: boolean; aboutFetching: boolean;
userAgreementsFetching: boolean; userAgreementsFetching: boolean;
@ -92,7 +89,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
const { const {
verifyAuthorized, verifyAuthorized,
loadFormats, loadFormats,
loadUsers,
loadAbout, loadAbout,
loadUserAgreements, loadUserAgreements,
initPlugins, initPlugins,
@ -102,8 +98,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
userFetching, userFetching,
formatsInitialized, formatsInitialized,
formatsFetching, formatsFetching,
usersInitialized,
usersFetching,
aboutInitialized, aboutInitialized,
aboutFetching, aboutFetching,
pluginsInitialized, pluginsInitialized,
@ -143,10 +137,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadFormats(); loadFormats();
} }
if (!usersInitialized && !usersFetching) {
loadUsers();
}
if (!aboutInitialized && !aboutFetching) { if (!aboutInitialized && !aboutFetching) {
loadAbout(); loadAbout();
} }
@ -235,7 +225,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
public render(): JSX.Element { public render(): JSX.Element {
const { const {
userInitialized, userInitialized,
usersInitialized,
aboutInitialized, aboutInitialized,
pluginsInitialized, pluginsInitialized,
formatsInitialized, formatsInitialized,
@ -248,7 +237,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
const readyForRender = const readyForRender =
(userInitialized && (user == null || !user.isVerified)) || (userInitialized && (user == null || !user.isVerified)) ||
(userInitialized && formatsInitialized && pluginsInitialized && usersInitialized && aboutInitialized); (userInitialized && formatsInitialized && pluginsInitialized && aboutInitialized);
const subKeyMap = { const subKeyMap = {
SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS, SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
@ -270,7 +259,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
if (showPlatformNotification()) { if (showPlatformNotification()) {
stopNotifications(false); stopNotifications(false);
const info = platformInfo(); const {
name, version, engine, os,
} = platformInfo();
Modal.warning({ Modal.warning({
title: 'Unsupported platform detected', title: 'Unsupported platform detected',
content: ( content: (
@ -278,7 +270,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Row> <Row>
<Col> <Col>
<Text> <Text>
{`The browser you are using is ${info.name} ${info.version} based on ${info.engine} .` + {`The browser you are using is ${name} ${version} based on ${engine}.` +
' CVAT was tested in the latest versions of Chrome and Firefox.' + ' CVAT was tested in the latest versions of Chrome and Firefox.' +
' We recommend to use Chrome (or another Chromium based browser)'} ' We recommend to use Chrome (or another Chromium based browser)'}
</Text> </Text>
@ -286,7 +278,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
</Row> </Row>
<Row> <Row>
<Col> <Col>
<Text type='secondary'>{`The operating system is ${info.os}`}</Text> <Text type='secondary'>{`The operating system is ${os}`}</Text>
</Col> </Col>
</Row> </Row>
</> </>

@ -18,7 +18,7 @@ import patterns from 'utils/validation-patterns';
import { getReposData, syncRepos } from 'utils/git-utils'; import { getReposData, syncRepos } from 'utils/git-utils';
import { ActiveInference } from 'reducers/interfaces'; import { ActiveInference } from 'reducers/interfaces';
import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress'; import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress';
import UserSelector from './user-selector'; import UserSelector, { User } from './user-selector';
import LabelsEditorComponent from '../labels-editor/labels-editor'; import LabelsEditorComponent from '../labels-editor/labels-editor';
const core = getCore(); const core = getCore();
@ -27,7 +27,6 @@ interface Props {
previewImage: string; previewImage: string;
taskInstance: any; taskInstance: any;
installedGit: boolean; // change to git repos url installedGit: boolean; // change to git repos url
registeredUsers: any[];
activeInference: ActiveInference | null; activeInference: ActiveInference | null;
cancelAutoAnnotation(): void; cancelAutoAnnotation(): void;
onTaskUpdate: (taskInstance: any) => void; onTaskUpdate: (taskInstance: any) => void;
@ -196,35 +195,26 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
} }
private renderUsers(): JSX.Element { private renderUsers(): JSX.Element {
const { taskInstance, registeredUsers, onTaskUpdate } = this.props; const { taskInstance, onTaskUpdate } = this.props;
const owner = taskInstance.owner ? taskInstance.owner.username : null; const owner = taskInstance.owner ? taskInstance.owner.username : null;
const assignee = taskInstance.assignee ? taskInstance.assignee.username : null; const assignee = taskInstance.assignee ? taskInstance.assignee : null;
const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); const created = moment(taskInstance.createdDate).format('MMMM Do YYYY');
const assigneeSelect = ( const assigneeSelect = (
<UserSelector <UserSelector
users={registeredUsers}
value={assignee} value={assignee}
onChange={(value: string): void => { onSelect={(value: User | null): void => {
let [userInstance] = registeredUsers.filter((user: any) => user.username === value); taskInstance.assignee = value;
if (userInstance === undefined) {
userInstance = null;
}
taskInstance.assignee = userInstance;
onTaskUpdate(taskInstance); onTaskUpdate(taskInstance);
}} }}
/> />
); );
return ( return (
<Row type='flex' justify='space-between' align='middle'> <Row className='cvat-task-details-user-block' type='flex' justify='space-between' align='middle'>
<Col span={12}>{owner && <Text type='secondary'>{`Created by ${owner} on ${created}`}</Text>}</Col> <Col span={12}>{owner && <Text type='secondary'>{`Created by ${owner} on ${created}`}</Text>}</Col>
<Col span={10}> <Col span={10}>
<Text type='secondary'> <Text type='secondary'>Assigned to</Text>
Assigned to {assigneeSelect}
{assigneeSelect}
</Text>
</Col> </Col>
</Row> </Row>
); );

@ -15,7 +15,7 @@ import moment from 'moment';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
import UserSelector from './user-selector'; import UserSelector, { User } from './user-selector';
const core = getCore(); const core = getCore();
@ -23,14 +23,12 @@ const baseURL = core.config.backendAPI.slice(0, -7);
interface Props { interface Props {
taskInstance: any; taskInstance: any;
registeredUsers: any[];
onJobUpdate(jobInstance: any): void; onJobUpdate(jobInstance: any): void;
} }
function JobListComponent(props: Props & RouteComponentProps): JSX.Element { function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
const { const {
taskInstance, taskInstance,
registeredUsers,
onJobUpdate, onJobUpdate,
history: { push }, history: { push },
} = props; } = props;
@ -100,21 +98,14 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
dataIndex: 'assignee', dataIndex: 'assignee',
key: 'assignee', key: 'assignee',
render: (jobInstance: any): JSX.Element => { render: (jobInstance: any): JSX.Element => {
const assignee = jobInstance.assignee ? jobInstance.assignee.username : null; const assignee = jobInstance.assignee ? jobInstance.assignee : null;
return ( return (
<UserSelector <UserSelector
users={registeredUsers}
value={assignee} value={assignee}
onChange={(value: string): void => { onSelect={(value: User | null): void => {
let [userInstance] = [...registeredUsers].filter((user: any) => user.username === value);
if (userInstance === undefined) {
userInstance = null;
}
// eslint-disable-next-line // eslint-disable-next-line
jobInstance.assignee = userInstance; jobInstance.assignee = value;
onJobUpdate(jobInstance); onJobUpdate(jobInstance);
}} }}
/> />

@ -17,6 +17,12 @@
padding: 20px; padding: 20px;
background: $background-color-1; background: $background-color-1;
.cvat-task-details-user-block {
> div:nth-child(2) > span {
margin-right: 8px;
}
}
> div:nth-child(2) { > div:nth-child(2) {
> div:nth-child(2) { > div:nth-child(2) {
padding-left: 20px; padding-left: 20px;
@ -87,11 +93,6 @@
background-color: $background-color-2; background-color: $background-color-2;
} }
.cvat-user-selector {
margin-left: 10px;
width: 150px;
}
.cvat-open-bug-tracker-button { .cvat-open-bug-tracker-button {
margin-left: 15px; margin-left: 15px;
} }

@ -2,30 +2,114 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { useState, useEffect, useRef } from 'react';
import Select from 'antd/lib/select'; import Autocomplete from 'antd/lib/auto-complete';
import Input from 'antd/lib/input';
import getCore from 'cvat-core-wrapper';
import { SelectValue } from 'antd/lib/select';
import debounce from 'lodash/debounce';
const core = getCore();
export interface User {
id: number;
username: string;
}
interface Props { interface Props {
value: string | null; value: User | null;
users: any[]; onSelect: (user: User | null) => void;
onChange: (user: string) => void;
} }
const searchUsers = debounce(
(searchValue: string, setUsers: (users: User[]) => void): void => {
core.users
.get({
search: searchValue,
limit: 10,
})
.then((result: User[]) => {
if (result) {
setUsers(result);
}
});
},
250,
{
maxWait: 750,
},
);
export default function UserSelector(props: Props): JSX.Element { export default function UserSelector(props: Props): JSX.Element {
const { value, users, onChange } = props; const { value, onSelect } = props;
const [searchPhrase, setSearchPhrase] = useState('');
const [users, setUsers] = useState<User[]>([]);
const autocompleteRef = useRef<Autocomplete | null>(null);
const handleSearch = (searchValue: string): void => {
if (searchValue) {
searchUsers(searchValue, setUsers);
} else {
setUsers([]);
}
setSearchPhrase(searchValue);
};
const handleFocus = (open: boolean): void => {
if (!users.length && open) {
core.users.get({ limit: 10 }).then((result: User[]) => {
if (result) {
setUsers(result);
}
});
}
if (!open && searchPhrase !== value?.username) {
setSearchPhrase('');
if (value) {
onSelect(null);
}
}
};
const handleSelect = (_value: SelectValue): void => {
setSearchPhrase(users.filter((user) => user.id === +_value)[0].username);
onSelect(_value ? users.filter((user) => user.id === +_value)[0] : null);
};
useEffect(() => {
if (value && !users.filter((user) => user.id === value.id).length) {
core.users.get({ id: value.id }).then((result: User[]) => {
const [user] = result;
setUsers([
...users,
{
id: user.id,
username: user.username,
},
]);
setSearchPhrase(user.username);
});
}
}, [value]);
return ( return (
<Select defaultValue={value || '—'} size='small' showSearch className='cvat-user-selector' onChange={onChange}> <Autocomplete
<Select.Option key='-1' value='—'> ref={autocompleteRef}
value={searchPhrase}
</Select.Option> placeholder='Select a user'
{users.map( onSearch={handleSearch}
(user): JSX.Element => ( onSelect={handleSelect}
<Select.Option key={user.id} value={user.username}> className='cvat-user-search-field'
{user.username} onDropdownVisibleChange={handleFocus}
</Select.Option> dataSource={users.map((user) => ({
), value: user.id.toString(),
)} text: user.username,
</Select> }))}
>
<Input onPressEnter={() => autocompleteRef.current?.blur()} />
</Autocomplete>
); );
} }

@ -15,7 +15,6 @@ interface OwnProps {
} }
interface StateToProps { interface StateToProps {
registeredUsers: any[];
activeInference: ActiveInference | null; activeInference: ActiveInference | null;
installedGit: boolean; installedGit: boolean;
} }
@ -29,7 +28,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { list } = state.plugins; const { list } = state.plugins;
return { return {
registeredUsers: state.users.users,
installedGit: list.GIT_INTEGRATION, installedGit: list.GIT_INTEGRATION,
activeInference: state.models.inferences[own.task.instance.id] || null, activeInference: state.models.inferences[own.task.instance.id] || null,
}; };
@ -47,14 +45,15 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {
} }
function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element {
const { task, installedGit, activeInference, registeredUsers, cancelAutoAnnotation, onTaskUpdate } = props; const {
task, installedGit, activeInference, cancelAutoAnnotation, onTaskUpdate,
} = props;
return ( return (
<DetailsComponent <DetailsComponent
previewImage={task.preview} previewImage={task.preview}
taskInstance={task.instance} taskInstance={task.instance}
installedGit={installedGit} installedGit={installedGit}
registeredUsers={registeredUsers}
activeInference={activeInference} activeInference={activeInference}
onTaskUpdate={onTaskUpdate} onTaskUpdate={onTaskUpdate}
cancelAutoAnnotation={cancelAutoAnnotation} cancelAutoAnnotation={cancelAutoAnnotation}

@ -7,38 +7,26 @@ import { connect } from 'react-redux';
import JobListComponent from 'components/task-page/job-list'; import JobListComponent from 'components/task-page/job-list';
import { updateJobAsync } from 'actions/tasks-actions'; import { updateJobAsync } from 'actions/tasks-actions';
import { Task, CombinedState } from 'reducers/interfaces'; import { Task } from 'reducers/interfaces';
interface OwnProps { interface OwnProps {
task: Task; task: Task;
} }
interface StateToProps {
registeredUsers: any[];
}
interface DispatchToProps { interface DispatchToProps {
onJobUpdate(jobInstance: any): void; onJobUpdate(jobInstance: any): void;
} }
function mapStateToProps(state: CombinedState): StateToProps {
return {
registeredUsers: state.users.users,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps {
return { return {
onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)), onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)),
}; };
} }
function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { function TaskPageContainer(props: DispatchToProps & OwnProps): JSX.Element {
const { task, registeredUsers, onJobUpdate } = props; const { task, onJobUpdate } = props;
return ( return <JobListComponent taskInstance={task.instance} onJobUpdate={onJobUpdate} />;
<JobListComponent taskInstance={task.instance} registeredUsers={registeredUsers} onJobUpdate={onJobUpdate} />
);
} }
export default connect(mapStateToProps, mapDispatchToProps)(TaskPageContainer); export default connect(null, mapDispatchToProps)(TaskPageContainer);

@ -10,7 +10,6 @@ import { getPluginsAsync } from 'actions/plugins-actions';
import { switchSettingsDialog } from 'actions/settings-actions'; import { switchSettingsDialog } from 'actions/settings-actions';
import { shortcutsActions } from 'actions/shortcuts-actions'; import { shortcutsActions } from 'actions/shortcuts-actions';
import { getUserAgreementsAsync } from 'actions/useragreements-actions'; import { getUserAgreementsAsync } from 'actions/useragreements-actions';
import { getUsersAsync } from 'actions/users-actions';
import CVATApplication from 'components/cvat-app'; import CVATApplication from 'components/cvat-app';
import LayoutGrid from 'components/layout-grid/layout-grid'; import LayoutGrid from 'components/layout-grid/layout-grid';
import logger, { LogType } from 'cvat-logger'; import logger, { LogType } from 'cvat-logger';
@ -34,8 +33,6 @@ interface StateToProps {
modelsFetching: boolean; modelsFetching: boolean;
userInitialized: boolean; userInitialized: boolean;
userFetching: boolean; userFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean; aboutInitialized: boolean;
aboutFetching: boolean; aboutFetching: boolean;
formatsInitialized: boolean; formatsInitialized: boolean;
@ -55,7 +52,6 @@ interface StateToProps {
interface DispatchToProps { interface DispatchToProps {
loadFormats: () => void; loadFormats: () => void;
verifyAuthorized: () => void; verifyAuthorized: () => void;
loadUsers: () => void;
loadAbout: () => void; loadAbout: () => void;
initModels: () => void; initModels: () => void;
initPlugins: () => void; initPlugins: () => void;
@ -71,7 +67,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { plugins } = state; const { plugins } = state;
const { auth } = state; const { auth } = state;
const { formats } = state; const { formats } = state;
const { users } = state;
const { about } = state; const { about } = state;
const { shortcuts } = state; const { shortcuts } = state;
const { userAgreements } = state; const { userAgreements } = state;
@ -84,8 +79,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
pluginsFetching: plugins.fetching, pluginsFetching: plugins.fetching,
modelsInitialized: models.initialized, modelsInitialized: models.initialized,
modelsFetching: models.fetching, modelsFetching: models.fetching,
usersInitialized: users.initialized,
usersFetching: users.fetching,
aboutInitialized: about.initialized, aboutInitialized: about.initialized,
aboutFetching: about.fetching, aboutFetching: about.fetching,
formatsInitialized: formats.initialized, formatsInitialized: formats.initialized,
@ -110,7 +103,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()), loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()),
initPlugins: (): void => dispatch(getPluginsAsync()), initPlugins: (): void => dispatch(getPluginsAsync()),
initModels: (): void => dispatch(getModelsAsync()), initModels: (): void => dispatch(getModelsAsync()),
loadUsers: (): void => dispatch(getUsersAsync()),
loadAbout: (): void => dispatch(getAboutAsync()), loadAbout: (): void => dispatch(getAboutAsync()),
resetErrors: (): void => dispatch(resetErrors()), resetErrors: (): void => dispatch(resetErrors()),
resetMessages: (): void => dispatch(resetMessages()), resetMessages: (): void => dispatch(resetMessages()),

@ -93,12 +93,6 @@ export interface PluginsState {
list: PluginsList; list: PluginsList;
} }
export interface UsersState {
users: any[];
fetching: boolean;
initialized: boolean;
}
export interface AboutState { export interface AboutState {
server: any; server: any;
packageVersion: { packageVersion: {
@ -494,7 +488,6 @@ export interface MetaState {
export interface CombinedState { export interface CombinedState {
auth: AuthState; auth: AuthState;
tasks: TasksState; tasks: TasksState;
users: UsersState;
about: AboutState; about: AboutState;
share: ShareState; share: ShareState;
formats: FormatsState; formats: FormatsState;

@ -9,7 +9,6 @@ import { FormatsActionTypes } from 'actions/formats-actions';
import { ModelsActionTypes } from 'actions/models-actions'; import { ModelsActionTypes } from 'actions/models-actions';
import { ShareActionTypes } from 'actions/share-actions'; import { ShareActionTypes } from 'actions/share-actions';
import { TasksActionTypes } from 'actions/tasks-actions'; import { TasksActionTypes } from 'actions/tasks-actions';
import { UsersActionTypes } from 'actions/users-actions';
import { AboutActionTypes } from 'actions/about-actions'; import { AboutActionTypes } from 'actions/about-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions';
import { NotificationsActionType } from 'actions/notification-actions'; import { NotificationsActionType } from 'actions/notification-actions';
@ -357,8 +356,7 @@ export default function (state = defaultState, action: AnyAction): Notifications
tasks: { tasks: {
...state.errors.tasks, ...state.errors.tasks,
updating: { updating: {
message: message: `Could not update <a href="/tasks/${taskID}" target="_blank">task ${taskID}</a>`,
'Could not update ' + `<a href="/tasks/${taskID}" target="_blank">task ${taskID}</a>`,
reason: action.payload.error.toString(), reason: action.payload.error.toString(),
}, },
}, },
@ -431,21 +429,6 @@ export default function (state = defaultState, action: AnyAction): Notifications
}, },
}; };
} }
case UsersActionTypes.GET_USERS_FAILED: {
return {
...state,
errors: {
...state.errors,
users: {
...state.errors.users,
fetching: {
message: 'Could not get users from the server',
reason: action.payload.error.toString(),
},
},
},
};
}
case AboutActionTypes.GET_ABOUT_FAILED: { case AboutActionTypes.GET_ABOUT_FAILED: {
return { return {
...state, ...state,

@ -5,7 +5,6 @@
import { combineReducers, Reducer } from 'redux'; import { combineReducers, Reducer } from 'redux';
import authReducer from './auth-reducer'; import authReducer from './auth-reducer';
import tasksReducer from './tasks-reducer'; import tasksReducer from './tasks-reducer';
import usersReducer from './users-reducer';
import aboutReducer from './about-reducer'; import aboutReducer from './about-reducer';
import shareReducer from './share-reducer'; import shareReducer from './share-reducer';
import formatsReducer from './formats-reducer'; import formatsReducer from './formats-reducer';
@ -21,7 +20,6 @@ export default function createRootReducer(): Reducer {
return combineReducers({ return combineReducers({
auth: authReducer, auth: authReducer,
tasks: tasksReducer, tasks: tasksReducer,
users: usersReducer,
about: aboutReducer, about: aboutReducer,
share: shareReducer, share: shareReducer,
formats: formatsReducer, formats: formatsReducer,

@ -1,48 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { BoundariesActionTypes, BoundariesActions } from 'actions/boundaries-actions';
import { AuthActionTypes, AuthActions } from 'actions/auth-actions';
import { UsersActionTypes, UsersActions } from 'actions/users-actions';
import { UsersState } from './interfaces';
const defaultState: UsersState = {
users: [],
fetching: false,
initialized: false,
};
export default function (
state: UsersState = defaultState,
action: UsersActions | AuthActions | BoundariesActions,
): UsersState {
switch (action.type) {
case UsersActionTypes.GET_USERS: {
return {
...state,
fetching: true,
initialized: false,
};
}
case UsersActionTypes.GET_USERS_SUCCESS:
return {
...state,
fetching: false,
initialized: true,
users: action.payload.users,
};
case UsersActionTypes.GET_USERS_FAILED:
return {
...state,
fetching: false,
initialized: true,
};
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return { ...defaultState };
}
default:
return state;
}
}

@ -4,6 +4,6 @@
from cvat.utils.version import get_version from cvat.utils.version import get_version
VERSION = (1, 2, 0, 'alpha', 0) VERSION = (1, 2, 0, 'beta', 0)
__version__ = get_version(VERSION) __version__ = get_version(VERSION)

@ -1,5 +1,6 @@
{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with {% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}
site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}! {% blocktrans with site_name=current_site.name site_domain=current_site.domain %}
Hello from {{ site_name }}!
<p> <p>
You're receiving this e-mail because user <strong>{{ user_display }}</strong> has given yours as an e-mail address You're receiving this e-mail because user <strong>{{ user_display }}</strong> has given yours as an e-mail address
@ -7,6 +8,7 @@ site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site
</p> </p>
<p>To confirm this is correct, go to <a href="{{ activate_url }}">{{ activate_url }}</a></p> <p>To confirm this is correct, go to <a href="{{ activate_url }}">{{ activate_url }}</a></p>
{% endblocktrans %} {% blocktrans with site_name=current_site.name site_domain=current_site.domain %} {% endblocktrans %}
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}
<strong>{{ site_domain }}</strong> <strong>{{ site_domain }}</strong>
{% endblocktrans %} {% endautoescape %} {% endblocktrans %} {% endautoescape %}

@ -1,5 +1,16 @@
{% load i18n %}{% autoescape off %} {% blocktrans %}You're receiving this email because you requested a password reset {% load i18n %}{% autoescape off %}
for your user account at {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page and choose a new {% blocktrans %}
password:" %} {% block reset_link %} {{ protocol }}://{{ domain }}/auth/password/reset/confirm?uid={{ uid }}&token={{ You're receiving this email because you requested a password reset for your user account at {{ site_name }}.
token }} {% endblock %} {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} {% trans "Thanks {% endblocktrans %}
for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endautoescape %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}

@ -18,6 +18,7 @@
- [PASCAL VOC and mask](#voc) - [PASCAL VOC and mask](#voc)
- [YOLO](#yolo) - [YOLO](#yolo)
- [TF detection API](#tfrecord) - [TF detection API](#tfrecord)
- [ImageNet](#imagenet)
## How to add a new annotation format support<a id="how-to-add"></a> ## How to add a new annotation format support<a id="how-to-add"></a>
@ -802,3 +803,35 @@ taskname.zip/
``` ```
- supported annotations: Rectangles, Polygons, Masks (as polygons) - supported annotations: Rectangles, Polygons, Masks (as polygons)
### [ImageNet](http://www.image-net.org)<a id="imagenet" />
#### ImageNet Dumper
Downloaded file: a zip archive of the following structure:
```bash
# if we save images:
taskname.zip/
└── label1/
├── label1_image1.jpg
└── label1_image2.jpg
└── label2/
├── label2_image1.jpg
├── label2_image3.jpg
└── label2_image4.jpg
# if we keep only annotation:
taskname.zip/
└── <any_subset_name>.txt
└── synsets.txt
```
- supported annotations: Labels
#### ImageNet Loader
Uploaded file: a zip archive of the structure above
- supported annotations: Labels

@ -0,0 +1,41 @@
# Copyright (C) 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT
import os.path as osp
from glob import glob
import zipfile
from tempfile import TemporaryDirectory
from datumaro.components.project import Dataset
from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \
import_dm_annotations
from cvat.apps.dataset_manager.util import make_zip_archive
from .registry import dm_env, exporter, importer
@exporter(name='ImageNet', ext='ZIP', version='1.0')
def _export(dst_file, task_data, save_images=False):
extractor = CvatTaskDataExtractor(task_data, include_images=save_images)
extractor = Dataset.from_extractors(extractor) # apply lazy transform
with TemporaryDirectory() as temp_dir:
if save_images:
dm_env.converters.get('imagenet').convert(extractor,
save_dir=temp_dir, save_images=save_images)
else:
dm_env.converters.get('imagenet_txt').convert(extractor,
save_dir=temp_dir, save_images=save_images)
make_zip_archive(temp_dir, dst_file)
@importer(name='ImageNet', ext='ZIP', version='1.0')
def _import(src_file, task_data):
with TemporaryDirectory() as tmp_dir:
zipfile.ZipFile(src_file).extractall(tmp_dir)
if glob(osp.join(tmp_dir, '*.txt')):
dataset = dm_env.make_importer('imagenet_txt')(tmp_dir).make_dataset()
else:
dataset = dm_env.make_importer('imagenet')(tmp_dir).make_dataset()
import_dm_annotations(dataset, task_data)

@ -90,4 +90,5 @@ import cvat.apps.dataset_manager.formats.mot
import cvat.apps.dataset_manager.formats.mots import cvat.apps.dataset_manager.formats.mots
import cvat.apps.dataset_manager.formats.pascal_voc import cvat.apps.dataset_manager.formats.pascal_voc
import cvat.apps.dataset_manager.formats.tfrecord import cvat.apps.dataset_manager.formats.tfrecord
import cvat.apps.dataset_manager.formats.yolo import cvat.apps.dataset_manager.formats.yolo
import cvat.apps.dataset_manager.formats.imagenet

@ -218,8 +218,6 @@ class TaskExportTest(_DbTestBase):
def _generate_task(self, images): def _generate_task(self, images):
task = { task = {
"name": "my task #1", "name": "my task #1",
"owner": '',
"assignee": '',
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -271,6 +269,7 @@ class TaskExportTest(_DbTestBase):
'Segmentation mask 1.1', 'Segmentation mask 1.1',
'TFRecord 1.0', 'TFRecord 1.0',
'YOLO 1.1', 'YOLO 1.1',
'ImageNet 1.0',
}) })
def test_import_formats_query(self): def test_import_formats_query(self):
@ -287,6 +286,7 @@ class TaskExportTest(_DbTestBase):
'Segmentation mask 1.1', 'Segmentation mask 1.1',
'TFRecord 1.0', 'TFRecord 1.0',
'YOLO 1.1', 'YOLO 1.1',
'ImageNet 1.0',
}) })
def test_exports(self): def test_exports(self):
@ -322,6 +322,7 @@ class TaskExportTest(_DbTestBase):
('Segmentation mask 1.1', 'voc'), ('Segmentation mask 1.1', 'voc'),
('TFRecord 1.0', 'tf_detection_api'), ('TFRecord 1.0', 'tf_detection_api'),
('YOLO 1.1', 'yolo'), ('YOLO 1.1', 'yolo'),
('ImageNet 1.0', 'imagenet_txt'),
]: ]:
with self.subTest(format=format_name): with self.subTest(format=format_name):
if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED: if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED:
@ -435,8 +436,6 @@ class FrameMatchingTest(_DbTestBase):
def _generate_task(self, images): def _generate_task(self, images):
task = { task = {
"name": "my task #1", "name": "my task #1",
"owner": '',
"assignee": '',
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [

@ -1,5 +1,5 @@
<!-- <!--
Copyright (C) 2018 Intel Corporation Copyright (C) 2018-2020 Intel Corporation
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--> -->
@ -15,8 +15,7 @@
</head> </head>
<body> <body>
<xmp id="content" style="display: none" <xmp id="content" style="display: none">
>
{% autoescape off %} {% autoescape off %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}

@ -1,7 +1,14 @@
<!-- <!--
Copyright (C) 2018 Intel Corporation Copyright (C) 2018-2020 Intel Corporation
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--> -->
{% extends 'documentation/base_page.html' %} {% block title %} CVAT User Guide {% endblock %} {% block content %} {{ {% extends 'documentation/base_page.html' %}
user_guide }} {% endblock %}
{% block title %}
CVAT User Guide
{% endblock %}
{% block content %}
{{ user_guide }}
{% endblock %}

@ -1,7 +1,8 @@
<!-- <!--
Copyright (C) 2018 Intel Corporation Copyright (C) 2018-2020 Intel Corporation
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--> -->
{% extends 'documentation/base_page.html' %} {% block title %} CVAT XML format {% endblock %} {% block content %} {{ {% extends 'documentation/base_page.html' %}
xml_format }} {% endblock %} {% block title %} CVAT XML format {% endblock %}
{% block content %} {{ xml_format }} {% endblock %}

@ -14,6 +14,7 @@ import av
import numpy as np import numpy as np
from pyunpack import Archive from pyunpack import Archive
from PIL import Image, ImageFile from PIL import Image, ImageFile
from cvat.apps.engine.utils import rotate_image
# fixes: "OSError:broken data stream" when executing line 72 while loading images downloaded from the web # fixes: "OSError:broken data stream" when executing line 72 while loading images downloaded from the web
# see: https://stackoverflow.com/questions/42462431/oserror-broken-data-stream-when-reading-image-file # see: https://stackoverflow.com/questions/42462431/oserror-broken-data-stream-when-reading-image-file
@ -228,6 +229,16 @@ class VideoReader(IMediaReader):
for image in packet.decode(): for image in packet.decode():
frame_num += 1 frame_num += 1
if self._has_frame(frame_num - 1): if self._has_frame(frame_num - 1):
if packet.stream.metadata.get('rotate'):
old_image = image
image = av.VideoFrame().from_ndarray(
rotate_image(
image.to_ndarray(format='bgr24'),
360 - int(container.streams.video[0].metadata.get('rotate'))
),
format ='bgr24'
)
image.pts = old_image.pts
yield (image, self._source_path[0], image.pts) yield (image, self._source_path[0], image.pts)
def __iter__(self): def __iter__(self):
@ -252,7 +263,15 @@ class VideoReader(IMediaReader):
container = self._get_av_container() container = self._get_av_container()
stream = container.streams.video[0] stream = container.streams.video[0]
preview = next(container.decode(stream)) preview = next(container.decode(stream))
return self._get_preview(preview.to_image()) return self._get_preview(preview.to_image() if not stream.metadata.get('rotate') \
else av.VideoFrame().from_ndarray(
rotate_image(
preview.to_ndarray(format='bgr24'),
360 - int(container.streams.video[0].metadata.get('rotate'))
),
format ='bgr24'
).to_image()
)
def get_image_size(self, i): def get_image_size(self, i):
image = (next(iter(self)))[0] image = (next(iter(self)))[0]

@ -6,6 +6,7 @@ import av
from collections import OrderedDict from collections import OrderedDict
import hashlib import hashlib
import os import os
from cvat.apps.engine.utils import rotate_image
class WorkWithVideo: class WorkWithVideo:
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -24,7 +25,6 @@ class WorkWithVideo:
video_stream.thread_type = 'AUTO' video_stream.thread_type = 'AUTO'
return video_stream return video_stream
class AnalyzeVideo(WorkWithVideo): class AnalyzeVideo(WorkWithVideo):
def check_type_first_frame(self): def check_type_first_frame(self):
container = self._open_video_container(self.source_path, mode='r') container = self._open_video_container(self.source_path, mode='r')
@ -76,7 +76,17 @@ class PrepareInfo(WorkWithVideo):
@property @property
def frame_sizes(self): def frame_sizes(self):
container = self._open_video_container(self.source_path, 'r')
frame = next(iter(self.key_frames.values())) frame = next(iter(self.key_frames.values()))
if container.streams.video[0].metadata.get('rotate'):
frame = av.VideoFrame().from_ndarray(
rotate_image(
frame.to_ndarray(format='bgr24'),
360 - int(container.streams.video[0].metadata.get('rotate'))
),
format ='bgr24'
)
self._close_video_container(container)
return (frame.width, frame.height) return (frame.width, frame.height)
def check_key_frame(self, container, video_stream, key_frame): def check_key_frame(self, container, video_stream, key_frame):
@ -150,6 +160,14 @@ class PrepareInfo(WorkWithVideo):
if frame_number < start_chunk_frame_number: if frame_number < start_chunk_frame_number:
continue continue
elif frame_number < end_chunk_frame_number and not ((frame_number - start_chunk_frame_number) % step): elif frame_number < end_chunk_frame_number and not ((frame_number - start_chunk_frame_number) % step):
if video_stream.metadata.get('rotate'):
frame = av.VideoFrame().from_ndarray(
rotate_image(
frame.to_ndarray(format='bgr24'),
360 - int(container.streams.video[0].metadata.get('rotate'))
),
format ='bgr24'
)
yield frame yield frame
elif (frame_number - start_chunk_frame_number) % step: elif (frame_number - start_chunk_frame_number) % step:
continue continue
@ -177,6 +195,14 @@ class UploadedMeta(PrepareInfo):
container.seek(offset=next(iter(self.key_frames.values())), stream=video_stream) container.seek(offset=next(iter(self.key_frames.values())), stream=video_stream)
for packet in container.demux(video_stream): for packet in container.demux(video_stream):
for frame in packet.decode(): for frame in packet.decode():
if video_stream.metadata.get('rotate'):
frame = av.VideoFrame().from_ndarray(
rotate_image(
frame.to_ndarray(format='bgr24'),
360 - int(container.streams.video[0].metadata.get('rotate'))
),
format ='bgr24'
)
self._close_video_container(container) self._close_video_container(container)
return (frame.width, frame.height) return (frame.width, frame.height)

@ -13,6 +13,36 @@ from cvat.apps.engine import models
from cvat.apps.engine.log import slogger from cvat.apps.engine.log import slogger
from cvat.apps.dataset_manager.formats.utils import get_label_color from cvat.apps.dataset_manager.formats.utils import get_label_color
class BasicUserSerializer(serializers.ModelSerializer):
def validate(self, data):
if hasattr(self, 'initial_data'):
unknown_keys = set(self.initial_data.keys()) - set(self.fields.keys())
if unknown_keys:
if set(['is_staff', 'is_superuser', 'groups']) & unknown_keys:
message = 'You do not have permissions to access some of' + \
' these fields: {}'.format(unknown_keys)
else:
message = 'Got unknown fields: {}'.format(unknown_keys)
raise serializers.ValidationError(message)
return data
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name')
ordering = ['-id']
class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(many=True,
slug_field='name', queryset=Group.objects.all())
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email',
'groups', 'is_staff', 'is_superuser', 'is_active', 'last_login',
'date_joined')
read_only_fields = ('last_login', 'date_joined')
write_only_fields = ('password', )
ordering = ['-id']
class AttributeSerializer(serializers.ModelSerializer): class AttributeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -53,16 +83,21 @@ class JobSerializer(serializers.ModelSerializer):
task_id = serializers.ReadOnlyField(source="segment.task.id") task_id = serializers.ReadOnlyField(source="segment.task.id")
start_frame = serializers.ReadOnlyField(source="segment.start_frame") start_frame = serializers.ReadOnlyField(source="segment.start_frame")
stop_frame = serializers.ReadOnlyField(source="segment.stop_frame") stop_frame = serializers.ReadOnlyField(source="segment.stop_frame")
assignee = BasicUserSerializer(allow_null=True, required=False)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
class Meta: class Meta:
model = models.Job model = models.Job
fields = ('url', 'id', 'assignee', 'status', 'start_frame', fields = ('url', 'id', 'assignee', 'assignee_id', 'status', 'start_frame',
'stop_frame', 'task_id') 'stop_frame', 'task_id')
class SimpleJobSerializer(serializers.ModelSerializer): class SimpleJobSerializer(serializers.ModelSerializer):
assignee = BasicUserSerializer(allow_null=True)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True)
class Meta: class Meta:
model = models.Job model = models.Job
fields = ('url', 'id', 'assignee', 'status') fields = ('url', 'id', 'assignee', 'assignee_id', 'status')
class SegmentSerializer(serializers.ModelSerializer): class SegmentSerializer(serializers.ModelSerializer):
jobs = SimpleJobSerializer(many=True, source='job_set') jobs = SimpleJobSerializer(many=True, source='job_set')
@ -239,14 +274,18 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
size = serializers.ReadOnlyField(source='data.size') size = serializers.ReadOnlyField(source='data.size')
image_quality = serializers.ReadOnlyField(source='data.image_quality') image_quality = serializers.ReadOnlyField(source='data.image_quality')
data = serializers.ReadOnlyField(source='data.id') data = serializers.ReadOnlyField(source='data.id')
owner = BasicUserSerializer(required=False)
owner_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
assignee = BasicUserSerializer(allow_null=True, required=False)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
class Meta: class Meta:
model = models.Task model = models.Task
fields = ('url', 'id', 'name', 'mode', 'owner', 'assignee', fields = ('url', 'id', 'name', 'mode', 'owner', 'assignee', 'owner_id', 'assignee_id',
'bug_tracker', 'created_date', 'updated_date', 'overlap', 'bug_tracker', 'created_date', 'updated_date', 'overlap',
'segment_size', 'status', 'labels', 'segments', 'segment_size', 'status', 'labels', 'segments',
'project', 'data_chunk_size', 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data') 'project', 'data_chunk_size', 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data')
read_only_fields = ('mode', 'created_date', 'updated_date', 'status', 'data_chunk_size', read_only_fields = ('mode', 'created_date', 'updated_date', 'status', 'data_chunk_size', 'owner', 'asignee',
'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data') 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data')
write_once_fields = ('overlap', 'segment_size') write_once_fields = ('overlap', 'segment_size')
ordering = ['-id'] ordering = ['-id']
@ -278,8 +317,8 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
# pylint: disable=no-self-use # pylint: disable=no-self-use
def update(self, instance, validated_data): def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name) instance.name = validated_data.get('name', instance.name)
instance.owner = validated_data.get('owner', instance.owner) instance.owner_id = validated_data.get('owner_id', instance.owner_id)
instance.assignee = validated_data.get('assignee', instance.assignee) instance.assignee_id = validated_data.get('assignee_id', instance.assignee_id)
instance.bug_tracker = validated_data.get('bug_tracker', instance.bug_tracker = validated_data.get('bug_tracker',
instance.bug_tracker) instance.bug_tracker)
instance.project = validated_data.get('project', instance.project) instance.project = validated_data.get('project', instance.project)
@ -339,37 +378,6 @@ class ProjectSerializer(serializers.ModelSerializer):
read_only_fields = ('created_date', 'updated_date', 'status') read_only_fields = ('created_date', 'updated_date', 'status')
ordering = ['-id'] ordering = ['-id']
class BasicUserSerializer(serializers.ModelSerializer):
def validate(self, data):
if hasattr(self, 'initial_data'):
unknown_keys = set(self.initial_data.keys()) - set(self.fields.keys())
if unknown_keys:
if set(['is_staff', 'is_superuser', 'groups']) & unknown_keys:
message = 'You do not have permissions to access some of' + \
' these fields: {}'.format(unknown_keys)
else:
message = 'Got unknown fields: {}'.format(unknown_keys)
raise serializers.ValidationError(message)
return data
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name')
ordering = ['-id']
class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(many=True,
slug_field='name', queryset=Group.objects.all())
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email',
'groups', 'is_staff', 'is_superuser', 'is_active', 'last_login',
'date_joined')
read_only_fields = ('last_login', 'date_joined')
write_only_fields = ('password', )
ordering = ['-id']
class ExceptionSerializer(serializers.Serializer): class ExceptionSerializer(serializers.Serializer):
system = serializers.CharField(max_length=255) system = serializers.CharField(max_length=255)
client = serializers.CharField(max_length=255) client = serializers.CharField(max_length=255)

@ -294,6 +294,7 @@ def _create_thread(tid, data):
if settings.USE_CACHE and db_data.storage_method == StorageMethodChoice.CACHE: if settings.USE_CACHE and db_data.storage_method == StorageMethodChoice.CACHE:
for media_type, media_files in media.items(): for media_type, media_files in media.items():
if not media_files: if not media_files:
continue continue

@ -297,47 +297,47 @@ class JobUpdateAPITestCase(APITestCase):
self.assertEqual(response.data["id"], self.job.id) self.assertEqual(response.data["id"], self.job.id)
self.assertEqual(response.data["status"], data.get('status', self.job.status)) self.assertEqual(response.data["status"], data.get('status', self.job.status))
assignee = self.job.assignee.id if self.job.assignee else None assignee = self.job.assignee.id if self.job.assignee else None
self.assertEqual(response.data["assignee"], data.get('assignee', assignee)) self.assertEqual(response.data["assignee"]["id"], data.get('assignee_id', assignee))
self.assertEqual(response.data["start_frame"], self.job.segment.start_frame) self.assertEqual(response.data["start_frame"], self.job.segment.start_frame)
self.assertEqual(response.data["stop_frame"], self.job.segment.stop_frame) self.assertEqual(response.data["stop_frame"], self.job.segment.stop_frame)
def test_api_v1_jobs_id_admin(self): def test_api_v1_jobs_id_admin(self):
data = {"status": StatusChoice.COMPLETED, "assignee": self.owner.id} data = {"status": StatusChoice.COMPLETED, "assignee_id": self.owner.id}
response = self._run_api_v1_jobs_id(self.job.id, self.admin, data) response = self._run_api_v1_jobs_id(self.job.id, self.admin, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.admin, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.admin, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_owner(self): def test_api_v1_jobs_id_owner(self):
data = {"status": StatusChoice.VALIDATION, "assignee": self.annotator.id} data = {"status": StatusChoice.VALIDATION, "assignee_id": self.annotator.id}
response = self._run_api_v1_jobs_id(self.job.id, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id, self.owner, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.owner, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_annotator(self): def test_api_v1_jobs_id_annotator(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.annotator, data) response = self._run_api_v1_jobs_id(self.job.id, self.annotator, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.annotator, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.annotator, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_observer(self): def test_api_v1_jobs_id_observer(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.admin.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.admin.id}
response = self._run_api_v1_jobs_id(self.job.id, self.observer, data) response = self._run_api_v1_jobs_id(self.job.id, self.observer, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.observer, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.observer, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_user(self): def test_api_v1_jobs_id_user(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.user, data) response = self._run_api_v1_jobs_id(self.job.id, self.user, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.user, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.user, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_no_auth(self): def test_api_v1_jobs_id_no_auth(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, None, data) response = self._run_api_v1_jobs_id(self.job.id, None, data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
response = self._run_api_v1_jobs_id(self.job.id + 10, None, data) response = self._run_api_v1_jobs_id(self.job.id + 10, None, data)
@ -356,7 +356,7 @@ class JobPartialUpdateAPITestCase(JobUpdateAPITestCase):
self._check_request(response, data) self._check_request(response, data)
def test_api_v1_jobs_id_admin_partial(self): def test_api_v1_jobs_id_admin_partial(self):
data = {"assignee": self.user.id} data = {"assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id, self.owner, data)
self._check_request(response, data) self._check_request(response, data)
@ -1073,9 +1073,11 @@ class TaskGetAPITestCase(APITestCase):
self.assertEqual(response.data["size"], db_task.data.size) self.assertEqual(response.data["size"], db_task.data.size)
self.assertEqual(response.data["mode"], db_task.mode) self.assertEqual(response.data["mode"], db_task.mode)
owner = db_task.owner.id if db_task.owner else None owner = db_task.owner.id if db_task.owner else None
self.assertEqual(response.data["owner"], owner) response_owner = response.data["owner"]["id"] if response.data["owner"] else None
self.assertEqual(response_owner, owner)
assignee = db_task.assignee.id if db_task.assignee else None assignee = db_task.assignee.id if db_task.assignee else None
self.assertEqual(response.data["assignee"], assignee) response_assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(response_assignee, assignee)
self.assertEqual(response.data["overlap"], db_task.overlap) self.assertEqual(response.data["overlap"], db_task.overlap)
self.assertEqual(response.data["segment_size"], db_task.segment_size) self.assertEqual(response.data["segment_size"], db_task.segment_size)
self.assertEqual(response.data["image_quality"], db_task.data.image_quality) self.assertEqual(response.data["image_quality"], db_task.data.image_quality)
@ -1179,11 +1181,13 @@ class TaskUpdateAPITestCase(APITestCase):
mode = data.get("mode", db_task.mode) mode = data.get("mode", db_task.mode)
self.assertEqual(response.data["mode"], mode) self.assertEqual(response.data["mode"], mode)
owner = db_task.owner.id if db_task.owner else None owner = db_task.owner.id if db_task.owner else None
owner = data.get("owner", owner) owner = data.get("owner_id", owner)
self.assertEqual(response.data["owner"], owner) response_owner = response.data["owner"]["id"] if response.data["owner"] else None
self.assertEqual(response_owner, owner)
assignee = db_task.assignee.id if db_task.assignee else None assignee = db_task.assignee.id if db_task.assignee else None
assignee = data.get("assignee", assignee) assignee = data.get("assignee_id", assignee)
self.assertEqual(response.data["assignee"], assignee) response_assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(response_assignee, assignee)
self.assertEqual(response.data["overlap"], db_task.overlap) self.assertEqual(response.data["overlap"], db_task.overlap)
self.assertEqual(response.data["segment_size"], db_task.segment_size) self.assertEqual(response.data["segment_size"], db_task.segment_size)
image_quality = data.get("image_quality", db_task.data.image_quality) image_quality = data.get("image_quality", db_task.data.image_quality)
@ -1213,7 +1217,7 @@ class TaskUpdateAPITestCase(APITestCase):
def test_api_v1_tasks_id_admin(self): def test_api_v1_tasks_id_admin(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.owner.id, "owner_id": self.owner.id,
"labels": [{ "labels": [{
"name": "non-vehicle", "name": "non-vehicle",
"attributes": [{ "attributes": [{
@ -1229,7 +1233,7 @@ class TaskUpdateAPITestCase(APITestCase):
def test_api_v1_tasks_id_user(self): def test_api_v1_tasks_id_user(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.assignee.id, "owner_id": self.assignee.id,
"labels": [{ "labels": [{
"name": "car", "name": "car",
"attributes": [{ "attributes": [{
@ -1277,7 +1281,7 @@ class TaskPartialUpdateAPITestCase(TaskUpdateAPITestCase):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.owner.id "owner_id": self.owner.id
} }
self._check_api_v1_tasks_id(self.admin, data) self._check_api_v1_tasks_id(self.admin, data)
# Now owner is updated, but self.db_tasks are obsolete # Now owner is updated, but self.db_tasks are obsolete
@ -1300,8 +1304,8 @@ class TaskPartialUpdateAPITestCase(TaskUpdateAPITestCase):
self._check_api_v1_tasks_id(self.user, data) self._check_api_v1_tasks_id(self.user, data)
data = { data = {
"owner": self.observer.id, "owner_id": self.observer.id,
"assignee": self.annotator.id "assignee_id": self.annotator.id
} }
self._check_api_v1_tasks_id(self.user, data) self._check_api_v1_tasks_id(self.user, data)
@ -1339,8 +1343,9 @@ class TaskCreateAPITestCase(APITestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data["name"], data["name"]) self.assertEqual(response.data["name"], data["name"])
self.assertEqual(response.data["mode"], "") self.assertEqual(response.data["mode"], "")
self.assertEqual(response.data["owner"], data.get("owner", user.id)) self.assertEqual(response.data["owner"]["id"], data.get("owner_id", user.id))
self.assertEqual(response.data["assignee"], data.get("assignee")) assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(assignee, data.get("assignee_id", None))
self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", "")) self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", ""))
self.assertEqual(response.data["overlap"], data.get("overlap", None)) self.assertEqual(response.data["overlap"], data.get("overlap", None))
self.assertEqual(response.data["segment_size"], data.get("segment_size", 0)) self.assertEqual(response.data["segment_size"], data.get("segment_size", 0))
@ -1377,7 +1382,7 @@ class TaskCreateAPITestCase(APITestCase):
def test_api_v1_tasks_user(self): def test_api_v1_tasks_user(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.assignee.id, "owner_id": self.assignee.id,
"labels": [{ "labels": [{
"name": "car", "name": "car",
"attributes": [{ "attributes": [{
@ -1548,6 +1553,16 @@ class TaskDataAPITestCase(APITestCase):
video.write(data.read()) video.write(data.read())
cls._image_sizes[filename] = img_sizes cls._image_sizes[filename] = img_sizes
filename = "test_rotated_90_video.mp4"
path = os.path.join(os.path.dirname(__file__), 'assets', 'test_rotated_90_video.mp4')
container = av.open(path, 'r')
for frame in container.decode(video=0):
# pyav ignores rotation record in metadata when decoding frames
img_sizes = [(frame.height, frame.width)] * container.streams.video[0].frames
break
container.close()
cls._image_sizes[filename] = img_sizes
filename = os.path.join("videos", "test_video_1.mp4") filename = os.path.join("videos", "test_video_1.mp4")
path = os.path.join(settings.SHARE_ROOT, filename) path = os.path.join(settings.SHARE_ROOT, filename)
os.makedirs(os.path.dirname(path)) os.makedirs(os.path.dirname(path))
@ -1653,8 +1668,8 @@ class TaskDataAPITestCase(APITestCase):
response = self._get_task(user, task_id) response = self._get_task(user, task_id)
expected_status_code = status.HTTP_200_OK expected_status_code = status.HTTP_200_OK
if user == self.user and "owner" in spec and spec["owner"] != user.id and \ if user == self.user and "owner_id" in spec and spec["owner_id"] != user.id and \
"assignee" in spec and spec["assignee"] != user.id: "assignee_id" in spec and spec["assignee_id"] != user.id:
expected_status_code = status.HTTP_403_FORBIDDEN expected_status_code = status.HTTP_403_FORBIDDEN
self.assertEqual(response.status_code, expected_status_code) self.assertEqual(response.status_code, expected_status_code)
@ -1736,8 +1751,8 @@ class TaskDataAPITestCase(APITestCase):
def _test_api_v1_tasks_id_data(self, user): def _test_api_v1_tasks_id_data(self, user):
task_spec = { task_spec = {
"name": "my task #1", "name": "my task #1",
"owner": self.owner.id, "owner_id": self.owner.id,
"assignee": self.assignee.id, "assignee_id": self.assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -2003,7 +2018,7 @@ class TaskDataAPITestCase(APITestCase):
os.path.join(settings.SHARE_ROOT, "videos") os.path.join(settings.SHARE_ROOT, "videos")
) )
task_spec = { task_spec = {
"name": "my video with meta info task #11", "name": "my video with meta info task #13",
"overlap": 0, "overlap": 0,
"segment_size": 0, "segment_size": 0,
"labels": [ "labels": [
@ -2022,6 +2037,47 @@ class TaskDataAPITestCase(APITestCase):
self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.VIDEO, self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.VIDEO,
self.ChunkType.VIDEO, image_sizes, StorageMethodChoice.CACHE) self.ChunkType.VIDEO, image_sizes, StorageMethodChoice.CACHE)
task_spec = {
"name": "my cached video task #14",
"overlap": 0,
"segment_size": 0,
"labels": [
{"name": "car"},
{"name": "person"},
]
}
task_data = {
"client_files[0]": open(os.path.join(os.path.dirname(__file__), 'assets', 'test_rotated_90_video.mp4'), 'rb'),
"image_quality": 70,
"use_zip_chunks": True
}
image_sizes = self._image_sizes['test_rotated_90_video.mp4']
self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET,
self.ChunkType.VIDEO, image_sizes, StorageMethodChoice.FILE_SYSTEM)
task_spec = {
"name": "my video task #15",
"overlap": 0,
"segment_size": 0,
"labels": [
{"name": "car"},
{"name": "person"},
]
}
task_data = {
"client_files[0]": open(os.path.join(os.path.dirname(__file__), 'assets', 'test_rotated_90_video.mp4'), 'rb'),
"image_quality": 70,
"use_cache": True,
"use_zip_chunks": True
}
image_sizes = self._image_sizes['test_rotated_90_video.mp4']
self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET,
self.ChunkType.VIDEO, image_sizes, StorageMethodChoice.CACHE)
def test_api_v1_tasks_id_data_admin(self): def test_api_v1_tasks_id_data_admin(self):
self._test_api_v1_tasks_id_data(self.admin) self._test_api_v1_tasks_id_data(self.admin)
@ -2034,8 +2090,8 @@ class TaskDataAPITestCase(APITestCase):
def test_api_v1_tasks_id_data_no_auth(self): def test_api_v1_tasks_id_data_no_auth(self):
data = { data = {
"name": "my task #3", "name": "my task #3",
"owner": self.owner.id, "owner_id": self.owner.id,
"assignee": self.assignee.id, "assignee_id": self.assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -2080,8 +2136,8 @@ class JobAnnotationAPITestCase(APITestCase):
def _create_task(self, owner, assignee): def _create_task(self, owner, assignee):
data = { data = {
"name": "my task #1", "name": "my task #1",
"owner": owner.id, "owner_id": owner.id,
"assignee": assignee.id, "assignee_id": assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -3355,6 +3411,9 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase):
+ polygon_shapes_with_attrs + polygon_shapes_with_attrs
annotations["tags"] = tags_with_attrs + tags_wo_attrs annotations["tags"] = tags_with_attrs + tags_wo_attrs
elif annotation_format == "ImageNet 1.0":
annotations["tags"] = tags_wo_attrs
else: else:
raise Exception("Unknown format {}".format(annotation_format)) raise Exception("Unknown format {}".format(annotation_format))

@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import ast import ast
import cv2 as cv
from collections import namedtuple from collections import namedtuple
import importlib import importlib
import sys import sys
@ -74,3 +75,16 @@ def av_scan_paths(*paths):
res = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) res = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if res.returncode: if res.returncode:
raise ValidationError(res.stdout) raise ValidationError(res.stdout)
def rotate_image(image, angle):
height, width = image.shape[:2]
image_center = (width/2, height/2)
matrix = cv.getRotationMatrix2D(image_center, angle, 1.)
abs_cos = abs(matrix[0,0])
abs_sin = abs(matrix[0,1])
bound_w = int(height * abs_sin + width * abs_cos)
bound_h = int(height * abs_cos + width * abs_sin)
matrix[0, 2] += bound_w/2 - image_center[0]
matrix[1, 2] += bound_h/2 - image_center[1]
matrix = cv.warpAffine(image, matrix, (bound_w, bound_h))
return matrix

@ -704,7 +704,16 @@ class JobViewSet(viewsets.GenericViewSet,
return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST) return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST)
return Response(data) return Response(data)
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ("id",)
@method_decorator(name='list', decorator=swagger_auto_schema( @method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[
openapi.Parameter('id',openapi.IN_QUERY,description="A unique number value identifying this user",type=openapi.TYPE_NUMBER),
],
operation_summary='Method provides a paginated list of users registered on the server')) operation_summary='Method provides a paginated list of users registered on the server'))
@method_decorator(name='retrieve', decorator=swagger_auto_schema( @method_decorator(name='retrieve', decorator=swagger_auto_schema(
operation_summary='Method provides information of a specific user')) operation_summary='Method provides information of a specific user'))
@ -716,6 +725,8 @@ class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin): mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
queryset = User.objects.prefetch_related('groups').all().order_by('id') queryset = User.objects.prefetch_related('groups').all().order_by('id')
http_method_names = ['get', 'post', 'head', 'patch', 'delete'] http_method_names = ['get', 'post', 'head', 'patch', 'delete']
search_fields = ('username', 'first_name', 'last_name')
filterset_class = UserFilter
def get_serializer_class(self): def get_serializer_class(self):
user = self.request.user user = self.request.user

@ -1,12 +1,10 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { taskName } from '../../support/const';
context('Actions on polygon', () => { context('Actions on polygon', () => {
const caseId = '10'; const caseId = '10';

@ -1,13 +1,10 @@
/* eslint-disable no-undef */ // Copyright (C) 2020 Intel Corporation
/* //
* Copyright (C) 2020 Intel Corporation // SPDX-License-Identifier: MIT
*
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { taskName } from '../../support/const';
context('Actions on polylines', () => { context('Actions on polylines', () => {
const caseId = '11'; const caseId = '11';

@ -1,12 +1,10 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { taskName } from '../../support/const';
context('Actions on points', () => { context('Actions on points', () => {
const caseId = '12'; const caseId = '12';

@ -45,16 +45,20 @@ context('Merge/split features', () => {
it('Create rectangle shape on first frame', () => { it('Create rectangle shape on first frame', () => {
goCheckFrameNumber(frameNum); goCheckFrameNumber(frameNum);
cy.createRectangle(createRectangleShape2Points); cy.createRectangle(createRectangleShape2Points);
cy.get('#cvat_canvas_shape_1').should('have.attr', 'x').then(xCoords => { cy.get('#cvat_canvas_shape_1')
xCoordinatesObjectFirstFrame = Math.floor(xCoords); .should('have.attr', 'x')
}); .then((xCoords) => {
xCoordinatesObjectFirstFrame = Math.floor(xCoords);
});
}); });
it('Create rectangle shape on third frame with another position', () => { it('Create rectangle shape on third frame with another position', () => {
goCheckFrameNumber(frameNum + 2); goCheckFrameNumber(frameNum + 2);
cy.createRectangle(createRectangleShape2PointsSecond); cy.createRectangle(createRectangleShape2PointsSecond);
cy.get('#cvat_canvas_shape_2').should('have.attr', 'x').then(xCoords => { cy.get('#cvat_canvas_shape_2')
xCoordinatesObjectThirdFrame = Math.floor(xCoords); .should('have.attr', 'x')
}); .then((xCoords) => {
xCoordinatesObjectThirdFrame = Math.floor(xCoords);
});
}); });
it('Merge the objects with "Merge button"', () => { it('Merge the objects with "Merge button"', () => {
cy.get('.cvat-merge-control').click(); cy.get('.cvat-merge-control').click();
@ -65,14 +69,20 @@ context('Merge/split features', () => {
}); });
it('Get a track with keyframes on first and third frame', () => { it('Get a track with keyframes on first and third frame', () => {
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .should('contain', '3')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
goCheckFrameNumber(frameNum + 2); goCheckFrameNumber(frameNum + 2);
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .should('contain', '3')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
}); });
it('On the second frame and on the fourth frame the track is invisible', () => { it('On the second frame and on the fourth frame the track is invisible', () => {
goCheckFrameNumber(frameNum + 1); goCheckFrameNumber(frameNum + 1);
@ -82,29 +92,43 @@ context('Merge/split features', () => {
}); });
it('Go to the second frame and remove "outside" flag from the track. The track now visible.', () => { it('Go to the second frame and remove "outside" flag from the track. The track now visible.', () => {
goCheckFrameNumber(frameNum + 1); goCheckFrameNumber(frameNum + 1);
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-outside').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('.cvat-object-item-button-outside').click();
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
});
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
}); });
it('Remove "keyframe" flag from the track. Track now interpolated between position on the first and the third frames.', () => { it('Remove "keyframe" flag from the track. Track now interpolated between position on the first and the third frames.', () => {
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('#cvat_canvas_shape_3').should('have.attr', 'x').then(xCoords => { cy.get('.cvat-object-item-button-keyframe').click();
// expected 9785 to be within 9642..9928 cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist');
expect(Math.floor(xCoords)).to.be.within(xCoordinatesObjectFirstFrame, xCoordinatesObjectThirdFrame); });
}); cy.get('#cvat_canvas_shape_3')
.should('have.attr', 'x')
.then((xCoords) => {
// expected 9785 to be within 9642..9928
expect(Math.floor(xCoords)).to.be.within(
xCoordinatesObjectFirstFrame,
xCoordinatesObjectThirdFrame,
);
});
}); });
it('On the fourth frame remove "keyframe" flag from the track. The track now visible and "outside" flag is disabled.', () => { it('On the fourth frame remove "keyframe" flag from the track. The track now visible and "outside" flag is disabled.', () => {
goCheckFrameNumber(frameNum + 3); goCheckFrameNumber(frameNum + 3);
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .within(() => {
}); cy.get('.cvat-object-item-button-keyframe').click();
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist');
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
});
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
}); });
it('Split a track with "split" button. Previous track became invisible (has "outside" flag). One more track and it is visible.', () => { it('Split a track with "split" button. Previous track became invisible (has "outside" flag). One more track and it is visible.', () => {
@ -112,14 +136,20 @@ context('Merge/split features', () => {
// A single click does not reproduce the split a track scenario in cypress test. // A single click does not reproduce the split a track scenario in cypress test.
cy.get('#cvat_canvas_shape_3').click().click(); cy.get('#cvat_canvas_shape_3').click().click();
cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden'); cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden');
cy.get('#cvat-objects-sidebar-state-item-4').should('contain', '4').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-4')
cy.get('.cvat-object-item-button-outside-enabled').should('exist'); .should('contain', '4')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-outside-enabled').should('exist');
});
cy.get('#cvat_canvas_shape_5').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_5').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-5').should('contain', '5').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-5')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .should('contain', '5')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
}); });
}); });
}); });

@ -0,0 +1,126 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/// <reference types="cypress" />
import { taskName } from '../../support/const';
context('Group features', () => {
const caseId = '15';
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
switchLabel: false,
firstX: 250,
firstY: 350,
secondX: 350,
secondY: 450,
};
const createRectangleShape2PointsSecond = {
points: 'By 2 Points',
type: 'Shape',
switchLabel: false,
firstX: createRectangleShape2Points.firstX + 300,
firstY: createRectangleShape2Points.firstY,
secondX: createRectangleShape2Points.secondX + 300,
secondY: createRectangleShape2Points.secondY,
};
const createRectangleTrack2Points = {
points: 'By 2 Points',
type: 'Track',
switchLabel: false,
firstX: 250,
firstY: 600,
secondX: 350,
secondY: 700,
};
const createRectangleTrack2PointsSecond = {
points: 'By 2 Points',
type: 'Track',
switchLabel: false,
firstX: createRectangleTrack2Points.firstX + 300,
firstY: createRectangleTrack2Points.firstY,
secondX: createRectangleTrack2Points.secondX + 300,
secondY: createRectangleTrack2Points.secondY,
};
let defaultGroupColor = '';
let shapesGroupColor = '';
let tracksGroupColor = '';
before(() => {
cy.openTaskJob(taskName);
});
describe(`Testing case "${caseId}"`, () => {
it('Create two shapes and two tracks.', () => {
cy.createRectangle(createRectangleShape2Points);
cy.createRectangle(createRectangleShape2PointsSecond);
cy.createRectangle(createRectangleTrack2Points);
cy.createRectangle(createRectangleTrack2PointsSecond);
});
it('Set option "Color by" to "Group".', () => {
cy.changeAppearance('Group');
cy.get('.cvat_canvas_shape').then($listCanvasShapes => {
for (let i=0; i<$listCanvasShapes.length; i++) {
cy.get($listCanvasShapes[i]).should('have.css', 'fill').then($fill => {
defaultGroupColor = $fill;
});
}
});
cy.get('.cvat-objects-sidebar-state-item').then($listObjectsSidebarStateItem => {
for (let i=0; i<$listObjectsSidebarStateItem.length; i++) {
cy.get($listObjectsSidebarStateItem[i]).should('have.css', 'background-color').then($bColorObjectsSidebarStateItem => {
// expected rgba(224, 224, 224, 0.533) to include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorObjectsSidebarStateItem).contain(defaultGroupColor.match(/\d+, \d+, \d+/));
});
}
});
});
it('With group button unite two shapes. They have corresponding colors.', () => {
cy.get('.cvat-group-control').click();
for (const shapeToGroup of ['#cvat_canvas_shape_1', '#cvat_canvas_shape_2']) {
cy.get(shapeToGroup).click();
}
cy.get('.cvat-group-control').click();
for (const groupedShape of ['#cvat_canvas_shape_1', '#cvat_canvas_shape_2']) {
cy.get(groupedShape).should('have.css', 'fill').then($shapesGroupColor => {
// expected rgb(250, 50, 83) to not equal rgb(224, 224, 224)
expect($shapesGroupColor).to.not.equal(defaultGroupColor);
shapesGroupColor = $shapesGroupColor;
});
}
for (const objectSideBarShape of ['#cvat-objects-sidebar-state-item-1', '#cvat-objects-sidebar-state-item-2']) {
cy.get(objectSideBarShape).should('have.css', 'background-color').then($bColorobjectSideBarShape => {
// expected rgba(250, 50, 83, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorobjectSideBarShape).to.not.contain(defaultGroupColor.match(/\d+, \d+, \d+/));
// expected rgba(250, 50, 83, 0.533) to include [ 250, 50, 83, index: 4, input: 'rgb(250, 50, 83)', groups: undefined ]
expect($bColorobjectSideBarShape).to.be.contain(shapesGroupColor.match(/\d+, \d+, \d+/));
});
}
});
it('With group button unite two track. They have corresponding colors.', () => {
cy.get('.cvat-group-control').click();
for (const trackToGroup of ['#cvat_canvas_shape_3', '#cvat_canvas_shape_4']) {
cy.get(trackToGroup).click();
}
cy.get('.cvat-group-control').click();
for (const groupedTrack of ['#cvat_canvas_shape_3', '#cvat_canvas_shape_4']) {
cy.get(groupedTrack).should('have.css', 'fill').then($tracksGroupColor => {
// expected rgb(250, 50, 83) to not equal rgb(224, 224, 224)
expect($tracksGroupColor).to.not.equal(defaultGroupColor);
tracksGroupColor = $tracksGroupColor;
});
}
for (const objectSideBarTrack of ['#cvat-objects-sidebar-state-item-3', '#cvat-objects-sidebar-state-item-4']) {
cy.get(objectSideBarTrack).should('have.css', 'background-color').then($bColorobjectSideBarTrack => {
// expected rgba(52, 209, 183, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorobjectSideBarTrack).to.not.contain(defaultGroupColor.match(/\d+, \d+, \d+/));
// expected rgba(52, 209, 183, 0.533) to include [ 52, 209, 183, index: 4, input: 'rgb(52, 209, 183)', groups: undefined ]
expect($bColorobjectSideBarTrack).to.be.contain(tracksGroupColor.match(/\d+, \d+, \d+/));
});
}
});
});
});

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,12 +1,10 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { taskName } from '../../support/const';
context('Actions on rectangle', () => { context('Actions on rectangle', () => {
const caseId = '8'; const caseId = '8';

@ -1,12 +1,10 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { taskName } from '../../support/const';
context('Actions on Cuboid', () => { context('Actions on Cuboid', () => {
const caseId = '9'; const caseId = '9';

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />
@ -86,7 +84,7 @@ context('Multiple users. Assign task, job.', () => {
it('Assign the task to the second user and logout', () => { it('Assign the task to the second user and logout', () => {
cy.openTask(taskName); cy.openTask(taskName);
cy.get('.cvat-task-details').within(() => { cy.get('.cvat-task-details').within(() => {
cy.get('.cvat-user-selector').click({ force: true }); cy.get('.cvat-user-search-field').click({ force: true });
}); });
cy.contains(secondUserName).click(); cy.contains(secondUserName).click();
cy.logout(); cy.logout();
@ -112,7 +110,7 @@ context('Multiple users. Assign task, job.', () => {
cy.get('[value="tasks"]').click(); cy.get('[value="tasks"]').click();
cy.openTask(taskName); cy.openTask(taskName);
cy.get('.cvat-task-job-list').within(() => { cy.get('.cvat-task-job-list').within(() => {
cy.get('.cvat-user-selector').click({ force: true }); cy.get('.cvat-user-search-field').click({ force: true });
}); });
cy.contains(thirdUserName).click(); cy.contains(thirdUserName).click();
cy.logout(); cy.logout();

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
exports.imageGenerator = imageGenerator; exports.imageGenerator = imageGenerator;
@ -22,7 +20,7 @@ function imageGenerator(args) {
const count = args.count; const count = args.count;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i++) {
const image = new jimp(width, height, color, function (err, image) { new jimp(width, height, color, function (err, image) {
if (err) reject(err); if (err) reject(err);
jimp.loadFont(jimp.FONT_SANS_64_BLACK, function (err, font) { jimp.loadFont(jimp.FONT_SANS_64_BLACK, function (err, font) {
if (err) reject(err); if (err) reject(err);

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
Cypress.Commands.add('imageGenerator', (directory, fileName, width, height, color, posX, posY, message, count) => { Cypress.Commands.add('imageGenerator', (directory, fileName, width, height, color, posX, posY, message, count) => {
return cy.task('imageGenerator', { return cy.task('imageGenerator', {

@ -21,7 +21,7 @@ module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => { on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) { if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--disable-gpu'); launchOptions.args.push('--disable-gpu');
return launchOptions return launchOptions;
} }
}); });
return config; return config;

@ -100,13 +100,14 @@ Cypress.Commands.add('createRectangle', (createRectangleParams) => {
cy.switchLabel(createRectangleParams.labelName); cy.switchLabel(createRectangleParams.labelName);
} }
cy.contains('Draw new rectangle') cy.contains('Draw new rectangle')
.parents('.cvat-draw-shape-popover-content').within(() => { .parents('.cvat-draw-shape-popover-content')
cy.get('.ant-select-selection-selected-value').then(($labelValue) => { .within(() => {
selectedValueGlobal = $labelValue.text(); cy.get('.ant-select-selection-selected-value').then(($labelValue) => {
selectedValueGlobal = $labelValue.text();
});
cy.get('.ant-radio-wrapper').contains(createRectangleParams.points).click();
cy.get('button').contains(createRectangleParams.type).click({ force: true });
}); });
cy.get('.ant-radio-wrapper').contains(createRectangleParams.points).click();
cy.get('button').contains(createRectangleParams.type).click({ force: true });
})
cy.get('.cvat-canvas-container').click(createRectangleParams.firstX, createRectangleParams.firstY); cy.get('.cvat-canvas-container').click(createRectangleParams.firstX, createRectangleParams.firstY);
cy.get('.cvat-canvas-container').click(createRectangleParams.secondX, createRectangleParams.secondY); cy.get('.cvat-canvas-container').click(createRectangleParams.secondX, createRectangleParams.secondY);
if (createRectangleParams.points === 'By 4 Points') { if (createRectangleParams.points === 'By 4 Points') {
@ -131,9 +132,11 @@ Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) =>
const maxId = Math.max(...listCanvasShapeId); const maxId = Math.max(...listCanvasShapeId);
cy.get(`#cvat_canvas_shape_${maxId}`).should('exist').and('be.visible'); cy.get(`#cvat_canvas_shape_${maxId}`).should('exist').and('be.visible');
cy.get(`#cvat-objects-sidebar-state-item-${maxId}`) cy.get(`#cvat-objects-sidebar-state-item-${maxId}`)
.should('contain', maxId).and('contain', `${objectType} ${objectParameters.type.toUpperCase()}`).within(() => { .should('contain', maxId)
cy.get('.ant-select-selection-selected-value').should('have.text', selectedValueGlobal); .and('contain', `${objectType} ${objectParameters.type.toUpperCase()}`)
}); .within(() => {
cy.get('.ant-select-selection-selected-value').should('have.text', selectedValueGlobal);
});
}); });
}); });

@ -1,8 +1,6 @@
/* // Copyright (C) 2020 Intel Corporation
* Copyright (C) 2020 Intel Corporation //
* // SPDX-License-Identifier: MIT
* SPDX-License-Identifier: MIT
*/
/// <reference types="cypress" /> /// <reference types="cypress" />

Loading…
Cancel
Save