CVAT.js (part 1) (#463)

main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 40fe468196
commit 923bcacd77

@ -1,2 +1,3 @@
exclude_paths:
- '**/3rdparty/**'
- '**/engine/js/cvat.js'

@ -6,3 +6,5 @@
/.vscode
/db.sqlite3
/keys
package-lock.json
node_modules

@ -27,6 +27,8 @@
"airbnb",
],
"rules": {
"no-await-in-loop": [0],
"global-require": [0],
"no-new": [0],
"class-methods-use-this": [0],
"no-restricted-properties": [0, {

6
.gitignore vendored

@ -19,3 +19,9 @@ docker-compose.override.yml
__pycache__
*.pyc
._*
# Ignore development npm files
package-lock.json
node_modules
.DS_Store

@ -118,6 +118,32 @@
"cwd": "${workspaceFolder}",
"env": {}
},
{
"name": "cvat.js debug",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/cvatjs",
"runtimeExecutable": "node",
"runtimeArgs": [
"--nolazy",
"--inspect-brk=9230",
"src/api.js"
],
"port": 9230
},
{
"type": "node",
"request": "launch",
"name": "jest debug",
"program": "${workspaceFolder}/cvatjs/node_modules/.bin/jest",
"args": [
"--config",
"${workspaceFolder}/cvatjs/jest.config.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
}
],
"compounds": [
{
@ -129,6 +155,6 @@
"RQ - low",
"git",
]
}
},
]
}

@ -26,6 +26,7 @@ apt-get update && apt-get install -y sudo cpio && \
if [ -f "install_cv_sdk_dependencies.sh" ]; then ./install_cv_sdk_dependencies.sh; \
else ./install_openvino_dependencies.sh; fi && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo
cat ../eula.cfg >> silent.cfg
./install.sh -s silent.cfg

23
cvat-ui/.gitignore vendored

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -0,0 +1,44 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

@ -0,0 +1,14 @@
const { override, fixBabelImports, addLessLoader } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
// modifyVars: { '@primary-color': '#1DA57A' },
}),
);

@ -0,0 +1,44 @@
{
"name": "cvat-ui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "24.0.13",
"@types/node": "12.0.3",
"@types/react": "16.8.19",
"@types/react-dom": "16.8.4",
"antd": "^3.19.1",
"babel-plugin-import": "^1.11.2",
"customize-cra": "^0.2.12",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"react": "^16.8.6",
"react-app-rewired": "^2.1.3",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"source-map-explorer": "^1.8.0",
"typescript": "3.4.5"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -0,0 +1,6 @@
.App {
display: flex;
align-items: center;
justify-content: center;
height: stretch;
}

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import { Button } from 'antd';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<Button type="primary">Button</Button>
</div>
);
}
}
export default App;

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

@ -0,0 +1 @@
/// <reference types="react-scripts" />

@ -0,0 +1,143 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}

File diff suppressed because it is too large Load Diff

@ -10,7 +10,7 @@
userConfirm
*/
window.cvat = window.cvat || {};
window.cvatUI = window.cvatUI || {};
const AutoAnnotationServer = {
start(modelId, taskId, data, success, error, progress, check) {
@ -136,8 +136,8 @@ const AutoAnnotationServer = {
class AutoAnnotationModelManagerView {
constructor() {
const html = `<div class="modal hidden" id="${window.cvat.autoAnnotation.managerWindowId}">
<div class="modal-content" id="${window.cvat.autoAnnotation.managerContentId}">
const html = `<div class="modal hidden" id="${window.cvatUI.autoAnnotation.managerWindowId}">
<div class="modal-content" id="${window.cvatUI.autoAnnotation.managerContentId}">
<div style="float: left; width: 55%; height: 100%;">
<center>
<label class="regular h1"> Uploaded Models </label>
@ -151,48 +151,48 @@ class AutoAnnotationModelManagerView {
<th> Actions </th>
</tr>
</thead>
<tbody id="${window.cvat.autoAnnotation.managerUploadedModelsId}"> </tbody>
<tbody id="${window.cvatUI.autoAnnotation.managerUploadedModelsId}"> </tbody>
</table>
</div>
</div>
<div class="regular" id="${window.cvat.autoAnnotation.uploadContentId}">
<div class="regular" id="${window.cvatUI.autoAnnotation.uploadContentId}">
<center>
<label class="regular h1" id="${window.cvat.autoAnnotation.uploadTitleId}"> Create Model </label>
<label class="regular h1" id="${window.cvatUI.autoAnnotation.uploadTitleId}"> Create Model </label>
</center>
<table>
<tr>
<td style="width: 25%"> <label class="regular h3"> Name: </label> </td>
<td> <input type="text" id="${window.cvat.autoAnnotation.uploadNameInputId}" class="regular h3" style="width: 100%"> </td>
<td> <input type="text" id="${window.cvatUI.autoAnnotation.uploadNameInputId}" class="regular h3" style="width: 100%"> </td>
</tr>
<tr>
<td> <label class="regular h3"> Source: </label> </td>
<td>
<input id="${window.cvat.autoAnnotation.uploadLocalSourceId}" type="radio" name="modelSourceType" value="local" checked>
<label for="${window.cvat.autoAnnotation.uploadLocalSourceId}" class="regular h3"> Local </label>
<input id="${window.cvatUI.autoAnnotation.uploadLocalSourceId}" type="radio" name="modelSourceType" value="local" checked>
<label for="${window.cvatUI.autoAnnotation.uploadLocalSourceId}" class="regular h3"> Local </label>
<br>
<input id="${window.cvat.autoAnnotation.uploadShareSourceId}" type="radio" name="modelSourceType" value="shared">
<label for="${window.cvat.autoAnnotation.uploadShareSourceId}" class="regular h3"> Share </label>
<input id="${window.cvatUI.autoAnnotation.uploadShareSourceId}" type="radio" name="modelSourceType" value="shared">
<label for="${window.cvatUI.autoAnnotation.uploadShareSourceId}" class="regular h3"> Share </label>
</td>
</tr>
<tr id="${window.cvat.autoAnnotation.uploadGloballyBlockId}">
<tr id="${window.cvatUI.autoAnnotation.uploadGloballyBlockId}">
<td> <label class="regular h3"> Upload Globally </label> </td>
<td> <input type="checkbox" id="${window.cvat.autoAnnotation.uploadGloballyId}"> </td>
<td> <input type="checkbox" id="${window.cvatUI.autoAnnotation.uploadGloballyId}"> </td>
</tr>
</table>
<div style="text-align: left;">
<div>
<button id="${window.cvat.autoAnnotation.selectFilesButtonId}" class="regular h3"> Select Files </button>
<label id="${window.cvat.autoAnnotation.selectedFilesId}" class="regular h3" style="margin-left: 10px"> No Files </label>
<input id="${window.cvat.autoAnnotation.localFileSelectorId}" type="file" accept=".bin,.xml,.json,.py" style="display: none" multiple>
<button id="${window.cvatUI.autoAnnotation.selectFilesButtonId}" class="regular h3"> Select Files </button>
<label id="${window.cvatUI.autoAnnotation.selectedFilesId}" class="regular h3" style="margin-left: 10px"> No Files </label>
<input id="${window.cvatUI.autoAnnotation.localFileSelectorId}" type="file" accept=".bin,.xml,.json,.py" style="display: none" multiple>
</div>
</div>
<div>
<div style="float: right; width: 50%; height: 50px;">
<button class="regular h3" id="${window.cvat.autoAnnotation.submitUploadButtonId}"> Submit </button>
<button class="regular h3" id="${window.cvat.autoAnnotation.cancelUploadButtonId}"> Cancel </button>
<button class="regular h3" id="${window.cvatUI.autoAnnotation.submitUploadButtonId}"> Submit </button>
<button class="regular h3" id="${window.cvatUI.autoAnnotation.cancelUploadButtonId}"> Cancel </button>
</div>
<div style="float: left; overflow-y: auto; height: 75px; overflow: auto; width: 100%; word-break: break-word;">
<label class="regular h3 selectable" style="float: left;" id="${window.cvat.autoAnnotation.uploadMessageId}"> </label>
<label class="regular h3 selectable" style="float: left;" id="${window.cvatUI.autoAnnotation.uploadMessageId}"> </label>
</div>
</div>
</div>
@ -200,20 +200,20 @@ class AutoAnnotationModelManagerView {
this.el = $(html);
this.table = this.el.find(`#${window.cvat.autoAnnotation.managerUploadedModelsId}`);
this.globallyBlock = this.el.find(`#${window.cvat.autoAnnotation.uploadGloballyBlockId}`);
this.uploadTitle = this.el.find(`#${window.cvat.autoAnnotation.uploadTitleId}`);
this.uploadNameInput = this.el.find(`#${window.cvat.autoAnnotation.uploadNameInputId}`);
this.uploadMessage = this.el.find(`#${window.cvat.autoAnnotation.uploadMessageId}`);
this.selectedFilesLabel = this.el.find(`#${window.cvat.autoAnnotation.selectedFilesId}`);
this.modelNameInput = this.el.find(`#${window.cvat.autoAnnotation.uploadNameInputId}`);
this.localSource = this.el.find(`#${window.cvat.autoAnnotation.uploadLocalSourceId}`);
this.shareSource = this.el.find(`#${window.cvat.autoAnnotation.uploadShareSourceId}`);
this.cancelButton = this.el.find(`#${window.cvat.autoAnnotation.cancelUploadButtonId}`);
this.submitButton = this.el.find(`#${window.cvat.autoAnnotation.submitUploadButtonId}`);
this.globallyBox = this.el.find(`#${window.cvat.autoAnnotation.uploadGloballyId}`);
this.selectButton = this.el.find(`#${window.cvat.autoAnnotation.selectFilesButtonId}`);
this.localSelector = this.el.find(`#${window.cvat.autoAnnotation.localFileSelectorId}`);
this.table = this.el.find(`#${window.cvatUI.autoAnnotation.managerUploadedModelsId}`);
this.globallyBlock = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyBlockId}`);
this.uploadTitle = this.el.find(`#${window.cvatUI.autoAnnotation.uploadTitleId}`);
this.uploadNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`);
this.uploadMessage = this.el.find(`#${window.cvatUI.autoAnnotation.uploadMessageId}`);
this.selectedFilesLabel = this.el.find(`#${window.cvatUI.autoAnnotation.selectedFilesId}`);
this.modelNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`);
this.localSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadLocalSourceId}`);
this.shareSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadShareSourceId}`);
this.cancelButton = this.el.find(`#${window.cvatUI.autoAnnotation.cancelUploadButtonId}`);
this.submitButton = this.el.find(`#${window.cvatUI.autoAnnotation.submitUploadButtonId}`);
this.globallyBox = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyId}`);
this.selectButton = this.el.find(`#${window.cvatUI.autoAnnotation.selectFilesButtonId}`);
this.localSelector = this.el.find(`#${window.cvatUI.autoAnnotation.localFileSelectorId}`);
this.shareSelector = $('#dashboardShareBrowseModal');
this.shareBrowseTree = $('#dashboardShareBrowser');
this.submitShare = $('#dashboardSubmitBrowseServer');
@ -300,25 +300,23 @@ class AutoAnnotationModelManagerView {
this.shareSelector.removeClass('hidden');
this.shareBrowseTree.jstree({
core: {
data: async function (obj, callback) {
let url = '/api/v1/server/share';
async data(obj, callback) {
const directory = obj.id === '#' ? '' : `${obj.id}/`;
if (obj.id != '#') {
url += `?directory=${obj.id.substr(2)}`;
}
const response = await $.get(url);
const files = Array.from(response, (element) => {
return {
id: `${obj.id}/${element.name}`,
let shareFiles = await window.cvat.server.share(directory);
shareFiles = Array.from(shareFiles, (element) => {
const shareFileInfo = {
id: `${directory}${element.name}`,
children: element.type === 'DIR',
text: element.name,
icon: element.type === 'DIR' ? 'jstree-folder' : 'jstree-file',
}
};
return shareFileInfo;
});
callback.call(this, files);
}
callback.call(this, shareFiles);
},
},
plugins: ['checkbox', 'sort'],
});
@ -370,14 +368,14 @@ class AutoAnnotationModelManagerView {
this.uploadMessage.text('');
const overlay = showOverlay('Send request to the server..');
window.cvat.autoAnnotation.server.update(modelData, () => {
window.cvatUI.autoAnnotation.server.update(modelData, () => {
window.location.reload();
}, (message) => {
overlay.remove();
showMessage(message);
}, (progress) => {
overlay.setMessage(progress);
}, window.cvat.autoAnnotation.server.check, this.id);
}, window.cvatUI.autoAnnotation.server.check, this.id);
} finally {
this.submitButton.prop('disabled', false);
}
@ -386,7 +384,7 @@ class AutoAnnotationModelManagerView {
reset() {
const setBlocked = () => {
if (window.cvat.autoAnnotation.data.admin) {
if (window.cvatUI.autoAnnotation.data.admin) {
this.globallyBlock.removeClass('hidden');
} else {
this.globallyBlock.addClass('hidden');
@ -417,11 +415,11 @@ class AutoAnnotationModelManagerView {
const deleteButtonClickHandler = (event) => {
userConfirm(`Do you actually want to delete the "${event.data.model.name}" model. Are you sure?`, () => {
window.cvat.autoAnnotation.server.delete(event.data.model.id, () => {
const filtered = window.cvat.autoAnnotation.data.models.filter(
window.cvatUI.autoAnnotation.server.delete(event.data.model.id, () => {
const filtered = window.cvatUI.autoAnnotation.data.models.filter(
item => item !== event.data.model,
);
window.cvat.autoAnnotation.data.models = filtered;
window.cvatUI.autoAnnotation.data.models = filtered;
this.reset();
}, (message) => {
showMessage(message);
@ -443,7 +441,7 @@ class AutoAnnotationModelManagerView {
);
};
window.cvat.autoAnnotation.data.models.forEach((model) => {
window.cvatUI.autoAnnotation.data.models.forEach((model) => {
const rowHtml = `<tr>
<td> ${model.name} </td>
<td> ${model.uploadDate} </td>
@ -470,18 +468,18 @@ class AutoAnnotationModelManagerView {
class AutoAnnotationModelRunnerView {
constructor() {
const html = `<div class="modal hidden" id="${window.cvat.autoAnnotation.runnerWindowId}">
<div class="modal-content" id="${window.cvat.autoAnnotation.runnerContentId}">
const html = `<div class="modal hidden" id="${window.cvatUI.autoAnnotation.runnerWindowId}">
<div class="modal-content" id="${window.cvatUI.autoAnnotation.runnerContentId}">
<div style="width: 55%; height: 100%; float: left;">
<center style="height: 10%;">
<label class="regular h1"> Uploaded Models </label>
</center>
<div style="height: 70%; overflow: auto; margin-top: 2%;">
<table class="modelsTable" id="${window.cvat.autoAnnotation.runnerUploadedModelsId}"> </table>
<table class="modelsTable" id="${window.cvatUI.autoAnnotation.runnerUploadedModelsId}"> </table>
</div>
<div>
<input type="checkbox" id="${window.cvat.autoAnnotation.removeCurrentAnnotationId}"/>
<label class="regular h3" for="${window.cvat.autoAnnotation.removeCurrentAnnotationId}"> Remove current annotation </label>
<input type="checkbox" id="${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}"/>
<label class="regular h3" for="${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}"> Remove current annotation </label>
</div>
</div>
<div style="width: 40%; height: 100%; float: left; margin-left: 3%;">
@ -497,14 +495,14 @@ class AutoAnnotationModelRunnerView {
<th style="width: 10%;"> </th>
</tr>
</thead>
<tbody id="${window.cvat.autoAnnotation.annotationLabelsId}">
<tbody id="${window.cvatUI.autoAnnotation.annotationLabelsId}">
</tbody>
</table>
</div>
<div style="float:right;">
<button class="regular h3" style="width: 6em;" id="${window.cvat.autoAnnotation.submitAnnotationId}"> Start </button>
<button class="regular h3" style="width: 6em;" id="${window.cvat.autoAnnotation.cancelAnnotationId}"> Cancel </button>
<button class="regular h3" style="width: 6em;" id="${window.cvatUI.autoAnnotation.submitAnnotationId}"> Start </button>
<button class="regular h3" style="width: 6em;" id="${window.cvatUI.autoAnnotation.cancelAnnotationId}"> Cancel </button>
</div>
</div>
</div>
@ -514,15 +512,15 @@ class AutoAnnotationModelRunnerView {
this.id = null;
this.tid = null;
this.initButton = null;
this.modelsTable = this.el.find(`#${window.cvat.autoAnnotation.runnerUploadedModelsId}`);
this.labelsTable = this.el.find(`#${window.cvat.autoAnnotation.annotationLabelsId}`);
this.modelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.runnerUploadedModelsId}`);
this.labelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.annotationLabelsId}`);
this.active = null;
this.el.find(`#${window.cvat.autoAnnotation.cancelAnnotationId}`).on('click', () => {
this.el.find(`#${window.cvatUI.autoAnnotation.cancelAnnotationId}`).on('click', () => {
this.el.addClass('hidden');
});
this.el.find(`#${window.cvat.autoAnnotation.submitAnnotationId}`).on('click', () => {
this.el.find(`#${window.cvatUI.autoAnnotation.submitAnnotationId}`).on('click', () => {
try {
if (this.id === null) {
throw Error('Please specify a model for an annotation process');
@ -543,20 +541,20 @@ class AutoAnnotationModelRunnerView {
}
const overlay = showOverlay('Request has been sent');
window.cvat.autoAnnotation.server.start(this.id, this.tid, {
reset: $(`#${window.cvat.autoAnnotation.removeCurrentAnnotationId}`).prop('checked'),
window.cvatUI.autoAnnotation.server.start(this.id, this.tid, {
reset: $(`#${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}`).prop('checked'),
labels: mapping,
}, () => {
overlay.remove();
this.initButton[0].setupRun();
window.cvat.autoAnnotation.runner.hide();
window.cvatUI.autoAnnotation.runner.hide();
}, (message) => {
overlay.remove();
this.initButton[0].setupRun();
showMessage(message);
}, () => {
window.location.reload();
}, window.cvat.autoAnnotation.server.check);
}, window.cvatUI.autoAnnotation.server.check);
} catch (error) {
showMessage(error);
}
@ -662,7 +660,7 @@ class AutoAnnotationModelRunnerView {
makeCreator(dlSelect, taskSelect, callback).appendTo(this.labelsTable);
};
window.cvat.autoAnnotation.data.models.forEach((model) => {
window.cvatUI.autoAnnotation.data.models.forEach((model) => {
this.modelsTable.append(
$(`<tr> <td> <label class="regular h3"> ${model.name} (${model.uploadDate}) </label> </td> </tr>`).on(
'click', { model, data }, modelItemClickHandler,
@ -688,7 +686,7 @@ class AutoAnnotationModelRunnerView {
}
}
window.cvat.autoAnnotation = {
window.cvatUI.autoAnnotation = {
managerWindowId: 'annotatorManagerWindow',
managerContentId: 'annotatorManagerContent',
managerUploadedModelsId: 'annotatorManagerUploadedModels',
@ -719,25 +717,26 @@ window.cvat.autoAnnotation = {
};
window.addEventListener('DOMContentLoaded', () => {
window.cvat.autoAnnotation.server = AutoAnnotationServer;
window.cvat.autoAnnotation.manager = new AutoAnnotationModelManagerView();
window.cvat.autoAnnotation.runner = new AutoAnnotationModelRunnerView();
window.cvatUI.autoAnnotation.server = AutoAnnotationServer;
window.cvatUI.autoAnnotation.manager = new AutoAnnotationModelManagerView();
window.cvatUI.autoAnnotation.runner = new AutoAnnotationModelRunnerView();
$('body').append(window.cvat.autoAnnotation.manager.element, window.cvat.autoAnnotation.runner.element);
$(`<button id="${window.cvat.autoAnnotation.managerButtonId}" class="regular h1" style=""> Model Manager</button>`)
$('body').append(window.cvatUI.autoAnnotation.manager.element, window.cvatUI.autoAnnotation.runner.element);
$(`<button id="${window.cvatUI.autoAnnotation.managerButtonId}" class="regular h1" style=""> Model Manager</button>`)
.on('click', () => {
const overlay = showOverlay('The manager are being setup..');
window.cvat.autoAnnotation.manager.reset().show();
window.cvatUI.autoAnnotation.manager.reset().show();
overlay.remove();
}).appendTo('#dashboardManageButtons');
});
window.addEventListener('dashboardReady', (event) => {
const elements = $('.dashboardItem');
const tids = Array.from(elements, el => +el.getAttribute('tid'));
window.cvat.autoAnnotation.server.meta(tids, (data) => {
window.cvat.autoAnnotation.data = data;
window.cvatUI.autoAnnotation.server.meta(tids, (data) => {
window.cvatUI.autoAnnotation.data = data;
elements.each(function setupDashboardItem() {
const elem = $(this);
@ -748,7 +747,7 @@ window.addEventListener('dashboardReady', (event) => {
const self = $(this);
const taskInfo = event.detail.filter(task => task.id === tid)[0];
self.text('Run Auto Annotation').off('click').on('click', () => {
window.cvat.autoAnnotation.runner.reset(taskInfo, self).show();
window.cvatUI.autoAnnotation.runner.reset(taskInfo, self).show();
});
};
@ -756,7 +755,7 @@ window.addEventListener('dashboardReady', (event) => {
const self = $(this);
self.off('click').text('Cancel Auto Annotation').on('click', () => {
userConfirm('Process will be canceled. Are you sure?', () => {
window.cvat.autoAnnotation.server.cancel(tid, () => {
window.cvatUI.autoAnnotation.server.cancel(tid, () => {
this.setupRun();
}, (message) => {
showMessage(message);
@ -764,8 +763,8 @@ window.addEventListener('dashboardReady', (event) => {
});
});
window.cvat.autoAnnotation.server.check(
window.cvat.autoAnnotation.data.run[tid].rq_id,
window.cvatUI.autoAnnotation.server.check(
window.cvatUI.autoAnnotation.data.run[tid].rq_id,
() => {
this.setupRun();
},
@ -780,7 +779,7 @@ window.addEventListener('dashboardReady', (event) => {
);
};
const taskStatus = window.cvat.autoAnnotation.data.run[tid];
const taskStatus = window.cvatUI.autoAnnotation.data.run[tid];
if (taskStatus && ['queued', 'started'].includes(taskStatus.status)) {
button[0].setupCancel();
} else {

@ -0,0 +1,107 @@
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*/
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
}
.pagination > li > a,
.pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #337ab7;
text-decoration: none;
background-color: #ffffff;
border: 1px solid #dddddd;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
z-index: 2;
color: #23527c;
background-color: #eeeeee;
border-color: #dddddd;
}
.pagination > li:first-child > a,
.pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination > li:last-child > a,
.pagination > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 3;
color: #ffffff;
cursor: default;
background-color: #337ab7;
border-color: #337ab7;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #777777;
cursor: not-allowed;
background-color: #ffffff;
border-color: #dddddd;
}
.pagination-lg > li > a,
.pagination-lg > li > span {
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
}
.pagination-lg > li:first-child > a,
.pagination-lg > li:first-child > span {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.pagination-lg > li:last-child > a,
.pagination-lg > li:last-child > span {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.pagination-sm > li > a,
.pagination-sm > li > span {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
}
.pagination-sm > li:first-child > a,
.pagination-sm > li:first-child > span {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.pagination-sm > li:last-child > a,
.pagination-sm > li:last-child > span {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}

@ -0,0 +1,364 @@
/*!
* jQuery pagination plugin v1.4.2
* http://josecebe.github.io/twbs-pagination/
*
* Copyright 2014-2018, Eugene Simakin
* Released under Apache 2.0 license
* http://apache.org/licenses/LICENSE-2.0.html
*/
(function ($, window, document, undefined) {
'use strict';
var old = $.fn.twbsPagination;
// PROTOTYPE AND CONSTRUCTOR
var TwbsPagination = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.twbsPagination.defaults, options);
if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) {
throw new Error('Start page option is incorrect');
}
this.options.totalPages = parseInt(this.options.totalPages);
if (isNaN(this.options.totalPages)) {
throw new Error('Total pages option is not correct!');
}
this.options.visiblePages = parseInt(this.options.visiblePages);
if (isNaN(this.options.visiblePages)) {
throw new Error('Visible pages option is not correct!');
}
if (this.options.beforePageClick instanceof Function) {
this.$element.first().on('beforePage', this.options.beforePageClick);
}
if (this.options.onPageClick instanceof Function) {
this.$element.first().on('page', this.options.onPageClick);
}
// hide if only one page exists
if (this.options.hideOnlyOnePage && this.options.totalPages == 1) {
if (this.options.initiateStartPageClick) {
this.$element.trigger('page', 1);
}
return this;
}
if (this.options.href) {
this.options.startPage = this.getPageFromQueryString();
if (!this.options.startPage) {
this.options.startPage = 1;
}
}
var tagName = (typeof this.$element.prop === 'function') ?
this.$element.prop('tagName') : this.$element.attr('tagName');
if (tagName === 'UL') {
this.$listContainer = this.$element;
} else {
var elements = this.$element;
var $newListContainer = $([]);
elements.each(function(index) {
var $newElem = $("<ul></ul>");
$(this).append($newElem);
$newListContainer.push($newElem[0]);
});
this.$listContainer = $newListContainer;
this.$element = $newListContainer;
}
this.$listContainer.addClass(this.options.paginationClass);
if (this.options.initiateStartPageClick) {
this.show(this.options.startPage);
} else {
this.currentPage = this.options.startPage;
this.render(this.getPages(this.options.startPage));
this.setupEvents();
}
return this;
};
TwbsPagination.prototype = {
constructor: TwbsPagination,
destroy: function () {
this.$element.empty();
this.$element.removeData('twbs-pagination');
this.$element.off('page');
return this;
},
show: function (page) {
if (page < 1 || page > this.options.totalPages) {
throw new Error('Page is incorrect.');
}
this.currentPage = page;
this.$element.trigger('beforePage', page);
var pages = this.getPages(page);
this.render(pages);
this.setupEvents();
this.$element.trigger('page', page);
return pages;
},
enable: function () {
this.show(this.currentPage);
},
disable: function () {
var _this = this;
this.$listContainer.off('click').on('click', 'li', function (evt) {
evt.preventDefault();
});
this.$listContainer.children().each(function () {
var $this = $(this);
if (!$this.hasClass(_this.options.activeClass)) {
$(this).addClass(_this.options.disabledClass);
}
});
},
buildListItems: function (pages) {
var listItems = [];
if (this.options.first) {
listItems.push(this.buildItem('first', 1));
}
if (this.options.prev) {
var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1;
listItems.push(this.buildItem('prev', prev));
}
for (var i = 0; i < pages.numeric.length; i++) {
listItems.push(this.buildItem('page', pages.numeric[i]));
}
if (this.options.next) {
var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages;
listItems.push(this.buildItem('next', next));
}
if (this.options.last) {
listItems.push(this.buildItem('last', this.options.totalPages));
}
return listItems;
},
buildItem: function (type, page) {
var $itemContainer = $('<li></li>'),
$itemContent = $('<a></a>'),
itemText = this.options[type] ? this.makeText(this.options[type], page) : page;
$itemContainer.addClass(this.options[type + 'Class']);
$itemContainer.data('page', page);
$itemContainer.data('page-type', type);
$itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText));
return $itemContainer;
},
getPages: function (currentPage) {
var pages = [];
var half = Math.floor(this.options.visiblePages / 2);
var start = currentPage - half + 1 - this.options.visiblePages % 2;
var end = currentPage + half;
var visiblePages = this.options.visiblePages;
if (visiblePages > this.options.totalPages) {
visiblePages = this.options.totalPages;
}
// handle boundary case
if (start <= 0) {
start = 1;
end = visiblePages;
}
if (end > this.options.totalPages) {
start = this.options.totalPages - visiblePages + 1;
end = this.options.totalPages;
}
var itPage = start;
while (itPage <= end) {
pages.push(itPage);
itPage++;
}
return {"currentPage": currentPage, "numeric": pages};
},
render: function (pages) {
var _this = this;
this.$listContainer.children().remove();
var items = this.buildListItems(pages);
$.each(items, function(key, item){
_this.$listContainer.append(item);
});
this.$listContainer.children().each(function () {
var $this = $(this),
pageType = $this.data('page-type');
switch (pageType) {
case 'page':
if ($this.data('page') === pages.currentPage) {
$this.addClass(_this.options.activeClass);
}
break;
case 'first':
$this.toggleClass(_this.options.disabledClass, pages.currentPage === 1);
break;
case 'last':
$this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages);
break;
case 'prev':
$this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1);
break;
case 'next':
$this.toggleClass(_this.options.disabledClass,
!_this.options.loop && pages.currentPage === _this.options.totalPages);
break;
default:
break;
}
});
},
setupEvents: function () {
var _this = this;
this.$listContainer.off('click').on('click', 'li', function (evt) {
var $this = $(this);
if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) {
return false;
}
// Prevent click event if href is not set.
!_this.options.href && evt.preventDefault();
_this.show(parseInt($this.data('page')));
});
},
changeTotalPages: function(totalPages, currentPage) {
this.options.totalPages = totalPages;
return this.show(currentPage);
},
makeHref: function (page) {
return this.options.href ? this.generateQueryString(page) : "#";
},
makeText: function (text, page) {
return text.replace(this.options.pageVariable, page)
.replace(this.options.totalPagesVariable, this.options.totalPages)
},
getPageFromQueryString: function (searchStr) {
var search = this.getSearchString(searchStr),
regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'),
page = regex.exec(search);
if (!page || !page[2]) {
return null;
}
page = decodeURIComponent(page[2]);
page = parseInt(page);
if (isNaN(page)) {
return null;
}
return page;
},
generateQueryString: function (pageNumber, searchStr) {
var search = this.getSearchString(searchStr),
regex = new RegExp(this.options.pageVariable + '=*[^&#]*');
if (!search) return '';
return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber);
},
getSearchString: function (searchStr) {
var search = searchStr || window.location.search;
if (search === '') {
return null;
}
if (search.indexOf('?') === 0) search = search.substr(1);
return search;
},
getCurrentPage: function () {
return this.currentPage;
},
getTotalPages: function () {
return this.options.totalPages;
}
};
// PLUGIN DEFINITION
$.fn.twbsPagination = function (option) {
var args = Array.prototype.slice.call(arguments, 1);
var methodReturn;
var $this = $(this);
var data = $this.data('twbs-pagination');
var options = typeof option === 'object' ? option : {};
if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) ));
if (typeof option === 'string') methodReturn = data[ option ].apply(data, args);
return ( methodReturn === undefined ) ? $this : methodReturn;
};
$.fn.twbsPagination.defaults = {
totalPages: 1,
startPage: 1,
visiblePages: 5,
initiateStartPageClick: true,
hideOnlyOnePage: false,
href: false,
pageVariable: '{{page}}',
totalPagesVariable: '{{total_pages}}',
page: null,
first: 'First',
prev: 'Previous',
next: 'Next',
last: 'Last',
loop: false,
beforePageClick: null,
onPageClick: null,
paginationClass: 'pagination',
nextClass: 'page-item next',
prevClass: 'page-item prev',
lastClass: 'page-item last',
firstClass: 'page-item first',
pageClass: 'page-item',
activeClass: 'active',
disabledClass: 'disabled',
anchorClass: 'page-link'
};
$.fn.twbsPagination.Constructor = TwbsPagination;
$.fn.twbsPagination.noConflict = function () {
$.fn.twbsPagination = old;
return this;
};
$.fn.twbsPagination.version = "1.4.2";
})(window.jQuery, window, document);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -7,22 +7,16 @@
/* global
AnnotationParser:false
userConfirm:false
createExportContainer:false
dumpAnnotationRequest: false
dumpAnnotation:false
LabelsInfo:false
showMessage:false
showOverlay:false
*/
'use strict';
class TaskView {
constructor(details, ondelete, onupdate) {
this.init(details);
constructor(task) {
this.init(task);
this._ondelete = ondelete;
this._onupdate = onupdate;
this._UI = null;
}
@ -31,73 +25,74 @@ class TaskView {
this._UI.find('.dashboardJobList').empty();
}
_remove() {
$.ajax ({
url: `/api/v1/tasks/${this._id}`,
type: 'DELETE',
success: () => {
this._ondelete(this._id);
this._disable();
},
error: (errorData) => {
const message = `Can not build CVAT dashboard. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
async _remove() {
try {
await this._task.delete();
} catch (exception) {
let { message } = exception;
if (exception instanceof window.cvat.exceptions.ServerError) {
message += ` Code: ${exception.code}`;
}
});
showMessage(message);
}
this._disable();
}
_update() {
$('#dashboardUpdateModal').remove();
const dashboardUpdateModal = $($('#dashboardUpdateTemplate').html()).appendTo('body');
$('#dashboardOldLabels').prop('value', LabelsInfo.serialize(this._labels));
// TODO: Use JSON labels format instead of custom
$('#dashboardOldLabels').prop('value', LabelsInfo.serialize(this._task.labels.map(el => el.toJSON())));
$('#dashboardCancelUpdate').on('click', () => {
dashboardUpdateModal.remove();
});
$('#dashboardSubmitUpdate').on('click', () => {
let jsonLabels = null;
try {
const body = {
labels: LabelsInfo.deserialize($('#dashboardNewLabels').prop('value'))
};
$.ajax({
url: `/api/v1/tasks/${this._id}`,
type: 'PATCH',
contentType: 'application/json',
data: JSON.stringify(body)
}).done(() => {
this._onupdate();
dashboardUpdateModal.remove();
showMessage('Task has been successfully updated');
}).fail((errorData) => {
const message = `Can not build CVAT dashboard. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
});
jsonLabels = LabelsInfo.deserialize($('#dashboardNewLabels').prop('value'));
} catch (exception) {
showMessage(exception);
return;
}
try {
const labels = jsonLabels.map(label => new window.cvat.classes.Label(label));
this._task.labels = labels;
this._task.save();
showMessage('Task has been successfully updated');
} catch (exception) {
let { message } = exception;
if (exception instanceof window.cvat.exceptions.ServerError) {
message += ` Code: ${exception.code}`;
}
showMessage(message);
}
dashboardUpdateModal.remove();
});
}
_upload() {
async function saveChunk(parsed) {
const CHUNK_SIZE = 30000;
let chunk = null;
class Chunk {
constructor() {
this.shapes = [];
this.tracks = [];
this.tags = [];
this.tags = [];
this.capasity = CHUNK_SIZE;
this.version = 0;
}
length() {
return this.tags.length +
this.shapes.length +
this.tracks.reduce((sum, track) => sum + track.shapes.length, 0);
return this.tags.length
+ this.shapes.length
+ this.tracks.reduce((sum, track) => sum + track.shapes.length, 0);
}
isFull() {
@ -111,8 +106,9 @@ class TaskView {
clear() {
this.shapes = [];
this.tracks = [];
this.tags = [];
this.tags = [];
}
export() {
return {
shapes: this.shapes,
@ -121,6 +117,7 @@ class TaskView {
version: this.version,
};
}
async save(taskID) {
try {
const response = await $.ajax({
@ -137,29 +134,30 @@ class TaskView {
}
}
const splitAndSave = async (chunk, prop, splitStep) => {
for(let start = 0; start < parsed[prop].length; start += splitStep) {
Array.prototype.push.apply(chunk[prop], parsed[prop].slice(start, start + splitStep));
if (chunk.isFull()) {
await chunk.save(this._id);
const splitAndSave = async (chunkForSave, prop, splitStep) => {
for (let start = 0; start < parsed[prop].length; start += splitStep) {
Array.prototype.push.apply(chunkForSave[prop],
parsed[prop].slice(start, start + splitStep));
if (chunkForSave.isFull()) {
await chunkForSave.save(this._task.id);
}
}
// save tail
if (!chunk.isEmpty()) {
await chunk.save(this._id);
if (!chunkForSave.isEmpty()) {
await chunkForSave.save(this._task.id);
}
};
let chunk = new Chunk();
// FIXME tags aren't supported by parser
chunk = new Chunk();
// TODO tags aren't supported by parser
// await split(chunk, "tags", CHUNK_SIZE);
await splitAndSave(chunk, "shapes", CHUNK_SIZE);
await splitAndSave(chunk, "tracks", 1);
await splitAndSave(chunk, 'shapes', CHUNK_SIZE);
await splitAndSave(chunk, 'tracks', 1);
}
async function save(parsed) {
await $.ajax({
url: `/api/v1/tasks/${this._id}/annotations`,
url: `/api/v1/tasks/${this._task.id}/annotations`,
type: 'DELETE',
});
@ -169,12 +167,12 @@ class TaskView {
async function onload(overlay, text) {
try {
overlay.setMessage('Required data are being downloaded from the server..');
const imageCache = await $.get(`/api/v1/tasks/${this._id}/frames/meta`);
const labelsCopy = JSON.parse(JSON.stringify(this._labels));
const imageCache = await $.get(`/api/v1/tasks/${this._task.id}/frames/meta`);
const labelsCopy = JSON.parse(JSON.stringify(this._task.labels
.map(el => el.toJSON())));
const parser = new AnnotationParser({
start: 0,
stop: this._size,
flipped: this._flipped,
stop: this._task.size,
image_meta_data: imageCache,
}, new LabelsInfo(labelsCopy));
@ -186,13 +184,13 @@ class TaskView {
const message = 'Annotation have been successfully uploaded';
showMessage(message);
} catch(errorData) {
} catch (errorData) {
let message = null;
if (typeof(errorData) === 'string') {
if (typeof (errorData) === 'string') {
message = `Can not upload annotations. ${errorData}`;
} else {
message = `Can not upload annotations. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
message = `Can not upload annotations. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
}
showMessage(message);
} finally {
@ -200,15 +198,15 @@ class TaskView {
}
}
$('<input type="file" accept="text/xml">').on('change', (e) => {
const file = e.target.files[0];
$(e.target).remove();
$('<input type="file" accept="text/xml">').on('change', (onChangeEvent) => {
const file = onChangeEvent.target.files[0];
$(onChangeEvent.target).remove();
if (file) {
const overlay = showOverlay('File is being parsed..');
const fileReader = new FileReader();
fileReader.onload = (e) => {
onload.call(this, overlay, e.target.result);
}
fileReader.onload = (onloadEvent) => {
onload.call(this, overlay, onloadEvent.target.result);
};
fileReader.readAsText(file);
}
}).click();
@ -217,7 +215,7 @@ class TaskView {
async _dump(button) {
button.disabled = true;
try {
await dumpAnnotationRequest(this._id, this._name);
await dumpAnnotationRequest(this._task.id, this._task.name);
} catch (error) {
showMessage(error.message);
} finally {
@ -225,59 +223,53 @@ class TaskView {
}
}
init(details) {
for (let prop in details) {
this[`_${prop}`] = details[prop];
}
init(task) {
this._task = task;
}
render(baseURL) {
const self = this;
this._UI = $(`<div tid=${this._id} class="dashboardItem"> </div>`).append(
this._UI = $(`<div tid=${this._task.id} class="dashboardItem"> </div>`).append(
$(`<center class="dashboardTitleWrapper">
<label class="semiBold h1 selectable"> ${this._name} </label>
</center>`)
<label class="semiBold h1 selectable"> ${this._task.name} </label>
</center>`),
).append(
$(`<center class="dashboardTitleWrapper">
<label class="regular selectable"> ${this._status} </label>
</center>`)
<label class="regular selectable"> ${this._task.status} </label>
</center>`),
).append(
$('<div class="dashboardTaskIntro"> </div>').css({
'background-image': `url("/api/v1/tasks/${this._id}/frames/0")`
})
'background-image': `url("/api/v1/tasks/${this._task.id}/frames/0")`,
}),
);
const buttonsContainer = $(`<div class="dashboardButtonsUI"> </div>`).appendTo(this._UI);
const buttonsContainer = $('<div class="dashboardButtonsUI"> </div>').appendTo(this._UI);
$('<button class="regular dashboardButtonUI"> Dump Annotation </button>').on('click', (e) => {
self._dump(e.target);
this._dump(e.target);
}).appendTo(buttonsContainer);
$('<button class="regular dashboardButtonUI"> Upload Annotation </button>').on('click', () => {
userConfirm("The current annotation will be lost. Are you sure?", () => self._upload());
userConfirm('The current annotation will be lost. Are you sure?', () => this._upload());
}).appendTo(buttonsContainer);
$('<button class="regular dashboardButtonUI"> Update Task </button>').on('click', () => {
self._update();
this._update();
}).appendTo(buttonsContainer);
$('<button class="regular dashboardButtonUI"> Delete Task </button>').on('click', () => {
userConfirm("The task will be removed. Are you sure?", () => self._remove());
userConfirm('The task will be removed. Are you sure?', () => this._remove());
}).appendTo(buttonsContainer);
if (this._bug_tracker) {
if (this._task.bugTracker) {
$('<button class="regular dashboardButtonUI"> Open Bug Tracker </button>').on('click', () => {
window.open(this._bug_tracker);
window.open.call(window, this._task.bugTracker);
}).appendTo(buttonsContainer);
}
const jobsContainer = $(`<table class="dashboardJobList regular">`);
for (let segment of this._segments) {
for (let job of segment.jobs) {
const link = `${baseURL}?id=${job.id}`;
jobsContainer.append($(`<tr> <td> <a href="${link}"> ${link} </a> </td> </tr>`));
}
const jobsContainer = $('<table class="dashboardJobList regular">');
for (const job of this._task.jobs) {
const link = `${baseURL}?id=${job.id}`;
jobsContainer.append($(`<tr> <td> <a href="${link}"> ${link} </a> </td> </tr>`));
}
this._UI.append($(`
@ -304,6 +296,7 @@ class DashboardView {
this._maxUploadCount = metaData.max_upload_count;
this._baseURL = metaData.base_url;
this._sharePath = metaData.share_path;
this._params = {};
this._setupList();
this._setupTaskSearch();
@ -313,114 +306,105 @@ class DashboardView {
_setupList() {
const dashboardList = $('#dashboardList');
const dashboardPagination = $('#dashboardPagination');
const baseURL = this._baseURL;
let overlay = null;
dashboardPagination.pagination({
dataSource: `/api/v1/tasks${window.location.search}`,
locator: 'results',
alias: {
pageNumber: 'page',
},
totalNumberLocator: function(response) {
return response.count;
},
ajax: {
beforeSend() {
overlay = showOverlay('Loading..');
},
},
callback: function(pageList) {
if (overlay) {
const defaults = {
totalPages: 1,
visiblePages: 7,
onPageClick: async (_, page) => {
dashboardPagination.css({
visibility: 'hidden',
});
const overlay = showOverlay('Loading..');
dashboardList.empty();
let tasks = null;
try {
tasks = await window.cvat.tasks.get(Object.assign({}, {
page,
}, this._params));
} catch (exception) {
let { message } = exception;
if (exception instanceof window.cvat.exceptions.ServerError) {
message += ` Code: ${exception.code}`;
}
showMessage(message);
return;
} finally {
overlay.remove();
overlay = null;
}
dashboardList.empty();
for (let details of pageList) {
const detailsCopy = JSON.parse(JSON.stringify(details));
const taskView = new TaskView(detailsCopy, () => {
// on delete task callback
details.removed = true
}, () => {
// on update task callback
$.get(`/api/v1/tasks/${details.id}`).done((taskData) => {
Object.assign(details, taskData);
taskView.init(details);
}).fail((errorData) => {
const message = `Can not get task from server. Showed info may be obsolete. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
})
});
let startPage = dashboardPagination.twbsPagination('getCurrentPage');
if (!Number.isInteger(startPage)) {
startPage = 1;
}
dashboardPagination.twbsPagination('destroy');
dashboardPagination.twbsPagination(Object.assign({}, defaults, {
totalPages: Math.max(1, Math.ceil(tasks.count / 10)),
startPage,
initiateStartPageClick: false,
}));
for (const task of tasks) {
const taskView = new TaskView(task);
dashboardList.append(taskView.render(baseURL));
}
dashboardPagination.css({
'margin-left': (window.screen.width - dashboardPagination.width()) / 2,
visibility: 'visible',
});
window.dispatchEvent(new CustomEvent('dashboardReady', {
detail: JSON.parse(JSON.stringify(pageList))
detail: tasks,
}));
},
};
const pages = $('.paginationjs-pages');
pages.css('margin-left', (window.screen.width - pages.width()) / 2);
}
});
dashboardPagination.twbsPagination(defaults);
}
_setupTaskSearch() {
const dashboardPagination = $('#dashboardPagination');
const searchInput = $('#dashboardSearchInput');
const searchSubmit = $('#dashboardSearchSubmit');
searchInput.on('keypress', (e) => {
if (e.keyCode != 13) {
if (e.keyCode !== 13) {
return;
}
const params = {};
this._params = {};
const search = e.target.value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim();
for (let field of ['name', 'mode', 'owner', 'assignee', 'status', 'id']) {
for (let param of search.split(' and ')) {
for (const field of ['name', 'mode', 'owner', 'assignee', 'status', 'id']) {
for (let param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
if (param.includes(':')) {
param = param.split(':');
if (param[0] === field && param[1]) {
params[field] = param[1];
[, this._params[field]] = param;
}
}
}
}
if (!Object.keys(params).length && search.length) {
params['search'] = search;
if ('id' in this._params) {
this._params.id = +this._params.id;
}
if (Object.keys(params).length) {
const searchParams = new URLSearchParams();
for (let key in params) {
searchParams.set(key, params[key]);
}
window.location.search = searchParams.toString();
} else {
window.location.search = '';
if (!Object.keys(this._params).length) {
this._params.search = search;
}
dashboardPagination.twbsPagination('show', 1);
});
searchSubmit.on('click', function() {
let e = $.Event('keypress');
searchSubmit.on('click', () => {
const e = $.Event('keypress');
e.keyCode = 13;
searchInput.trigger(e);
});
const searchParams = new URLSearchParams(window.location.search.substring(1));
if (searchParams.get('all')) {
searchInput.prop('value', searchParams.get('all'));
} else {
let search = '';
for (let field of ['name', 'mode', 'owner', 'assignee', 'status']) {
const fieldVal = searchParams.get(field);
if (fieldVal) {
search += `${field}: ${fieldVal} and `;
}
}
searchInput.prop('value', search.slice(0, -5));
}
}
_setupCreateDialog() {
@ -544,13 +528,61 @@ class DashboardView {
let frameFilter = '';
let files = [];
function updateSelectedFiles() {
switch (files.length) {
case 0:
filesLabel.text('No Files');
break;
case 1:
filesLabel.text(typeof (files[0]) === 'string' ? files[0] : files[0].name);
break;
default:
filesLabel.text(`${files.length} files`);
}
}
function validateName() {
const math = name.match('[a-zA-Z0-9_]+');
return math !== null;
}
function validateLabels() {
try {
const result = LabelsInfo.deserialize(labels);
return result.length;
} catch (error) {
return false;
}
}
function validateBugTracker() {
return !bugTrackerLink || !!bugTrackerLink.match(/^http[s]?/);
}
function validateSegmentSize() {
return (segmentSize >= 100 && segmentSize <= 50000);
}
function validateOverlapSize() {
return (overlapSize >= 0 && overlapSize <= segmentSize - 1);
}
dashboardCreateTaskButton.on('click', () => {
$('#dashboardCreateModal').removeClass('hidden');
});
nameInput.on('change', (e) => name = e.target.value);
bugTrackerInput.on('change', (e) => bugTrackerLink = e.target.value.trim());
labelsInput.on('change', (e) => labels = e.target.value);
nameInput.on('change', (e) => {
name = e.target.value;
});
bugTrackerInput.on('change', (e) => {
bugTrackerLink = e.target.value.trim();
});
labelsInput.on('change', (e) => {
labels = e.target.value;
});
localSourceRadio.on('click', () => {
if (source === 'local') {
@ -573,46 +605,44 @@ class DashboardView {
selectFiles.on('click', () => {
if (source === 'local') {
localFileSelector.click();
}
else {
} else {
shareBrowseTree.jstree('refresh');
shareFileSelector.removeClass('hidden');
shareBrowseTree.jstree({
core: {
data: async function (obj, callback) {
let url = '/api/v1/server/share';
async data(obj, callback) {
const directory = obj.id === '#' ? '' : `${obj.id}/`;
if (obj.id != '#') {
url += `?directory=${obj.id.substr(2)}`;
}
const response = await $.get(url);
const files = Array.from(response, (element) => {
return {
id: `${obj.id}/${element.name}`,
let shareFiles = await window.cvat.server.share(directory);
shareFiles = Array.from(shareFiles, (element) => {
const shareFileInfo = {
id: `${directory}${element.name}`,
children: element.type === 'DIR',
text: element.name,
icon: element.type === 'DIR' ? 'jstree-folder' : 'jstree-file',
}
};
return shareFileInfo;
});
callback.call(this, files);
}
callback.call(this, shareFiles);
},
},
plugins: ['checkbox', 'sort'],
});
}
});
localFileSelector.on('change', function(e) {
files = e.target.files;
localFileSelector.on('change', (e) => {
const localFiles = e.target.files;
files = localFiles;
updateSelectedFiles();
});
cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden'));
submitBrowseServer.on('click', () => {
if (!createModal.hasClass('hidden')) {
files = Array.from(shareBrowseTree.jstree(true).get_selected(), (el) => el.substr(2));
files = Array.from(shareBrowseTree.jstree(true).get_selected());
cancelBrowseServer.click();
updateSelectedFiles();
}
@ -633,7 +663,7 @@ class DashboardView {
const value = Math.clamp(
+segmentSizeInput.prop('value'),
+segmentSizeInput.prop('min'),
+segmentSizeInput.prop('max')
+segmentSizeInput.prop('max'),
);
segmentSizeInput.prop('value', value);
@ -644,7 +674,7 @@ class DashboardView {
const value = Math.clamp(
+overlapSizeInput.prop('value'),
+overlapSizeInput.prop('min'),
+overlapSizeInput.prop('max')
+overlapSizeInput.prop('max'),
);
overlapSizeInput.prop('value', value);
@ -655,7 +685,7 @@ class DashboardView {
const value = Math.clamp(
+imageQualityInput.prop('value'),
+imageQualityInput.prop('min'),
+imageQualityInput.prop('max')
+imageQualityInput.prop('max'),
);
imageQualityInput.prop('value', value);
@ -684,32 +714,32 @@ class DashboardView {
frameFilter = frameFilterInput.prop('value');
});
submitCreate.on('click', () => {
submitCreate.on('click', async () => {
if (!validateName(name)) {
taskMessage.css('color', 'red');
taskMessage.text('Bad task name');
return;
}
if (!validateLabels(labels)) {
if (!validateLabels()) {
taskMessage.css('color', 'red');
taskMessage.text('Bad labels specification');
return;
}
if (!validateSegmentSize(segmentSize)) {
if (!validateSegmentSize()) {
taskMessage.css('color', 'red');
taskMessage.text('Segment size out of range');
return;
}
if (!validateBugTracker(bugTrackerLink)) {
if (!validateBugTracker()) {
taskMessage.css('color', 'red');
taskMessage.text('Bad bag tracker link');
return;
}
if (!validateOverlapSize(overlapSize, segmentSize)) {
if (!validateOverlapSize()) {
taskMessage.css('color', 'red');
taskMessage.text('Overlap size must be positive and not more then segment size');
return;
@ -726,16 +756,20 @@ class DashboardView {
taskMessage.text('No files specified for the task');
return;
}
else if (files.length > window.maxUploadCount && source === 'local') {
if (files.length > window.maxUploadCount && source === 'local') {
taskMessage.css('color', 'red');
taskMessage.text('Too many files were specified. Please use share to upload');
return;
}
else if (source === 'local') {
if (source === 'local') {
let commonSize = 0;
for (let file of files) {
for (const file of files) {
commonSize += file.size;
}
if (commonSize > window.maxUploadSize) {
taskMessage.css('color', 'red');
taskMessage.text('Too big files size. Please use share to upload');
@ -744,20 +778,17 @@ class DashboardView {
}
const description = {
name: name,
name,
labels: LabelsInfo.deserialize(labels),
image_quality: compressQuality
}
image_quality: compressQuality,
z_order: zOrder,
bug_tracker: bugTrackerLink,
};
if (bugTrackerLink) {
description.bug_tracker = bugTrackerLink;
}
if (zOrder) {
description.z_order = zOrder;
}
if (customSegmentSize.prop('checked')) {
description.segment_size = segmentSize;
}
if (customOverlapSize.prop('checked')) {
description.overlap = overlapSize;
}
@ -771,103 +802,39 @@ class DashboardView {
description.frame_filter = frameFilter;
}
function cleanupTask(tid) {
$.ajax({
url: `/api/v1/tasks/${tid}`,
type: 'DELETE'
});
}
submitCreate.prop('disabled', true);
$.ajax({
url: '/api/v1/tasks',
type: 'POST',
data: JSON.stringify(description),
contentType: 'application/json'
}).done((taskData) => {
taskMessage.css('color', 'green');
taskMessage.text('Task has been created. Uploading the data..');
const batchOfFiles = new FormData();
for (let j = 0; j < files.length; j++) {
if (source === "local") {
batchOfFiles.append(`client_files[${j}]`, files[j]);
} else {
batchOfFiles.append(`server_files[${j}]`, files[j]);
}
try {
let task = new window.cvat.classes.Task(description);
if (source === 'local') {
task.clientFiles = Array.from(files);
} else {
task.serverFiles = Array.from(files);
}
$.ajax({
url: `/api/v1/tasks/${taskData.id}/data`,
type: 'POST',
data: batchOfFiles,
contentType: false,
processData: false
}).done(() => {
taskMessage.text('The data has been sent. Task is being created..');
requestCreatingStatus(taskData.id, (status) => {
taskMessage.css('color', 'blue');
taskMessage.text(status);
}, () => {
const decorators = DashboardView.decorators('createTask');
let idx = 0;
function next() {
const decorator = decorators[idx++];
if (decorator) {
decorator(taskData, next, () => {
submitCreate.prop('disabled', false);
cleanupTask(tid);
});
} else {
window.location.reload();
}
}
next();
}, (errorMessage) => {
submitCreate.prop('disabled', false);
taskMessage.css('color', 'red');
taskMessage.text(errorMessage);
cleanupTask(taskData.id);
});
}).fail((errorData) => {
const message = `Can not put the data for the task. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
taskMessage.css('color', 'red');
submitCreate.attr('disabled', true);
cancelCreate.attr('disabled', true);
task = await task.save((message) => {
taskMessage.css('color', 'green');
taskMessage.text(message);
submitCreate.prop('disabled', false);
cleanupTask(taskData.id);
});
}).fail((errorData) => {
const message = `Task has not been created. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
window.location.reload();
} catch (exception) {
let { message } = exception;
if (exception instanceof window.cvat.exceptions.ServerError) {
message += ` Code: ${exception.code}`;
}
taskMessage.css('color', 'red');
taskMessage.text(message);
submitCreate.prop('disabled', false);
cleanupTask(taskData.id);
});
submitCreate.attr('disabled', false);
cancelCreate.attr('disabled', false);
}
});
cancelCreate.on('click', () => createModal.addClass('hidden'));
}
}
DashboardView.decorators = (action) => {
DashboardView._decorators = DashboardView._decorators || {};
return DashboardView._decorators[action] || [];
}
DashboardView.registerDecorator = (action, decorator) => {
DashboardView._decorators = DashboardView._decorators || {};
DashboardView._decorators[action] = DashboardView._decorators[action] || [];
DashboardView._decorators[action].push(decorator);
}
// DASHBOARD ENTRYPOINT
window.addEventListener('DOMContentLoaded', () => {
window.cvat.config.backendAPI = `${window.location.origin}/api/v1`;
$.when(
// TODO: Use REST API in order to get meta
$.get('/dashboard/meta'),
@ -875,16 +842,15 @@ window.addEventListener('DOMContentLoaded', () => {
).then((metaData, taskData) => {
try {
new DashboardView(metaData[0], taskData[0]);
}
catch(exception) {
} catch (exception) {
$('#content').empty();
const message = `Can not build CVAT dashboard. Exception: ${exception}.`;
showMessage(message);
}
}).fail((errorData) => {
$('#content').empty();
const message = `Can not build CVAT dashboard. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
const message = `Can not build CVAT dashboard. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
}).always(() => {
$('#loadingOverlay').remove();

@ -16,7 +16,7 @@
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/stylesheet.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/jstree/themes/default/style.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/pagination/pagination.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/pagination/bootstrap.css' %}">
{% for css_file in css_3rdparty %}
<link rel="stylesheet" type="text/css" href="{% static css_file %}">
{% endfor %}
@ -25,7 +25,7 @@
{% block head_js_3rdparty %}
{{ block.super }}
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/jstree/jstree.min.js' %}"></script>
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/pagination/pagination.min.js' %}"></script>
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/pagination/jquery.twbsPagination.js' %}"></script>
{% for js_file in js_3rdparty %}
<script type="text/javascript" src="{% static js_file %}" defer></script>
{% endfor %}
@ -33,6 +33,7 @@
{% block head_js_cvat %}
{{ block.super }}
<script type="text/javascript" src="{% static 'engine/js/cvat.js' %}"></script>
<script type="text/javascript" src="{% static 'dashboard/js/dashboard.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
@ -57,9 +58,8 @@
<div style="width: 50%; display: flex;"> </div>
</div>
<div id="dashboardPagination">
<div id="dashboardList" style="float: left; width: 100%"> </div>
</div>
<div id="dashboardList" style="float: left; width: 100%"> </div>
<ul id="dashboardPagination" class="pagination-sm"></ul>
</div>
<div id="dashboardCreateModal" class="modal hidden">

@ -56,7 +56,7 @@ class SegmentAdmin(admin.ModelAdmin):
class TaskAdmin(admin.ModelAdmin):
date_hierarchy = 'updated_date'
readonly_fields = ('size', 'created_date', 'updated_date', 'overlap', 'flipped')
readonly_fields = ('size', 'created_date', 'updated_date', 'overlap')
list_display = ('name', 'mode', 'owner', 'assignee', 'created_date', 'updated_date')
search_fields = ('name', 'mode', 'owner__username', 'owner__first_name',
'owner__last_name', 'owner__email', 'assignee__username', 'assignee__first_name',

@ -1227,13 +1227,6 @@ class TaskAnnotation:
overlap = self.db_task.overlap
self._merge_data(annotation.data, start_frame, overlap)
@staticmethod
def _flip_shape(shape, im_w, im_h):
for x in range(0, len(shape["points"]), 2):
y = x + 1
shape["points"][x] = im_w - shape["points"][x]
shape["points"][y] = im_w - shape["points"][y]
def dump(self, file_path, scheme, host, query_params):
db_task = self.db_task
db_segments = db_task.segment_set.all().prefetch_related('job_set')
@ -1252,7 +1245,6 @@ class TaskAnnotation:
("mode", db_task.mode),
("overlap", str(db_task.overlap)),
("bugtracker", db_task.bug_tracker),
("flipped", str(db_task.flipped)),
("created", str(timezone.localtime(db_task.created_date))),
("updated", str(timezone.localtime(db_task.updated_date))),
("start_frame", str(db_task.start_frame)),
@ -1332,9 +1324,6 @@ class TaskAnnotation:
]))
for shape in shapes.get(frame, []):
if db_task.flipped:
self._flip_shape(shape, im_w, im_h)
db_label = db_label_by_id[shape["label_id"]]
dump_data = OrderedDict([
@ -1415,8 +1404,6 @@ class TaskAnnotation:
dumper.open_track(dump_data)
for shape in TrackManager.get_interpolated_shapes(
track, 0, db_task.size):
if db_task.flipped:
self._flip_shape(shape, im_w, im_h)
dump_data = OrderedDict([
("frame", str(db_task.start_frame + shape["frame"] * db_task.get_frame_step())),

@ -0,0 +1,125 @@
# Generated by Django 2.1.7 on 2019-06-18 11:08
from django.db import migrations
from django.conf import settings
from cvat.apps.engine.task import get_image_meta_cache
from cvat.apps.engine.models import Job, ShapeType
from PIL import Image
import os
def _flip_shape(shape, size):
if shape.type == ShapeType.RECTANGLE:
shape.points = [
shape.points[2], # xbr -> xtl
shape.points[3], # ybr -> ytl
shape.points[0], # xtl -> xbr
shape.points[1] # ytl -> ybr
]
for x in range(0, len(shape.points), 2):
y = x + 1
shape.points[x] = size['width'] - shape.points[x]
shape.points[y] = size['height'] - shape.points[y]
def frame_path(db_task, frame):
task_dirname = os.path.join(settings.DATA_ROOT, str(db_task.id))
d1 = str(int(frame) // 10000)
d2 = str(int(frame) // 100)
path = os.path.join(task_dirname, 'data', d1, d2, str(frame) + '.jpg')
return path
def _get_image_meta_cache_path(self):
task_dirname = os.path.join(settings.DATA_ROOT, str(self.id))
return os.path.join(task_dirname, "image_meta.cache")
def forwards_func(apps, schema_editor):
Task = apps.get_model('engine', 'Task')
# class methods unavailable in the class which got via get_model()
# nevertheless it is needed for us to use the function get_image_meta_cache()
setattr(Task, 'get_image_meta_cache_path', _get_image_meta_cache_path)
print('Getting flipped tasks...')
db_flipped_tasks = Task.objects.prefetch_related(
'image_set',
).filter(flipped=True).all()
print('Conversion started...')
for db_task in db_flipped_tasks:
print('Processing task {}...'.format(db_task.id))
db_image_by_frame = {}
if db_task.mode == 'annotation':
db_image_by_frame = {db_image.frame: {'width': db_image.width, 'height': db_image.height}
for db_image in db_task.image_set.all()}
else:
im_meta_data = get_image_meta_cache(db_task)['original_size']
db_image_by_frame = {
0: {
'width': im_meta_data[0]['width'],
'height': im_meta_data[0]['height']
}
}
def get_size(frame):
if frame in db_image_by_frame:
return db_image_by_frame[frame]
else:
return db_image_by_frame[0]
db_jobs = Job.objects.select_related('segment').prefetch_related(
'labeledshape_set',
'labeledtrack_set',
'labeledtrack_set__trackedshape_set').filter(segment__task_id=db_task.id).all()
for db_job in db_jobs:
db_shapes = db_job.labeledshape_set.all()
db_tracks = db_job.labeledtrack_set.all()
for db_shape in db_shapes:
_flip_shape(db_shape, get_size(db_shape.frame))
db_shape.save()
for db_track in db_tracks:
db_shapes = db_track.trackedshape_set.all()
for db_shape in db_shapes:
_flip_shape(db_shape, get_size(db_shape.frame))
db_shape.save()
for db_task in db_flipped_tasks:
for frame in range(db_task.size):
path = frame_path(db_task, frame)
if os.path.islink(path):
path = os.path.realpath(path)
try:
image = Image.open(path)
image = image.transpose(Image.ROTATE_180)
image.save(path)
except IOError as ex:
print('Error of handling the frame {}'.format(frame))
print(ex)
class Migration(migrations.Migration):
dependencies = [
('engine', '0019_frame_selection'),
]
operations = [
migrations.RunPython(
forwards_func
),
migrations.RemoveField(
model_name='task',
name='flipped',
),
]

@ -48,7 +48,6 @@ class Task(models.Model):
# Zero means that there are no limits (default)
segment_size = models.PositiveIntegerField(default=0)
z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False)
image_quality = models.PositiveSmallIntegerField(default=50)
start_frame = models.PositiveIntegerField(default=0)
stop_frame = models.PositiveIntegerField(default=0)

@ -187,7 +187,7 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
model = models.Task
fields = ('url', 'id', 'name', 'size', 'mode', 'owner', 'assignee',
'bug_tracker', 'created_date', 'updated_date', 'overlap',
'segment_size', 'z_order', 'flipped', 'status', 'labels', 'segments',
'segment_size', 'z_order', 'status', 'labels', 'segments',
'image_quality', 'start_frame', 'stop_frame', 'frame_filter')
read_only_fields = ('size', 'mode', 'created_date', 'updated_date',
'status')
@ -232,7 +232,6 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
instance.bug_tracker = validated_data.get('bug_tracker',
instance.bug_tracker)
instance.z_order = validated_data.get('z_order', instance.z_order)
instance.flipped = validated_data.get('flipped', instance.flipped)
instance.image_quality = validated_data.get('image_quality',
instance.image_quality)
instance.start_frame = validated_data.get('start_frame', instance.start_frame)

@ -16,7 +16,6 @@ class AnnotationParser {
this._parser = new DOMParser();
this._startFrame = job.start;
this._stopFrame = job.stop;
this._flipped = job.flipped;
this._im_meta = job.image_meta_data;
this._labelsInfo = labelsInfo;
}
@ -43,15 +42,6 @@ class AnnotationParser {
throw Error(message);
}
if (this._flipped) {
[xtl, ytl, xbr, ybr] = [
imWidth - xbr,
imWidth - xtl,
imHeight - ybr,
imHeight - ytl,
];
}
const occluded = box.getAttribute('occluded');
const zOrder = box.getAttribute('z_order') || '0';
return [[xtl, ytl, xbr, ybr], +occluded, +zOrder];
@ -70,11 +60,6 @@ class AnnotationParser {
+ `y=${point.y}. Point out of range ${imWidth}x${imHeight}`;
throw Error(message);
}
if (this._flipped) {
point.x = imWidth - point.x;
point.y = imHeight - point.y;
}
}
points = points.reduce((acc, el) => {

@ -371,7 +371,6 @@ function setupMenu(job, task, shapeCollectionModel,
$('#statFrames').text(`[${window.cvat.player.frames.start}-${window.cvat.player.frames.stop}]`);
$('#statOverlap').text(task.overlap);
$('#statZOrder').text(task.z_order);
$('#statFlipped').text(task.flipped);
$('#statTaskStatus').prop('value', job.status).on('change', async (e) => {
try {
const jobCopy = JSON.parse(JSON.stringify(job));

File diff suppressed because one or more lines are too long

@ -49,7 +49,7 @@ def rq_handler(job, exc_type, exc_value, traceback):
############################# Internal implementation for server API
class _FrameExtractor:
def __init__(self, source_path, compress_quality, step=1, start=0, stop=0, flip_flag=False):
def __init__(self, source_path, compress_quality, step=1, start=0, stop=0):
# translate inversed range 1:95 to 2:32
translated_quality = 96 - compress_quality
translated_quality = round((((translated_quality - 1) * (31 - 2)) / (95 - 1)) + 2)
@ -66,8 +66,6 @@ class _FrameExtractor:
filters += ('*' if filters else '') + 'not(mod(n-' + str(start) + ',' + str(step) + '))'
if filters:
filters = "select=\"'" + filters + "'\""
if flip_flag:
filters += (',' if filters else '') + 'transpose=2,transpose=2'
if filters:
output_opts += ' -vf ' + filters
ff = FFmpeg(

@ -354,22 +354,18 @@
<center>
<table style="width: 100%">
<tr>
<td style="width: 20%;">
<td style="width: 30%;">
<label class="regular h2"> Frames: </label>
<label id="statFrames" class="regular h2"> </label>
</td>
<td style="width: 15%;">
<td style="width: 30%;">
<label class="regular h2"> Overlap: </label>
<label id="statOverlap" class="regular h2"> </label>
</td>
<td style="width: 15%;">
<td style="width: 30%;">
<label class="regular h2"> Z-Order: </label>
<label id="statZOrder" class="regular h2"> </label>
</td>
<td style="width: 15%;">
<label class="regular h2"> Flipped: </label>
<label id="statFlipped" class="regular h2"> </label>
</td>
</tr>
</table>
</center>

@ -6,7 +6,6 @@
/* global
showMessage:false
DashboardView:false
*/
// GIT ENTRYPOINT
@ -17,8 +16,6 @@ window.addEventListener('dashboardReady', () => {
const reposSyncButtonId = 'gitReposSyncButton';
const labelStatusId = 'gitReposLabelStatus';
const labelMessageId = 'gitReposLabelMessage';
const createURLInputTextId = 'gitCreateURLInputText';
const lfsCheckboxId = 'gitLFSCheckbox';
const reposWindowTemplate = `
<div id="${reposWindowId}" class="modal">
@ -177,6 +174,11 @@ window.addEventListener('dashboardReady', () => {
+ `Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
});
});
window.addEventListener('DOMContentLoaded', () => {
const createURLInputTextId = 'gitCreateURLInputText';
const lfsCheckboxId = 'gitLFSCheckbox';
// Setup the "Create task" dialog
const title = 'Field for a repository URL and a relative path inside the repository. \n'
@ -196,72 +198,92 @@ window.addEventListener('dashboardReady', () => {
<td> <input type="checkbox" checked id="${lfsCheckboxId}" </td>
</tr>`).insertAfter($('#dashboardBugTrackerInput').parent().parent());
async function cloneRepos(_, createdTask) {
async function wait(rqId) {
return new Promise((resolve, reject) => {
async function checkCallback() {
let response = null;
try {
response = await $.get(`/git/repository/check/${rqId}`);
} catch (errorData) {
const message = `Can not sent a request to clone the repository. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
}
if (['queued', 'started'].includes(response.status)) {
setTimeout(checkCallback, 1000);
} else if (response.status === 'finished') {
resolve();
} else if (response.status === 'failed') {
let message = 'Repository status check failed. ';
if (response.stderr) {
message += response.stderr;
}
reject(new Error(message));
} else {
const message = `Repository status check returned the status "${response.status}"`;
reject(new Error(message));
}
}
setTimeout(checkCallback, 1000);
});
}
DashboardView.registerDecorator('createTask', (taskData, next, onFault) => {
const taskMessage = $('#dashboardCreateTaskMessage');
const path = $(`#${createURLInputTextId}`).prop('value').replace(/\s/g, '');
const lfs = $(`#${lfsCheckboxId}`).prop('checked');
let response = null;
if (path.length) {
taskMessage.css('color', 'blue');
taskMessage.text('Git repository is being cloned..');
$.ajax({
url: `/git/repository/create/${taskData.id}`,
type: 'POST',
data: JSON.stringify({
path,
lfs,
tid: taskData.id,
}),
contentType: 'application/json',
}).done((rqData) => {
function checkCallback() {
$.ajax({
url: `/git/repository/check/${rqData.rq_id}`,
type: 'GET',
}).done((statusData) => {
if (['queued', 'started'].includes(statusData.status)) {
setTimeout(checkCallback, 1000);
} else if (statusData.status === 'finished') {
taskMessage.css('color', 'blue');
taskMessage.text('Git repository has been cloned');
next();
} else if (statusData.status === 'failed') {
let message = 'Repository status check failed. ';
if (statusData.stderr) {
message += statusData.stderr;
}
taskMessage.css('color', 'red');
taskMessage.text(message);
onFault();
} else {
const message = `Repository status check returned the status "${statusData.status}"`;
taskMessage.css('color', 'red');
taskMessage.text(message);
onFault();
}
}).fail((errorData) => {
const message = `Can not sent a request to clone the repository. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
taskMessage.css('color', 'red');
taskMessage.text(message);
onFault();
});
}
setTimeout(checkCallback, 1000);
}).fail((errorData) => {
const message = `Can not sent a request to clone the repository. Code: ${errorData.status}. `
try {
response = await $.ajax({
url: `/git/repository/create/${createdTask.id}`,
type: 'POST',
data: JSON.stringify({
path,
lfs,
tid: createdTask.id,
}),
contentType: 'application/json',
});
} catch (errorData) {
createdTask.delete();
const message = `Can not send a request to clone the repository. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
taskMessage.css('color', 'red');
taskMessage.text(message);
onFault();
});
} else {
next();
throw new Error(message);
}
try {
await wait(response.rq_id);
} catch (exception) {
createdTask.delete();
throw exception;
}
taskMessage.text('Git repository has been cloned..');
}
});
}
const gitPlugin = {
name: 'Git Plugin',
description: 'Plugin allows you to attach a repository to a task',
cvat: {
classes: {
Task: {
prototype: {
save: {
leave: cloneRepos,
},
},
},
},
},
};
window.cvat.plugins.register(gitPlugin);
});

@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
module.exports = {
"env": {
"node": false,
"browser": true,
"es6": true,
"jquery": true,
"qunit": true,
},
"parserOptions": {
"parser": "babel-eslint",
"sourceType": "module",
"ecmaVersion": 2018,
},
"plugins": [
"security",
"no-unsanitized",
"no-unsafe-innerhtml",
],
"extends": [
"eslint:recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"airbnb",
],
"rules": {
"no-await-in-loop": [0],
"global-require": [0],
"no-new": [0],
"class-methods-use-this": [0],
"no-restricted-properties": [0, {
"object": "Math",
"property": "pow",
}],
"no-plusplus": [0],
"no-param-reassign": [0],
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
"no-restricted-syntax": [0, {"selector": "ForOfStatement"}],
"no-continue": [0],
"no-unsafe-innerhtml/no-unsafe-innerhtml": 1,
// This rule actual for user input data on the node.js environment mainly.
"security/detect-object-injection": 0,
"indent": ["warn", 4],
"no-useless-constructor": 0,
},
};

@ -0,0 +1,38 @@
# Computer Vision Annotation Tool (JS)
## Description
This CVAT module has been created in order to easy integration process with CVAT.
### Short development manual
- Install dependencies
```bash
npm install
```
- Build library from sources in ```dist``` directory:
```bash
npm run-script build
npm run build -- --mode=development # without a minification
```
- Build documentation in ```docs``` directory:
```bash
npm run-script docs
```
- Run tests:
```bash
npm run-script test
```
- Update version of library:
```bash
npm version patch # updated after minor fixes
npm version minor # updated after major changes which don't affect API compatibility with previous versions
npm version major # updated after major changes which affect API compatibility with previous versions
```
Visual studio code configurations:
- cvat.js debug starts debugging with entrypoint api.js
- cvat.js test builds library and runs entrypoint tests.js

@ -0,0 +1,30 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const { defaults } = require('jest-config');
module.exports = {
moduleFileExtensions: [
...defaults.moduleFileExtensions,
'ts',
'tsx',
],
reporters: [
'default',
'jest-junit',
],
testMatch: [
'**/tests/**/*.js',
],
testPathIgnorePatterns: [
'/node_modules/',
'/tests/mocks/*',
],
automock: false,
};

@ -0,0 +1,26 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
module.exports = {
plugins: [],
recurseDepth: 10,
source: {
includePattern: '.+\\.js(doc|x)?$',
excludePattern: '(^|\\/|\\\\)_',
},
sourceType: 'module',
tags: {
allowUnknownTags: false,
dictionaries: ['jsdoc', 'closure'],
},
templates: {
cleverLinks: false,
monospaceLinks: false,
default: {
outputSourceFiles: false,
},
},
};

@ -0,0 +1,35 @@
{
"name": "cvat.js",
"version": "1.0.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {
"build": "webpack",
"test": "jest --config=jest.config.js",
"docs": "jsdoc --readme README.md src/*.js -p -c jsdoc.config.js -d docs"
},
"author": "Intel",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"core-js": "^3.0.1",
"jest": "^24.8.0",
"jest-junit": "^6.4.0",
"jsdoc": "^3.6.2",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
},
"dependencies": {
"axios": "^0.18.0",
"browser-env": "^3.2.6",
"error-stack-parser": "^2.0.2",
"jest-config": "^24.8.0",
"js-cookie": "^2.2.0",
"platform": "^1.3.5",
"stacktrace-gps": "^3.0.2"
}
}

@ -0,0 +1,270 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */
/* global
require:false
*/
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const {
Task,
Job,
} = require('./session');
function isBoolean(value) {
return typeof (value) === 'boolean';
}
function isInteger(value) {
return typeof (value) === 'number' && Number.isInteger(value);
}
function isEnum(value) {
// Called with specific Enum context
for (const key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
if (this[key] === value) {
return true;
}
}
}
return false;
}
function isString(value) {
return typeof (value) === 'string';
}
function checkFilter(filter, fields) {
for (const prop in filter) {
if (Object.prototype.hasOwnProperty.call(filter, prop)) {
if (!(prop in fields)) {
throw new window.cvat.exceptions.ArgumentError(
`Unsupported filter property has been recieved: "${prop}"`,
);
} else if (!fields[prop](filter[prop])) {
throw new window.cvat.exceptions.ArgumentError(
`Received filter property ${prop} is not satisfied for checker`,
);
}
}
}
}
const hidden = require('./hidden');
function setupEnv(wrappedFunction) {
return async function wrapper(...args) {
try {
if (this instanceof window.cvat.classes.Task) {
hidden.taskID = this.id;
} else if (this instanceof window.cvat.classes.Job) {
hidden.jobID = this.id;
hidden.taskID = this.task.id;
} else {
throw new window.cvat.exceptions.ScriptingError('Bad context for the function');
}
const result = await wrappedFunction.call(this, ...args);
return result;
} finally {
delete hidden.taskID;
delete hidden.jobID;
}
};
}
function implementAPI(cvat) {
cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register;
cvat.server.about.implementation = async () => {
const result = await serverProxy.server.about();
return result;
};
cvat.server.share.implementation = async (directory) => {
const result = await serverProxy.server.share(directory);
return result;
};
cvat.server.login.implementation = async (username, password) => {
await serverProxy.server.login(username, password);
};
cvat.users.get.implementation = async (filter) => {
checkFilter(filter, {
self: isBoolean,
});
let users = null;
if ('self' in filter && filter.self) {
users = await serverProxy.users.getSelf();
users = [users];
} else {
users = await serverProxy.users.getUsers();
}
users = users.map(user => new window.cvat.classes.User(user));
return users;
};
cvat.jobs.get.implementation = async (filter) => {
checkFilter(filter, {
taskID: isInteger,
jobID: isInteger,
});
if (('taskID' in filter) && ('jobID' in filter)) {
throw new window.cvat.exceptions.ArgumentError(
'Only one of fields "taskID" and "jobID" allowed simultaneously',
);
}
if (!Object.keys(filter).length) {
throw new window.cvat.exceptions.ArgumentError(
'Job filter must not be empty',
);
}
let task = null;
if ('taskID' in filter) {
task = await serverProxy.tasks.getTasks(`id=${filter.taskID}`);
} else {
const [job] = await serverProxy.jobs.getJob(filter.jobID);
task = await serverProxy.tasks.getTasks(`id=${job.task_id}`);
}
// If task was found by its id, then create task instance and get Job instance from it
if (task.length) {
task = new window.cvat.classes.Task(task[0]);
return filter.jobID ? task.jobs.filter(job => job.id === filter.jobID) : task.jobs;
}
return [];
};
cvat.tasks.get.implementation = async (filter) => {
checkFilter(filter, {
page: isInteger,
name: isString,
id: isInteger,
owner: isString,
assignee: isString,
search: isString,
status: isEnum.bind(window.cvat.enums.TaskStatus),
mode: isEnum.bind(window.cvat.enums.TaskMode),
});
if ('search' in filter && Object.keys(filter).length > 1) {
if (!('page' in filter && Object.keys(filter).length === 2)) {
throw new window.cvat.exceptions.ArgumentError(
'Do not use the filter field "search" with others',
);
}
}
if ('id' in filter && Object.keys(filter).length > 1) {
if (!('page' in filter && Object.keys(filter).length === 2)) {
throw new window.cvat.exceptions.ArgumentError(
'Do not use the filter field "id" with others',
);
}
}
const searchParams = new URLSearchParams();
for (const field of ['name', 'owner', 'assignee', 'search', 'status', 'mode', 'id', 'page']) {
if (Object.prototype.hasOwnProperty.call(filter, field)) {
searchParams.set(field, filter[field]);
}
}
const tasksData = await serverProxy.tasks.getTasks(searchParams.toString());
const tasks = tasksData.map(task => new window.cvat.classes.Task(task));
tasks.count = tasksData.count;
return tasks;
};
Task.prototype.save.implementation = setupEnv(
async function saveTaskImplementation(onUpdate) {
// TODO: Add ability to change an owner and an assignee
if (typeof (this.id) !== 'undefined') {
// If the task has been already created, we update it
const taskData = {
name: this.name,
bug_tracker: this.bugTracker,
z_order: this.zOrder,
labels: [...this.labels.map(el => el.toJSON())],
};
await serverProxy.tasks.saveTask(this.id, taskData);
return this;
}
const taskData = {
name: this.name,
labels: this.labels.map(el => el.toJSON()),
image_quality: this.imageQuality,
z_order: Boolean(this.zOrder),
};
if (this.bugTracker) {
taskData.bug_tracker = this.bugTracker;
}
if (this.segmentSize) {
taskData.segment_size = this.segmentSize;
}
if (this.overlap) {
taskData.overlap = this.overlap;
}
const taskFiles = {
client_files: this.clientFiles,
server_files: this.serverFiles,
remote_files: [], // hasn't been supported yet
};
const task = await serverProxy.tasks.createTask(taskData, taskFiles, onUpdate);
return new Task(task);
},
);
Task.prototype.delete.implementation = setupEnv(
async function deleteTaskImplementation() {
await serverProxy.tasks.deleteTask(this.id);
},
);
Job.prototype.save.implementation = setupEnv(
async function saveJobImplementation() {
// TODO: Add ability to change an assignee
if (this.id) {
const jobData = {
status: this.status,
};
await serverProxy.jobs.saveJob(this.id, jobData);
return this;
}
throw window.cvat.exceptions.ArgumentError(
'Can not save job without and id',
);
},
);
return cvat;
}
module.exports = implementAPI;
})();

@ -0,0 +1,650 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
/**
* External API which should be used by for development
* @module API
*/
(() => {
const PluginRegistry = require('./plugins');
const User = require('./user');
const ObjectState = require('./object-state');
const Statistics = require('./statistics');
const { Job, Task } = require('./session');
const { Attribute, Label } = require('./labels');
const {
ShareFileType,
TaskStatus,
TaskMode,
AttributeType,
ObjectType,
ObjectShape,
LogType,
EventType,
} = require('./enums');
const {
Exception,
ArgumentError,
ScriptingError,
PluginError,
ServerError,
} = require('./exceptions');
const pjson = require('../package.json');
function buildDublicatedAPI() {
const annotations = {
async upload(file) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.upload, file);
return result;
},
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.save);
return result;
},
async clear() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.clear);
return result;
},
async dump() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.dump);
return result;
},
async statistics() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.statistics);
return result;
},
async put(arrayOfObjects = []) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.put, arrayOfObjects);
return result;
},
async get(frame, filter = {}) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.get, frame, filter);
return result;
},
async search(filter, frameFrom, frameTo) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.search, filter, frameFrom, frameTo);
return result;
},
async select(frame, x, y) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.select, frame, x, y);
return result;
},
};
const frames = {
async get(frame) {
const result = await PluginRegistry
.apiWrapper.call(this, frames.get, frame);
return result;
},
};
const logs = {
async put(logType, details) {
const result = await PluginRegistry
.apiWrapper.call(this, logs.put, logType, details);
return result;
},
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, logs.save);
return result;
},
};
const actions = {
async undo(count) {
const result = await PluginRegistry
.apiWrapper.call(this, actions.undo, count);
return result;
},
async redo(count) {
const result = await PluginRegistry
.apiWrapper.call(this, actions.redo, count);
return result;
},
async clear() {
const result = await PluginRegistry
.apiWrapper.call(this, actions.clear);
return result;
},
};
const events = {
async subscribe(eventType, callback) {
const result = await PluginRegistry
.apiWrapper.call(this, events.subscribe, eventType, callback);
return result;
},
async unsubscribe(eventType, callback = null) {
const result = await PluginRegistry
.apiWrapper.call(this, events.unsubscribe, eventType, callback);
return result;
},
};
return {
annotations,
frames,
logs,
actions,
events,
};
}
// Two copies of API for Task and for Job
const jobAPI = buildDublicatedAPI();
const taskAPI = buildDublicatedAPI();
/**
* API entrypoint
* @namespace cvat
* @memberof module:API
*/
const cvat = {
/**
* Namespace is used for an interaction with a server
* @namespace server
* @package
* @memberof module:API.cvat
*/
server: {
/**
* @typedef {Object} ServerInfo
* @property {string} name A name of the tool
* @property {string} description A description of the tool
* @property {string} version A version of the tool
* @global
*/
/**
* Method returns some information about the annotation tool
* @method about
* @async
* @memberof module:API.cvat.server
* @return {ServerInfo}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async about() {
const result = await PluginRegistry
.apiWrapper(cvat.server.about);
return result;
},
/**
* @typedef {Object} FileInfo
* @property {string} name A name of a file
* @property {module:API.cvat.enums.ShareFileType} type
* A type of a file
* @global
*/
/**
* Method returns a list of files in a specified directory on a share
* @method share
* @async
* @memberof module:API.cvat.server
* @param {string} [directory=/] - Share directory path
* @returns {FileInfo[]}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async share(directory = '/') {
const result = await PluginRegistry
.apiWrapper(cvat.server.share, directory);
return result;
},
/**
* Method allows to login on a server
* @method login
* @async
* @memberof module:API.cvat.server
* @param {string} username An username of an account
* @param {string} password A password of an account
* @throws {module:API.cvat.exceptions.ScriptingError}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async login(username, password) {
const result = await PluginRegistry
.apiWrapper(cvat.server.login, username, password);
return result;
},
},
/**
* Namespace is used for getting tasks
* @namespace tasks
* @memberof module:API.cvat
*/
tasks: {
/**
* @typedef {Object} TaskFilter
* @property {string} name Check if name contains this value
* @property {module:API.cvat.enums.TaskStatus} status
* Check if status contains this value
* @property {module:API.cvat.enums.TaskMode} mode
* Check if mode contains this value
* @property {integer} id Check if id equals this value
* @property {integer} page Get specific page
* (default REST API returns 20 tasks per request.
* In order to get more, it is need to specify next page)
* @property {string} owner Check if owner user contains this value
* @property {string} assignee Check if assigneed contains this value
* @property {string} search Combined search of contains among all fields
* @global
*/
/**
* Method returns list of tasks corresponding to a filter
* @method get
* @async
* @memberof module:API.cvat.tasks
* @param {TaskFilter} [filter={}] task filter
* @returns {module:API.cvat.classes.Task[]}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async get(filter = {}) {
const result = await PluginRegistry
.apiWrapper(cvat.tasks.get, filter);
return result;
},
},
/**
* Namespace is used for getting jobs
* @namespace jobs
* @memberof module:API.cvat
*/
jobs: {
/**
* @typedef {Object} JobFilter
* Only one of fields is allowed simultaneously
* @property {integer} taskID filter all jobs of specific task
* @property {integer} jobID filter job with a specific id
* @global
*/
/**
* Method returns list of jobs corresponding to a filter
* @method get
* @async
* @memberof module:API.cvat.jobs
* @param {JobFilter} filter job filter
* @returns {module:API.cvat.classes.Job[]}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async get(filter = {}) {
const result = await PluginRegistry
.apiWrapper(cvat.jobs.get, filter);
return result;
},
},
/**
* Namespace is used for getting users
* @namespace users
* @memberof module:API.cvat
*/
users: {
/**
* @typedef {Object} UserFilter
* @property {boolean} self get only self
* @global
*/
/**
* Method returns list of users corresponding to a filter
* @method get
* @async
* @memberof module:API.cvat.users
* @param {UserFilter} [filter={}] user filter
* @returns {User[]}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async get(filter = {}) {
const result = await PluginRegistry
.apiWrapper(cvat.users.get, filter);
return result;
},
},
/**
* Namespace is used for plugin management
* @namespace plugins
* @memberof module:API.cvat
*/
plugins: {
/**
* @typedef {Object} Plugin
* A plugin is a Javascript object. It must have properties are listed below. <br>
* It also mustn't have property 'functions' which is used internally. <br>
* You can expand any API method including class methods. <br>
* In order to expand class method just use a class name
* in a cvat space (example is listed below).
*
* @property {string} name A name of a plugin
* @property {string} description A description of a plugin
* Example plugin implementation listed below:
* @example
* plugin = {
* name: 'Example Plugin',
* description: 'This example plugin demonstrates how plugin system in CVAT works',
* cvat: {
* server: {
* about: {
* // Plugin adds some actions after executing the cvat.server.about()
* // For example it adds a field with installed plugins to a result
* // An argument "self" is a plugin itself
* // An argument "result" is a return value of cvat.server.about()
* // All next arguments are arguments of a wrapped function
* // (in this case the wrapped function doesn't have any arguments)
* async leave(self, result) {
* result.plugins = await self.internal.getPlugins();
* // Note that a method leave must return "result" (changed or not)
* // Otherwise API won't work as expected
* return result;
* },
* },
* },
* // In this example plugin also wraps a class method
* classes: {
* Job: {
* prototype: {
* annotations: {
* put: {
* // The first argument "self" is a plugin, like in a case above
* // The second argument is an argument of the
* // cvat.Job.annotations.put()
* // It contains an array of objects to put
* // In this sample we round objects coordinates and save them
* enter(self, objects) {
* for (const obj of objects) {
* if (obj.type != 'tag') {
* const points = obj.position.map((point) => {
* const roundPoint = {
* x: Math.round(point.x),
* y: Math.round(point.y),
* };
* return roundPoint;
* });
* }
* }
* },
* },
* },
* },
* },
* },
* },
* // In general you can add any others members to your plugin
* // Members below are only examples
* internal: {
* async getPlugins() {
* // Collect information about installed plugins
* const plugins = await window.cvat.plugins.list();
* return plugins.map((el) => {
* return {
* name: el.name,
* description: el.description,
* };
* });
* },
* },
* };
* @global
*/
/**
* Method returns list of installed plugins
* @method list
* @async
* @memberof module:API.cvat.plugins
* @returns {Plugin[]}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async list() {
const result = await PluginRegistry
.apiWrapper(cvat.plugins.list);
return result;
},
/**
* Install plugin to CVAT
* @method register
* @async
* @memberof module:API.cvat.plugins
* @param {Plugin} [plugin] plugin for registration
* @throws {module:API.cvat.exceptions.PluginError}
*/
async register(plugin) {
const result = await PluginRegistry
.apiWrapper(cvat.plugins.register, plugin);
return result;
},
},
/**
* Namespace contains some changeable configurations
* @namespace config
* @memberof module:API.cvat
*/
config: {
/**
* @property {string} backendAPI host with a backend api
* @memberof module:API.cvat.config
* @property {string} proxy Axios proxy settings.
* For more details please read <a href="https://github.com/axios/axios"> here </a>
* @memberof module:API.cvat.config
* @property {integer} preloadFrames the number of subsequent frames which are
* loaded in background
* @memberof module:API.cvat.config
*/
preloadFrames: 300,
backendAPI: 'http://localhost:7000/api/v1',
proxy: false,
},
/**
* Namespace contains some library information e.g. api version
* @namespace client
* @memberof module:API.cvat
*/
client: {
/**
* @property {string} version Client version.
* Format: <b>{major}.{minor}.{patch}</b>
* <li style="margin-left: 10px;"> A major number is changed after an API becomes
* incompatible with a previous version
* <li style="margin-left: 10px;"> A minor number is changed after an API expands
* <li style="margin-left: 10px;"> A patch number is changed after an each build
* @memberof module:API.cvat.client
* @readonly
*/
version: `${pjson.version}`,
},
/**
* Namespace is used for access to enums
* @namespace enums
* @memberof module:API.cvat
*/
enums: {
ShareFileType,
TaskStatus,
TaskMode,
AttributeType,
ObjectType,
ObjectShape,
LogType,
EventType,
},
/**
* Namespace is used for access to exceptions
* @namespace exceptions
* @memberof module:API.cvat
*/
exceptions: {
Exception,
ArgumentError,
ScriptingError,
PluginError,
ServerError,
},
/**
* Namespace is used for access to classes
* @namespace classes
* @memberof module:API.cvat
*/
classes: {
Task,
User,
Job,
Attribute,
Label,
Statistics,
ObjectState,
},
};
cvat.server = Object.freeze(cvat.server);
cvat.tasks = Object.freeze(cvat.tasks);
cvat.jobs = Object.freeze(cvat.jobs);
cvat.users = Object.freeze(cvat.users);
cvat.plugins = Object.freeze(cvat.plugins);
cvat.client = Object.freeze(cvat.client);
cvat.enums = Object.freeze(cvat.enums);
cvat.Job = Object.freeze(cvat.Job);
cvat.Task = Object.freeze(cvat.Task);
Object.defineProperties(Job.prototype, Object.freeze({
annotations: {
value: Object.freeze({
upload: jobAPI.annotations.upload,
save: jobAPI.annotations.save,
clear: jobAPI.annotations.clear,
dump: jobAPI.annotations.dump,
statistics: jobAPI.annotations,
put: jobAPI.annotations.put,
get: jobAPI.annotations.get,
search: jobAPI.annotations.search,
select: jobAPI.annotations.select,
}),
writable: false,
},
frames: {
value: Object.freeze({
get: jobAPI.frames.get,
}),
writable: false,
},
logs: {
value: Object.freeze({
put: jobAPI.logs.put,
save: jobAPI.logs.save,
}),
writable: false,
},
actions: {
value: Object.freeze({
undo: jobAPI.actions.undo,
redo: jobAPI.actions.redo,
clear: jobAPI.actions.clear,
}),
writable: false,
},
events: {
value: Object.freeze({
subscribe: jobAPI.events.subscribe,
unsubscribe: jobAPI.events.unsubscribe,
}),
writable: false,
},
}));
Object.defineProperties(Task.prototype, Object.freeze({
annotations: {
value: Object.freeze({
upload: taskAPI.annotations.upload,
save: taskAPI.annotations.save,
clear: taskAPI.annotations.clear,
dump: taskAPI.annotations.dump,
statistics: taskAPI.annotations.statistics,
put: taskAPI.annotations.put,
get: taskAPI.annotations.get,
search: taskAPI.annotations.search,
select: taskAPI.annotations.select,
}),
writable: false,
},
frames: {
value: Object.freeze({
get: taskAPI.frames.get,
}),
writable: false,
},
logs: {
value: Object.freeze({
put: taskAPI.logs.put,
save: taskAPI.logs.save,
}),
writable: false,
},
actions: {
value: Object.freeze({
undo: taskAPI.actions.undo,
redo: taskAPI.actions.redo,
clear: taskAPI.actions.clear,
}),
writable: false,
},
events: {
value: Object.freeze({
subscribe: taskAPI.events.subscribe,
unsubscribe: taskAPI.events.unsubscribe,
}),
writable: false,
},
}));
const implementAPI = require('./api-implementation');
if (typeof (window) === 'undefined') {
// Dummy browser environment
require('browser-env')();
}
window.cvat = Object.freeze(implementAPI(cvat));
const hidden = require('./hidden');
hidden.location = cvat.config.backendAPI.slice(0, -7); // TODO: Use JS server instead
})();

@ -0,0 +1,191 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
(() => {
/**
* Enum for type of server files
* @enum {string}
* @name ShareFileType
* @memberof module:API.cvat.enums
* @property {string} DIR 'DIR'
* @property {string} REG 'REG'
* @readonly
*/
const ShareFileType = Object.freeze({
DIR: 'DIR',
REG: 'REG',
});
/**
* Enum for a status of a task
* @enum {string}
* @name TaskStatus
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation'
* @property {string} COMPLETED 'completed'
* @readonly
*/
const TaskStatus = Object.freeze({
ANNOTATION: 'annotation',
VALIDATION: 'validation',
COMPLETED: 'completed',
});
/**
* Enum for a mode of a task
* @enum {string}
* @name TaskMode
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} INTERPOLATION 'interpolation'
* @readonly
*/
const TaskMode = Object.freeze({
ANNOTATION: 'annotation',
INTERPOLATION: 'interpolation',
});
/**
* Enum for type of server files
* @enum {string}
* @name AttributeType
* @memberof module:API.cvat.enums
* @property {string} CHECKBOX 'checkbox'
* @property {string} SELECT 'select'
* @property {string} RADIO 'radio'
* @property {string} NUMBER 'number'
* @property {string} TEXT 'text'
* @readonly
*/
const AttributeType = Object.freeze({
CHECKBOX: 'checkbox',
RADIO: 'radio',
SELECT: 'select',
NUMBER: 'number',
TEXT: 'text',
});
/**
* Enum for type of an object
* @enum {string}
* @name ObjectType
* @memberof module:API.cvat.enums
* @property {string} TAG 'tag'
* @property {string} SHAPE 'shape'
* @property {string} TRACK 'track'
* @readonly
*/
const ObjectType = Object.freeze({
TAG: 'tag',
SHAPE: 'shape',
TRACK: 'track',
});
/**
* Enum for type of server files
* @enum {string}
* @name ObjectShape
* @memberof module:API.cvat.enums
* @property {string} RECTANGLE 'rectangle'
* @property {string} POLYGON 'polygon'
* @property {string} POLYLINE 'polyline'
* @property {string} POINTS 'points'
* @readonly
*/
const ObjectShape = Object.freeze({
RECTANGLE: 'rectangle',
POLYGON: 'polygon',
POLYLINE: 'polyline',
POINTS: 'points',
});
/**
* Enum for type of server files
* @enum {number}
* @name LogType
* @memberof module:API.cvat.enums
* @property {number} pasteObject 0
* @property {number} changeAttribute 1
* @property {number} dragObject 2
* @property {number} deleteObject 3
* @property {number} pressShortcut 4
* @property {number} resizeObject 5
* @property {number} sendLogs 6
* @property {number} saveJob 7
* @property {number} jumpFrame 8
* @property {number} drawObject 9
* @property {number} changeLabel 10
* @property {number} sendTaskInfo 11
* @property {number} loadJob 12
* @property {number} moveImage 13
* @property {number} zoomImage 14
* @property {number} lockObject 15
* @property {number} mergeObjects 16
* @property {number} copyObject 17
* @property {number} propagateObject 18
* @property {number} undoAction 19
* @property {number} redoAction 20
* @property {number} sendUserActivity 21
* @property {number} sendException 22
* @property {number} changeFrame 23
* @property {number} debugInfo 24
* @property {number} fitImage 25
* @property {number} rotateImage 26
* @readonly
*/
const LogType = {
pasteObject: 0,
changeAttribute: 1,
dragObject: 2,
deleteObject: 3,
pressShortcut: 4,
resizeObject: 5,
sendLogs: 6,
saveJob: 7,
jumpFrame: 8,
drawObject: 9,
changeLabel: 10,
sendTaskInfo: 11,
loadJob: 12,
moveImage: 13,
zoomImage: 14,
lockObject: 15,
mergeObjects: 16,
copyObject: 17,
propagateObject: 18,
undoAction: 19,
redoAction: 20,
sendUserActivity: 21,
sendException: 22,
changeFrame: 23,
debugInfo: 24,
fitImage: 25,
rotateImage: 26,
};
/**
* Enum for type of server files
* @enum {number}
* @name EventType
* @memberof module:API.cvat.enums
* @property {number} frameDownloaded 0
* @readonly
*/
const EventType = Object.freeze({
frameDownloaded: 0,
});
module.exports = {
ShareFileType,
TaskStatus,
TaskMode,
AttributeType,
ObjectType,
ObjectShape,
LogType,
EventType,
};
})();

@ -0,0 +1,258 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const Platform = require('platform');
const ErrorStackParser = require('error-stack-parser');
const hidden = require('./hidden');
/**
* Base exception class
* @memberof module:API.cvat.exceptions
* @extends Error
* @ignore
*/
class Exception extends Error {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
const time = new Date().toISOString();
const system = Platform.os.toString();
const client = `${Platform.name} ${Platform.version}`;
const info = ErrorStackParser.parse(this)[0];
const filename = `${hidden.location}${info.fileName}`;
const line = info.lineNumber;
const column = info.columnNumber;
const {
jobID,
taskID,
clientID,
} = hidden;
const projID = undefined; // wasn't implemented
Object.defineProperties(this, Object.freeze({
system: {
/**
* @name system
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => system,
},
client: {
/**
* @name client
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => client,
},
time: {
/**
* @name time
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => time,
},
jobID: {
/**
* @name jobID
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => jobID,
},
taskID: {
/**
* @name taskID
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => taskID,
},
projID: {
/**
* @name projID
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => projID,
},
clientID: {
/**
* @name clientID
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => clientID,
},
filename: {
/**
* @name filename
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => filename,
},
line: {
/**
* @name line
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => line,
},
column: {
/**
* @name column
* @type {integer}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => column,
},
}));
}
/**
* Save an exception on a server
* @name save
* @method
* @memberof Exception
* @instance
* @async
*/
async save() {
const exceptionObject = {
system: this.system,
client: this.client,
time: this.time,
job_id: this.jobID,
task_id: this.taskID,
proj_id: this.projID,
client_id: this.clientID,
message: this.message,
filename: this.filename,
line: this.line,
column: this.column,
stack: this.stack,
};
try {
const serverProxy = require('./server-proxy');
await serverProxy.server.exception(exceptionObject);
} catch (exception) {
// add event
}
}
}
/**
* Exceptions are referred with arguments data
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
class ArgumentError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
/**
* Unexpected situations in code
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
class ScriptingError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
/**
* Plugin-referred exceptions
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
class PluginError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
/**
* Exceptions in interaction with a server
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
class ServerError extends Exception {
/**
* @param {string} message - Exception message
* @param {(string|integer)} code - Response code
*/
constructor(message, code) {
super(message);
Object.defineProperties(this, Object.freeze({
/**
* @name code
* @type {(string|integer)}
* @memberof module:API.cvat.exceptions.ServerError
* @readonly
* @instance
*/
code: {
get: () => code,
},
}));
}
}
module.exports = {
Exception,
ArgumentError,
ScriptingError,
PluginError,
ServerError,
};
})();

@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const PluginRegistry = require('./plugins');
/**
* Class provides meta information about specific frame and frame itself
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class FrameData {
constructor(width, height, tid, number) {
Object.defineProperties(this, Object.freeze({
/**
* @name width
* @type {integer}
* @memberof module:API.cvat.classes.FrameData
* @readonly
* @instance
*/
width: {
value: width,
writable: false,
},
/**
* @name height
* @type {integer}
* @memberof module:API.cvat.classes.FrameData
* @readonly
* @instance
*/
height: {
value: height,
writable: false,
},
tid: {
value: tid,
writable: false,
},
number: {
value: number,
writable: false,
},
}));
}
/**
* Method returns URL encoded image which can be placed in the img tag
* @method image
* @returns {string}
* @memberof module:API.cvat.classes.FrameData
* @instance
* @async
* @throws {module:API.cvat.exception.ServerError}
* @throws {module:API.cvat.exception.PluginError}
*/
async image() {
const result = await PluginRegistry
.apiWrapper.call(this, FrameData.prototype.image);
return result;
}
}
module.exports = FrameData;
})();

@ -0,0 +1,17 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* Some shared cvat.js data which aren't intended for a user */
(() => {
const hidden = {
clientID: +Date.now().toString().substr(-6),
projID: undefined,
taskID: undefined,
jobID: undefined,
location: undefined,
};
module.exports = hidden;
})();

@ -0,0 +1,203 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
(() => {
/**
* Class representing an attribute
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Attribute {
constructor(initialData) {
const data = {
id: undefined,
default_value: undefined,
input_type: undefined,
mutable: undefined,
name: undefined,
values: undefined,
};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
if (Array.isArray(initialData[key])) {
data[key] = [...initialData[key]];
} else {
data[key] = initialData[key];
}
}
}
}
if (!Object.values(window.cvat.enums.AttributeType).includes(data.input_type)) {
throw new window.cvat.exceptions.ArgumentError(
`Got invalid attribute type ${data.input_type}`,
);
}
Object.defineProperties(this, Object.freeze({
/**
* @name id
* @type {integer}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name defaultValue
* @type {(string|integer|boolean)}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
defaultValue: {
get: () => data.default_value,
},
/**
* @name inputType
* @type {module:API.cvat.enums.AttributeType}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
inputType: {
get: () => data.input_type,
},
/**
* @name mutable
* @type {boolean}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
mutable: {
get: () => data.mutable,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
name: {
get: () => data.name,
},
/**
* @name values
* @type {(string[]|integer[]|boolean[])}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
values: {
get: () => [...data.values],
},
}));
}
toJSON() {
const object = {
name: this.name,
mutable: this.mutable,
input_type: this.inputType,
default_value: this.defaultValue,
values: this.values,
};
if (typeof (this.id) !== 'undefined') {
object.id = this.id;
}
return object;
}
}
/**
* Class representing a label
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Label {
constructor(initialData) {
const data = {
id: undefined,
name: undefined,
};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
data[key] = initialData[key];
}
}
}
data.attributes = [];
if (Object.prototype.hasOwnProperty.call(initialData, 'attributes')
&& Array.isArray(initialData.attributes)) {
for (const attrData of initialData.attributes) {
data.attributes.push(new window.cvat.classes.Attribute(attrData));
}
}
Object.defineProperties(this, Object.freeze({
/**
* @name id
* @type {integer}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
name: {
get: () => data.name,
},
/**
* @name attributes
* @type {module:API.cvat.classes.Attribute[]}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
attributes: {
get: () => [...data.attributes],
},
}));
}
toJSON() {
const object = {
name: this.name,
attributes: [...this.attributes.map(el => el.toJSON())],
};
if (typeof (this.id) !== 'undefined') {
object.id = this.id;
}
return object;
}
}
module.exports = {
Attribute,
Label,
};
})();

@ -0,0 +1,42 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const PluginRegistry = require('./plugins');
/**
* Class describe scheme of a log object
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Log {
constructor(logType, continuous, details) {
this.type = logType;
this.continuous = continuous;
this.details = details;
}
/**
* Method closes a continue log
* @method close
* @memberof module:API.cvat.classes.Log
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
*/
async close() {
const result = await PluginRegistry
.apiWrapper.call(this, Log.prototype.close);
return result;
}
}
module.exports = Log;
})();

@ -0,0 +1,275 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const PluginRegistry = require('./plugins');
/**
* Class representing a state of an object on a specific frame
* @memberof module:API.cvat.classes
*/
class ObjectState {
/**
* @param {module:API.cvat.enums.ObjectType} type - a type of an object
* @param {module:API.cvat.enums.ObjectShape} shape -
* a type of a shape if an object is a shape of a track
* @param {module:API.cvat.classes.Label} label - a label of an object
*/
constructor(type, shape, label) {
const data = {
position: null,
group: null,
zOrder: null,
outside: false,
occluded: false,
attributes: null,
lock: false,
type,
shape,
label,
};
Object.defineProperties(this, Object.freeze({
type: {
/**
* @name type
* @type {module:API.cvat.enums.ShapeType}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.type,
},
shape: {
/**
* @name shape
* @type {module:API.cvat.enums.ShapeType}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.shape,
},
label: {
/**
* @name shape
* @type {module:API.cvat.classes.Label}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.label,
set: (labelInstance) => {
if (!(labelInstance instanceof window.cvat.classes.Label)) {
throw new window.cvat.exceptions.ArgumentException(
`Expected Label instance, but got "${typeof (labelInstance.constructor.name)}"`,
);
}
data.label = labelInstance;
},
},
position: {
/**
* @typedef {Object} Point
* @property {number} x
* @property {number} y
* @global
*/
/**
* @name position
* @type {Point[]}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.position,
set: (position) => {
if (Array.isArray(position)) {
for (const point of position) {
if (typeof (point) !== 'object'
|| !('x' in point) || !('y' in point)) {
throw new window.cvat.exceptions.ArgumentException(
`Got invalid point ${point}`,
);
}
}
} else {
throw new window.cvat.exceptions.ArgumentException(
`Got invalid type "${typeof (position.constructor.name)}"`,
);
}
data.position = position;
},
},
group: {
/**
* @name group
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.group,
set: (groupID) => {
if (!Number.isInteger(groupID)) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${groupID.constructor.name}`,
);
}
data.group = groupID;
},
},
zOrder: {
/**
* @name zOrder
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.zOrder,
set: (zOrder) => {
if (!Number.isInteger(zOrder)) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${zOrder.constructor.name}`,
);
}
data.zOrder = zOrder;
},
},
outside: {
/**
* @name outside
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.outside,
set: (outside) => {
if (!(typeof (outside) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${outside.constructor.name}`,
);
}
data.outside = outside;
},
},
occluded: {
/**
* @name occluded
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.occluded,
set: (occluded) => {
if (!(typeof (occluded) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${occluded.constructor.name}`,
);
}
data.occluded = occluded;
},
},
lock: {
/**
* @name lock
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.lock,
set: (lock) => {
if (!(typeof (lock) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${lock.constructor.name}`,
);
}
data.lock = lock;
},
},
attributes: {
/**
* Object is id:value pairs where "id" is an integer
* attribute identifier and "value" is an attribute value
* @name attributes
* @type {Object}
* @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
get: () => data.attributes,
set: (attributes) => {
if (typeof (attributes) !== 'object') {
throw new window.cvat.exceptions.ArgumentException(
`Expected object, but got ${attributes.constructor.name}`,
);
}
for (let attrId in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrId)) {
attrId = +attrId;
if (!Number.isInteger(attrId)) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer attribute id, but got ${attrId.constructor.name}`,
);
}
data.attributes[attrId] = attributes[attrId];
}
}
},
},
}));
}
/**
* Method saves object state in a collection
* @method save
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
*/
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, ObjectState.prototype.save);
return result;
}
/**
* Method deletes object from a collection
* @method delete
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
*/
async delete() {
const result = await PluginRegistry
.apiWrapper.call(this, ObjectState.prototype.delete);
return result;
}
}
module.exports = ObjectState;
})();

@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const { PluginError } = require('./exceptions');
const plugins = [];
class PluginRegistry {
static async apiWrapper(wrappedFunc, ...args) {
// I have to optimize the wrapper
const pluginList = await window.cvat.plugins.list.implementation();
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions
.filter(obj => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.enter) {
try {
await pluginDecorators.enter.call(this, plugin, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
}
}
}
let result = await wrappedFunc.implementation.call(this, ...args);
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions
.filter(obj => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.leave) {
try {
result = await pluginDecorators.leave.call(this, plugin, result, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
}
}
}
return result;
}
static async register(plug) {
const functions = [];
if (typeof (plug) !== 'object') {
throw new PluginError(`Plugin should be an object, but got "${typeof (plug)}"`);
}
if (!('name' in plug) || typeof (plug.name) !== 'string') {
throw new PluginError('Plugin must contain a "name" field and it must be a string');
}
if (!('description' in plug) || typeof (plug.description) !== 'string') {
throw new PluginError('Plugin must contain a "description" field and it must be a string');
}
if ('functions' in plug) {
throw new PluginError('Plugin must not contain a "functions" field');
}
(function traverse(plugin, api) {
const decorator = {};
for (const key in plugin) {
if (Object.prototype.hasOwnProperty.call(plugin, key)) {
if (typeof (plugin[key]) === 'object') {
if (Object.prototype.hasOwnProperty.call(api, key)) {
traverse(plugin[key], api[key]);
}
} else if (['enter', 'leave'].includes(key)
&& typeof (api) === 'function'
&& typeof (plugin[key] === 'function')) {
decorator.callback = api;
decorator[key] = plugin[key];
}
}
}
if (Object.keys(decorator).length) {
functions.push(decorator);
}
}(plug, {
cvat: window.cvat,
}));
Object.defineProperty(plug, 'functions', {
value: functions,
writable: false,
});
plugins.push(plug);
}
static async list() {
return plugins;
}
}
module.exports = PluginRegistry;
})();

@ -0,0 +1,487 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
encodeURIComponent:false
*/
(() => {
class ServerProxy {
constructor() {
const Cookie = require('js-cookie');
const Axios = require('axios');
function setCSRFHeader(header) {
Axios.defaults.headers.delete['X-CSRFToken'] = header;
Axios.defaults.headers.patch['X-CSRFToken'] = header;
Axios.defaults.headers.post['X-CSRFToken'] = header;
Axios.defaults.headers.put['X-CSRFToken'] = header;
}
async function about() {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/server/about`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get "about" information from the server',
code,
);
}
return response.data;
}
async function share(directory) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/server/share?directory=${directory}`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get "share" information from the server',
code,
);
}
return response.data;
}
async function exception(exceptionObject) {
const { backendAPI } = window.cvat.config;
try {
await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), {
proxy: window.cvat.config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not send an exception to the server',
code,
);
}
}
async function login(username, password) {
function setCookie(response) {
if (response.headers['set-cookie']) {
// Browser itself setup cookie and header is none
// In NodeJS we need do it manually
let cookies = '';
for (let cookie of response.headers['set-cookie']) {
[cookie] = cookie.split(';'); // truncate extra information
const name = cookie.split('=')[0];
const value = cookie.split('=')[1];
if (name === 'csrftoken') {
setCSRFHeader(value);
}
Cookie.set(name, value);
cookies += `${cookie};`;
}
Axios.defaults.headers.common.Cookie = cookies;
} else {
// Browser code. We need set additinal header for authentification
const csrftoken = Cookie.get('csrftoken');
if (csrftoken) {
setCSRFHeader(csrftoken);
} else {
throw new window.cvat.exceptions.ScriptingError(
'An environment has been detected as a browser'
+ ', but CSRF token has not been found in cookies',
);
}
}
}
const host = window.cvat.config.backendAPI.slice(0, -7);
let csrf = null;
try {
csrf = await Axios.get(`${host}/auth/login`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get CSRF token from a server',
code,
);
}
setCookie(csrf);
const authentificationData = ([
`${encodeURIComponent('username')}=${encodeURIComponent(username)}`,
`${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
]).join('&').replace(/%20/g, '+');
let authentificationResponse = null;
try {
authentificationResponse = await Axios.post(
`${host}/auth/login`,
authentificationData,
{
'Content-Type': 'application/x-www-form-urlencoded',
proxy: window.cvat.config.proxy,
// do not redirect to a dashboard,
// otherwise we don't get a session id in a response
maxRedirects: 0,
},
);
} catch (errorData) {
if (errorData.response.status === 302) {
// Redirection code expected
authentificationResponse = errorData.response;
} else {
const code = errorData.response
? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not login on a server',
code,
);
}
}
setCookie(authentificationResponse);
}
async function getTasks(filter = '') {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks?${filter}`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get tasks from a server',
code,
);
}
response.data.results.count = response.data.count;
return response.data.results;
}
async function saveTask(id, taskData) {
const { backendAPI } = window.cvat.config;
try {
await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), {
proxy: window.cvat.config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not save the task on the server',
code,
);
}
}
async function deleteTask(id) {
const { backendAPI } = window.cvat.config;
try {
await Axios.delete(`${backendAPI}/tasks/${id}`);
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not delete the task from the server',
code,
);
}
}
async function createTask(taskData, files, onUpdate) {
const { backendAPI } = window.cvat.config;
async function wait(id) {
return new Promise((resolve, reject) => {
async function checkStatus() {
try {
const response = await Axios.get(`${backendAPI}/tasks/${id}/status`);
if (['Queued', 'Started'].includes(response.data.state)) {
if (response.data.message !== '') {
onUpdate(response.data.message);
}
setTimeout(checkStatus, 1000);
} else if (response.data.state === 'Finished') {
resolve();
} else if (response.data.state === 'Failed') {
// If request has been successful, but task hasn't been created
// Then passed data is wrong and we can pass code 400
reject(new window.cvat.exceptions.ServerError(
'Could not create the task on the server',
400,
));
} else {
// If server has another status, it is unexpected
// Therefore it is server error and we can pass code 500
reject(new window.cvat.exceptions.ServerError(
`Unknown task state has been recieved: ${response.data.state}`,
500,
));
}
} catch (errorData) {
const code = errorData.response
? errorData.response.status : errorData.code;
reject(new window.cvat.exceptions.ServerError(
'Data uploading error occured',
code,
));
}
}
setTimeout(checkStatus, 1000);
});
}
const batchOfFiles = new window.FormData();
for (const key in files) {
if (Object.prototype.hasOwnProperty.call(files, key)) {
for (let i = 0; i < files[key].length; i++) {
batchOfFiles.append(`${key}[${i}]`, files[key][i]);
}
}
}
let response = null;
onUpdate('The task is being created on the server..');
try {
response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskData), {
proxy: window.cvat.config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not put task to the server',
code,
);
}
onUpdate('The data is being uploaded to the server..');
try {
await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, batchOfFiles, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
deleteTask(response.data.id);
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not put data to the server',
code,
);
}
try {
await wait(response.data.id);
} catch (createException) {
deleteTask(response.data.id);
throw createException;
}
const createdTask = await getTasks(`?id=${response.id}`);
return createdTask[0];
}
async function getJob(jobID) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/jobs/${jobID}`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get jobs from a server',
code,
);
}
return response.data;
}
async function saveJob(id, jobData) {
const { backendAPI } = window.cvat.config;
try {
await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), {
proxy: window.cvat.config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not save the job on the server',
code,
);
}
}
async function getUsers() {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/users`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get users from a server',
code,
);
}
return response.data.results;
}
async function getSelf() {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/users/self`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get users from a server',
code,
);
}
return response.data;
}
async function getFrame(tid, frame) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/${frame}`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
`Could not get frame ${frame} for a task ${tid} from a server`,
code,
);
}
return response.data;
}
async function getMeta(tid) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/meta`, {
proxy: window.cvat.config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
`Could not get frame meta info for a task ${tid} from a server`,
code,
);
}
return response.data;
}
// Set csrftoken header from browser cookies if it exists
// NodeJS env returns 'undefined'
// So in NodeJS we need login after each run
const csrftoken = Cookie.get('csrftoken');
if (csrftoken) {
setCSRFHeader(csrftoken);
}
Object.defineProperties(this, Object.freeze({
server: {
value: Object.freeze({
about,
share,
exception,
login,
}),
writable: false,
},
tasks: {
value: Object.freeze({
getTasks,
saveTask,
createTask,
deleteTask,
}),
writable: false,
},
jobs: {
value: Object.freeze({
getJob,
saveJob,
}),
writable: false,
},
users: {
value: Object.freeze({
getUsers,
getSelf,
}),
writable: false,
},
frames: {
value: Object.freeze({
getFrame,
getMeta,
}),
writable: false,
},
}));
}
}
const serverProxy = new ServerProxy();
module.exports = serverProxy;
})();

@ -0,0 +1,808 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const PluginRegistry = require('./plugins');
/**
* Base abstract class for Task and Job. It contains common members.
* @hideconstructor
* @virtual
*/
class Session {
constructor() {
/**
* An interaction with annotations
* @namespace annotations
* @memberof Session
*/
/**
* Upload annotations from a dump file
* @method upload
* @memberof Session.annotations
* @param {File} [annotations] - text file with annotations
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
/**
* Save annotation changes on a server
* @method save
* @memberof Session.annotations
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @instance
* @async
*/
/**
* Remove all annotations from a session
* @method clear
* @memberof Session.annotations
* @throws {module:API.cvat.exceptions.PluginError}
* @instance
* @async
*/
/**
* Dump of annotations to a file.
* Method always dumps annotations for a whole task.
* @method dump
* @memberof Session.annotations
* @returns {string} URL which can be used in order to get a dump file
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @instance
* @async
*/
/**
* Collect some statistics from a session.
* For example number of shapes, tracks, polygons etc
* @method statistics
* @memberof Session.annotations
* @returns {module:API.cvat.classes.Statistics} statistics object
* @throws {module:API.cvat.exceptions.PluginError}
* @instance
* @async
*/
/**
* Add some annotations to a session
* @method put
* @memberof Session.annotations
* @param {module:API.cvat.classes.ObjectState[]} data
* array of objects on the specific frame
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* @typedef {Object} ObjectFilter
* @property {string} [label] a name of a label
* @property {module:API.cvat.enums.ObjectType} [type]
* @property {module:API.cvat.enums.ObjectShape} [shape]
* @property {boolean} [occluded] a value of occluded property
* @property {boolean} [lock] a value of lock property
* @property {number} [width] a width of a shape
* @property {number} [height] a height of a shape
* @property {Object[]} [attributes] dictionary with "name: value" pairs
* @global
*/
/**
* Get annotations for a specific frame
* @method get
* @param {integer} frame get objects from the frame
* @param {ObjectFilter[]} [filter = []]
* get only objects are satisfied to specific filter
* @returns {module:API.cvat.classes.ObjectState[]}
* @memberof Session.annotations
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* Find frame which contains at least one object satisfied to a filter
* @method search
* @memberof Session.annotations
* @param {ObjectFilter} [filter = []] filter
* @param {integer} from lower bound of a search
* @param {integer} to upper bound of a search
* @returns {integer} the nearest frame which contains filtered objects
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* Select shape under a cursor using smart alghorithms
* @method select
* @memberof Session.annotations
* @param {integer} frame frame for selecting
* @param {float} x horizontal coordinate
* @param {float} y vertical coordinate
* @returns {(integer|null)}
* identifier of a selected object or null if no one of objects is on position
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* Namespace is used for an interaction with frames
* @namespace frames
* @memberof Session
*/
/**
* Get frame by its number
* @method get
* @memberof Session.frames
* @param {integer} frame number of frame which you want to get
* @returns {module:API.cvat.classes.FrameData}
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
/**
* Namespace is used for an interaction with logs
* @namespace logs
* @memberof Session
*/
/**
* Append log to a log collection.
* Continue logs will have been added after "close" method is called
* @method put
* @memberof Session.logs
* @param {module:API.cvat.enums.LogType} type a type of a log
* @param {boolean} continuous log is a continuous log
* @param {Object} details any others data which will be append to log data
* @returns {module:API.cvat.classes.Log}
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
/**
* Save accumulated logs on a server
* @method save
* @memberof Session.logs
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @instance
* @async
*/
/**
* Namespace is used for an interaction with actions
* @namespace actions
* @memberof Session
*/
/**
* Is a dictionary of pairs "id:action" where "id" is an identifier of an object
* which has been affected by undo/redo and "action" is what exactly has been
* done with the object. Action can be: "created", "deleted", "updated".
* Size of an output array equal the param "count".
* @typedef {Object} HistoryAction
* @global
*/
/**
* Undo actions
* @method undo
* @memberof Session.actions
* @returns {HistoryAction}
* @throws {module:API.cvat.exceptions.PluginError}
* @instance
* @async
*/
/**
* Redo actions
* @method redo
* @memberof Session.actions
* @returns {HistoryAction}
* @throws {module:API.cvat.exceptions.PluginError}
* @instance
* @async
*/
/**
* Namespace is used for an interaction with events
* @namespace events
* @memberof Session
*/
/**
* Subscribe on an event
* @method subscribe
* @memberof Session.events
* @param {module:API.cvat.enums.EventType} type - event type
* @param {functions} callback - function which will be called on event
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* Unsubscribe from an event. If callback is not provided,
* all callbacks will be removed from subscribers for the event
* @method unsubscribe
* @memberof Session.events
* @param {module:API.cvat.enums.EventType} type - event type
* @param {functions} [callback = null] - function which is called on event
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
}
}
/**
* Class representing a job.
* @memberof module:API.cvat.classes
* @hideconstructor
* @extends Session
*/
class Job extends Session {
constructor(initialData) {
super();
const data = {
id: undefined,
assignee: undefined,
status: undefined,
start_frame: undefined,
stop_frame: undefined,
task: undefined,
};
for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property)) {
if (property in initialData) {
data[property] = initialData[property];
}
if (data[property] === undefined) {
throw new window.cvat.exceptions.ArgumentError(
`Job field "${property}" was not initialized`,
);
}
}
}
Object.defineProperties(this, Object.freeze({
/**
* @name id
* @type {integer}
* @memberof module:API.cvat.classes.Job
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* Identifier of a user who is responsible for the job
* @name assignee
* @type {integer}
* @memberof module:API.cvat.classes.Job
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
assignee: {
get: () => data.assignee,
set: () => (assignee) => {
if (!Number.isInteger(assignee) || assignee < 0) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a non negative integer',
);
}
data.assignee = assignee;
},
},
/**
* @name status
* @type {module:API.cvat.enums.TaskStatus}
* @memberof module:API.cvat.classes.Job
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
status: {
get: () => data.status,
set: (status) => {
const type = window.cvat.enums.TaskStatus;
let valueInEnum = false;
for (const value in type) {
if (type[value] === status) {
valueInEnum = true;
break;
}
}
if (!valueInEnum) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a value from the enumeration cvat.enums.TaskStatus',
);
}
data.status = status;
},
},
/**
* @name startFrame
* @type {integer}
* @memberof module:API.cvat.classes.Job
* @readonly
* @instance
*/
startFrame: {
get: () => data.start_frame,
},
/**
* @name stopFrame
* @type {integer}
* @memberof module:API.cvat.classes.Job
* @readonly
* @instance
*/
stopFrame: {
get: () => data.stop_frame,
},
/**
* @name task
* @type {module:API.cvat.classes.Task}
* @memberof module:API.cvat.classes.Job
* @readonly
* @instance
*/
task: {
get: () => data.task,
},
}));
}
/**
* Method updates job data like status or assignee
* @method save
* @memberof module:API.cvat.classes.Job
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, Job.prototype.save);
return result;
}
}
/**
* Class representing a task
* @memberof module:API.cvat.classes
* @extends Session
*/
class Task extends Session {
/**
* In a fact you need use the constructor only if you want to create a task
* @param {object} initialData - Object which is used for initalization
* <br> It can contain keys:
* <br> <li style="margin-left: 10px;"> name
* <br> <li style="margin-left: 10px;"> assignee
* <br> <li style="margin-left: 10px;"> bug_tracker
* <br> <li style="margin-left: 10px;"> z_order
* <br> <li style="margin-left: 10px;"> labels
* <br> <li style="margin-left: 10px;"> segment_size
* <br> <li style="margin-left: 10px;"> overlap
*/
constructor(initialData) {
super();
const data = {
id: undefined,
name: undefined,
status: undefined,
size: undefined,
mode: undefined,
owner: undefined,
assignee: undefined,
created_date: undefined,
updated_date: undefined,
bug_tracker: undefined,
overlap: undefined,
segment_size: undefined,
z_order: undefined,
image_quality: undefined,
};
for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property)
&& property in initialData) {
data[property] = initialData[property];
}
}
data.labels = [];
data.jobs = [];
data.files = Object.freeze({
server_files: [],
client_files: [],
remote_files: [],
});
if (Array.isArray(initialData.segments)) {
for (const segment of initialData.segments) {
if (Array.isArray(segment.jobs)) {
for (const job of segment.jobs) {
const jobInstance = new window.cvat.classes.Job({
url: job.url,
id: job.id,
assignee: job.assignee,
status: job.status,
start_frame: segment.start_frame,
stop_frame: segment.stop_frame,
task: this,
});
data.jobs.push(jobInstance);
}
}
}
}
if (Array.isArray(initialData.labels)) {
for (const label of initialData.labels) {
const classInstance = new window.cvat.classes.Label(label);
data.labels.push(classInstance);
}
}
Object.defineProperties(this, Object.freeze({
/**
* @name id
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
name: {
get: () => data.name,
set: (value) => {
if (!value.trim().length) {
throw new window.cvat.exceptions.ArgumentError(
'Value must not be empty',
);
}
data.name = value;
},
},
/**
* @name status
* @type {module:API.cvat.enums.TaskStatus}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
status: {
get: () => data.status,
},
/**
* @name size
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
size: {
get: () => data.size,
},
/**
* @name mode
* @type {TaskMode}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
mode: {
get: () => data.mode,
},
/**
* Identificator of a user who has created the task
* @name owner
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
owner: {
get: () => data.owner,
},
/**
* Identificator of a user who is responsible for the task
* @name assignee
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
assignee: {
get: () => data.assignee,
set: () => (assignee) => {
if (!Number.isInteger(assignee) || assignee < 0) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a non negative integer',
);
}
data.assignee = assignee;
},
},
/**
* @name createdDate
* @type {string}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
createdDate: {
get: () => data.created_date,
},
/**
* @name updatedDate
* @type {string}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
updatedDate: {
get: () => data.updated_date,
},
/**
* @name bugTracker
* @type {string}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
bugTracker: {
get: () => data.bug_tracker,
set: (tracker) => {
data.bug_tracker = tracker;
},
},
/**
* @name overlap
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
overlap: {
get: () => data.overlap,
set: (overlap) => {
if (!Number.isInteger(overlap) || overlap < 0) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a non negative integer',
);
}
data.overlap = overlap;
},
},
/**
* @name segmentSize
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
segmentSize: {
get: () => data.segment_size,
set: (segment) => {
if (!Number.isInteger(segment) || segment < 0) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a positive integer',
);
}
data.segment_size = segment;
},
},
/**
* @name zOrder
* @type {boolean}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
zOrder: {
get: () => data.z_order,
set: (zOrder) => {
if (typeof (zOrder) !== 'boolean') {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a boolean',
);
}
data.z_order = zOrder;
},
},
/**
* @name imageQuality
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
imageQuality: {
get: () => data.image_quality,
set: (quality) => {
if (!Number.isInteger(quality) || quality < 0) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be a positive integer',
);
}
data.image_quality = quality;
},
},
/**
* After task has been created value can be appended only.
* @name labels
* @type {module:API.cvat.classes.Label[]}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
labels: {
get: () => [...data.labels],
set: (labels) => {
if (!Array.isArray(labels)) {
throw new window.cvat.exceptions.ArgumentError(
'Value must be an array of Labels',
);
}
for (const label of labels) {
if (!(label instanceof window.cvat.classes.Label)) {
throw new window.cvat.exceptions.ArgumentError(
'Each array value must be an instance of Label. '
+ `${typeof (label)} was found`,
);
}
}
if (typeof (data.id) === 'undefined') {
data.labels = [...labels];
} else {
data.labels = data.labels.concat([...labels]);
}
},
},
/**
* @name jobs
* @type {module:API.cvat.classes.Job[]}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
*/
jobs: {
get: () => [...data.jobs],
},
/**
* List of files from shared resource
* @name serverFiles
* @type {string[]}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
serverFiles: {
get: () => [...data.files.server_files],
set: (serverFiles) => {
if (!Array.isArray(serverFiles)) {
throw new window.cvat.exceptions.ArgumentError(
`Value must be an array. But ${typeof (serverFiles)} has been got.`,
);
}
for (const value of serverFiles) {
if (typeof (value) !== 'string') {
throw new window.cvat.exceptions.ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`,
);
}
}
Array.prototype.push.apply(data.files.server_files, serverFiles);
},
},
/**
* List of files from client host
* @name clientFiles
* @type {File[]}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
clientFiles: {
get: () => [...data.files.client_files],
set: (clientFiles) => {
if (!Array.isArray(clientFiles)) {
throw new window.cvat.exceptions.ArgumentError(
`Value must be an array. But ${typeof (clientFiles)} has been got.`,
);
}
for (const value of clientFiles) {
if (!(value instanceof window.File)) {
throw new window.cvat.exceptions.ArgumentError(
`Array values must be a File. But ${value.constructor.name} has been got.`,
);
}
}
Array.prototype.push.apply(data.files.client_files, clientFiles);
},
},
}));
}
/**
* Method updates data of a created task or creates new task from scratch
* @method save
* @returns {module:API.cvat.classes.Task}
* @memberof module:API.cvat.classes.Task
* @param {function} [onUpdate] - the function which is used only if task hasn't
* been created yet. It called in order to notify about creation status.
* It receives the string parameter which is a status message
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async save(onUpdate = () => {}) {
const result = await PluginRegistry
.apiWrapper.call(this, Task.prototype.save, onUpdate);
return result;
}
/**
* Method deletes a task from a server
* @method delete
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async delete() {
const result = await PluginRegistry
.apiWrapper.call(this, Task.prototype.delete);
return result;
}
}
module.exports = {
Job,
Task,
};
})();

@ -0,0 +1,91 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
(() => {
/**
* Class representing statistics inside a session
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Statistics {
constructor(label, total) {
Object.defineProperties(this, Object.freeze({
/**
* Statistics by labels which have a structure:
* @example
* {
* label1: {
* boxes: {
* tracks: 10,
* shapes: 11,
* },
* polygons: {
* tracks: 12,
* shapes: 13,
* },
* polylines: {
* tracks: 14,
* shapes: 15,
* },
* points: {
* tracks: 16,
* shapes: 17,
* },
* manually: 108,
* interpolated: 500,
* total: 608,
* }
* }
* @name label
* @type {Object}
* @memberof module:API.cvat.classes.Statistics
* @readonly
* @instance
*/
label: {
get: () => JSON.parse(JSON.stringify(label)),
},
/**
* Total statistics (summed by all labels) which have a structure:
* @example
* {
* total: {
* boxes: {
* tracks: 10,
* shapes: 11,
* },
* polygons: {
* tracks: 12,
* shapes: 13,
* },
* polylines: {
* tracks: 14,
* shapes: 15,
* },
* points: {
* tracks: 16,
* shapes: 17,
* },
* manually: 108,
* interpolated: 500,
* total: 608,
* }
* }
* @name total
* @type {Object}
* @memberof module:API.cvat.classes.Statistics
* @readonly
* @instance
*/
total: {
get: () => JSON.parse(JSON.stringify(total)),
},
}));
}
}
module.exports = Statistics;
})();

@ -0,0 +1,151 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
(() => {
/**
* Class representing a user
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class User {
constructor(initialData) {
const data = {
id: null,
username: null,
email: null,
first_name: null,
last_name: null,
groups: null,
last_login: null,
date_joined: null,
is_staff: null,
is_superuser: null,
is_active: null,
};
for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property)
&& property in initialData) {
data[property] = initialData[property];
}
}
Object.defineProperties(this, Object.freeze({
id: {
/**
* @name id
* @type {integer}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.id,
},
username: {
/**
* @name username
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.username,
},
email: {
/**
* @name email
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.email,
},
firstName: {
/**
* @name firstName
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.first_name,
},
lastName: {
/**
* @name lastName
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.last_name,
},
groups: {
/**
* @name groups
* @type {string[]}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => JSON.parse(JSON.stringify(data.groups)),
},
lastLogin: {
/**
* @name lastLogin
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.last_login,
},
dateJoined: {
/**
* @name dateJoined
* @type {string}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.date_joined,
},
isStaff: {
/**
* @name isStaff
* @type {boolean}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.is_staff,
},
isSuperuser: {
/**
* @name isSuperuser
* @type {boolean}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.is_superuser,
},
isActive: {
/**
* @name isActive
* @type {boolean}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => data.is_active,
},
}));
}
}
module.exports = User;
})();

@ -0,0 +1,118 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
const { Job } = require('../../src/session');
// Test cases
describe('Feature: get a list of jobs', () => {
test('get jobs by a task id', async () => {
const result = await window.cvat.jobs.get({
taskID: 3,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(2);
for (const el of result) {
expect(el).toBeInstanceOf(Job);
}
expect(result[0].task.id).toBe(3);
expect(result[0].task).toBe(result[1].task);
});
test('get jobs by an unknown task id', async () => {
const result = await window.cvat.jobs.get({
taskID: 50,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(0);
});
test('get jobs by a job id', async () => {
const result = await window.cvat.jobs.get({
jobID: 1,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(1);
expect(result[0].id).toBe(1);
});
test('get jobs by an unknown job id', async () => {
const result = await window.cvat.jobs.get({
taskID: 50,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(0);
});
test('get jobs by invalid filter with both taskID and jobID', async () => {
await expect(window.cvat.jobs.get({
taskID: 1,
jobID: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by invalid job id', async () => {
await expect(window.cvat.jobs.get({
jobID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by invalid task id', async () => {
await expect(window.cvat.jobs.get({
taskID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by unknown filter', async () => {
await expect(window.cvat.jobs.get({
unknown: 50,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: save job', () => {
test('save status of a job', async () => {
let result = await window.cvat.jobs.get({
jobID: 1,
});
result[0].status = 'validation';
await result[0].save();
result = await window.cvat.jobs.get({
jobID: 1,
});
expect(result[0].status).toBe('validation');
});
test('save invalid status of a job', async () => {
const result = await window.cvat.jobs.get({
jobID: 1,
});
await result[0].save();
expect(() => {
result[0].status = 'invalid';
}).toThrow(window.cvat.exceptions.ArgumentError);
});
});

@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
// Test cases
describe('Feature: get info about cvat', () => {
test('get info about server', async () => {
const result = await window.cvat.server.about();
expect(result).toBeInstanceOf(Object);
expect('name' in result).toBeTruthy();
expect('description' in result).toBeTruthy();
expect('version' in result).toBeTruthy();
});
});
describe('Feature: get share storage info', () => {
test('get files in a root of a share storage', async () => {
const result = await window.cvat.server.share();
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(5);
});
test('get files in a some dir of a share storage', async () => {
const result = await window.cvat.server.share('images');
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(8);
});
test('get files in a some unknown dir of a share storage', async () => {
await expect(window.cvat.server.share(
'Unknown Directory',
)).rejects.toThrow(window.cvat.exceptions.ServerError);
});
});

@ -0,0 +1,260 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
const { Task } = require('../../src/session');
// Test cases
describe('Feature: get a list of tasks', () => {
test('get all tasks', async () => {
const result = await window.cvat.tasks.get();
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(3);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
}
});
test('get a task by an id', async () => {
const result = await window.cvat.tasks.get({
id: 3,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(Task);
expect(result[0].id).toBe(3);
});
test('get a task by an unknown id', async () => {
const result = await window.cvat.tasks.get({
id: 50,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(0);
});
test('get a task by an invalid id', async () => {
await expect(window.cvat.tasks.get({
id: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get tasks by filters', async () => {
const result = await window.cvat.tasks.get({
mode: 'interpolation',
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(2);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
expect(el.mode).toBe('interpolation');
}
});
test('get tasks by invalid filters', async () => {
await expect(window.cvat.tasks.get({
unknown: '5',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get task by name, status and mode', async () => {
const result = await window.cvat.tasks.get({
mode: 'interpolation',
status: 'annotation',
name: 'Test Task',
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(1);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
expect(el.mode).toBe('interpolation');
expect(el.status).toBe('annotation');
expect(el.name).toBe('Test Task');
}
});
});
describe('Feature: save a task', () => {
test('save some changed fields in a task', async () => {
let result = await window.cvat.tasks.get({
id: 2,
});
result[0].bugTracker = 'newBugTracker';
result[0].zOrder = true;
result[0].name = 'New Task Name';
result[0].save();
result = await window.cvat.tasks.get({
id: 2,
});
expect(result[0].bugTracker).toBe('newBugTracker');
expect(result[0].zOrder).toBe(true);
expect(result[0].name).toBe('New Task Name');
});
test('save some new labels in a task', async () => {
let result = await window.cvat.tasks.get({
id: 2,
});
const labelsLength = result[0].labels.length;
const newLabel = new window.cvat.classes.Label({
name: 'My boss\'s car',
attributes: [{
default_value: 'false',
input_type: 'checkbox',
mutable: true,
name: 'parked',
values: ['false'],
}],
});
result[0].labels = [newLabel];
result[0].save();
result = await window.cvat.tasks.get({
id: 2,
});
expect(result[0].labels).toHaveLength(labelsLength + 1);
const appendedLabel = result[0].labels.filter(el => el.name === 'My boss\'s car');
expect(appendedLabel).toHaveLength(1);
expect(appendedLabel[0].attributes).toHaveLength(1);
expect(appendedLabel[0].attributes[0].name).toBe('parked');
expect(appendedLabel[0].attributes[0].defaultValue).toBe('false');
expect(appendedLabel[0].attributes[0].mutable).toBe(true);
expect(appendedLabel[0].attributes[0].inputType).toBe('checkbox');
});
test('save new task without an id', async () => {
const task = new window.cvat.classes.Task({
name: 'New Task',
labels: [{
name: 'My boss\'s car',
attributes: [{
default_value: 'false',
input_type: 'checkbox',
mutable: true,
name: 'parked',
values: ['false'],
}],
}],
bug_tracker: 'bug tracker value',
image_quality: 50,
z_order: true,
});
const result = await task.save();
expect(typeof (result.id)).toBe('number');
});
});
describe('Feature: delete a task', () => {
test('delete a task', async () => {
let result = await window.cvat.tasks.get({
id: 3,
});
result[0].delete();
result = await window.cvat.tasks.get({
id: 3,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(0);
});
});
/*
const plugin = {
name: 'Example Plugin',
description: 'This example plugin demonstrates how plugin system in CVAT works',
cvat: {
server: {
about: {
async leave(self, result) {
result.plugins = await self.internal.getPlugins();
return result;
},
},
},
classes: {
Job: {
prototype: {
annotations: {
put: {
enter(self, objects) {
for (const obj of objects) {
if (obj.type !== 'tag') {
const points = obj.position.map((point) => {
const roundPoint = {
x: Math.round(point.x),
y: Math.round(point.y),
};
return roundPoint;
});
obj.points = points;
}
}
},
},
},
},
},
},
},
internal: {
async getPlugins() {
const plugins = await window.cvat.plugins.list();
return plugins.map((el) => {
const obj = {
name: el.name,
description: el.description,
};
return obj;
});
},
},
};
async function test() {
await window.cvat.plugins.register(plugin);
await window.cvat.server.login('admin', 'nimda760');
try {
console.log(JSON.stringify(await window.cvat.server.about()));
console.log(await window.cvat.users.get({ self: false }));
console.log(await window.cvat.users.get({ self: true }));
console.log(JSON.stringify(await window.cvat.jobs.get({ taskID: 8 })));
console.log(JSON.stringify(await window.cvat.jobs.get({ jobID: 10 })));
console.log(await window.cvat.tasks.get());
console.log(await window.cvat.tasks.get({ id: 8 }));
console.log('Done.');
} catch (exception) {
console.log(exception.constructor.name);
console.log(exception.message);
}
}
*/

@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
const User = require('../../src/user');
// Test cases
describe('Feature: get a list of users', () => {
test('get all users', async () => {
const result = await window.cvat.users.get();
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(2);
for (const el of result) {
expect(el).toBeInstanceOf(User);
}
});
test('get only self', async () => {
const result = await window.cvat.users.get({
self: true,
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(User);
});
test('get users with unknown filter key', async () => {
await expect(window.cvat.users.get({
unknown: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get users with invalid filter key', async () => {
await expect(window.cvat.users.get({
self: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});

@ -0,0 +1,889 @@
/* eslint-disable */
const aboutDummyData = {
"name": "Computer Vision Annotation Tool",
"description": "CVAT is completely re-designed and re-implemented version of Video Annotation Tool from Irvine, California tool. It is free, online, interactive video and image annotation tool for computer vision. It is being used by our team to annotate million of objects with different properties. Many UI and UX decisions are based on feedbacks from professional data annotation team.",
"version": "0.5.dev20190516142240"
}
const usersDummyData = {
"count": 2,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:7000/api/v1/users/1",
"id": 1,
"username": "admin",
"first_name": "",
"last_name": "",
"email": "admin@dummy.com",
"groups": [
"admin"
],
"is_staff": true,
"is_superuser": true,
"is_active": true,
"last_login": "2019-05-17T11:53:05.961434+03:00",
"date_joined": "2019-05-13T15:33:17.833200+03:00"
},
{
"url": "http://localhost:7000/api/v1/users/2",
"id": 2,
"username": "bsekache",
"first_name": "",
"last_name": "",
"email": "",
"groups": [
"user",
"observer"
],
"is_staff": false,
"is_superuser": false,
"is_active": true,
"last_login": "2019-05-16T13:07:19.564241+03:00",
"date_joined": "2019-05-16T13:05:57+03:00"
}
]
}
const shareDummyData = [
{
"name": "images",
"type": "DIR",
"children": [
{
"name": "image000001.jpg",
"type": "REG"
},
{
"name": "nowy-jork-time-sqare.jpg",
"type": "REG"
},
{
"name": "123123.jpg",
"type": "REG"
},
{
"name": "ws_Oasis-night_1920x1200.jpg",
"type": "REG"
},
{
"name": "image000002.jpg",
"type": "REG"
},
{
"name": "fdgdfgfd.jpg",
"type": "REG"
},
{
"name": "bbbbb.jpg",
"type": "REG"
},
{
"name": "gdfgdfgdf.jpg",
"type": "REG"
}
]
},
{
"name": "2.avi",
"type": "REG"
},
{
"name": "data",
"type": "DIR",
"children": [],
},
{
"name": "out.MOV",
"type": "REG"
},
{
"name": "bbbbb.jpg",
"type": "REG"
}
]
const tasksDummyData = {
"count": 3,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:7000/api/v1/tasks/3",
"id": 3,
"name": "Test Task",
"size": 5002,
"mode": "interpolation",
"owner": 2,
"assignee": null,
"bug_tracker": "",
"created_date": "2019-05-16T13:08:00.621747+03:00",
"updated_date": "2019-05-16T13:08:00.621797+03:00",
"overlap": 5,
"segment_size": 5000,
"z_order": true,
"flipped": false,
"status": "annotation",
"labels": [
{
"id": 16,
"name": "bicycle",
"attributes": [
{
"id": 43,
"name": "driver",
"mutable": false,
"input_type": "radio",
"default_value": "man",
"values": [
"man",
"woman"
]
},
{
"id": 44,
"name": "sport",
"mutable": true,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
}
]
},
{
"id": 15,
"name": "car",
"attributes": [
{
"id": 40,
"name": "model",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"bmw",
"mazda",
"suzuki",
"kia"
]
},
{
"id": 41,
"name": "driver",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"man",
"woman"
]
},
{
"id": 42,
"name": "parked",
"mutable": true,
"input_type": "checkbox",
"default_value": "true",
"values": [
"true"
]
}
]
},
{
"id": 14,
"name": "face",
"attributes": [
{
"id": 36,
"name": "age",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"baby (0-5)",
"child (6-12)",
"adolescent (13-19)",
"adult (20-45)",
"middle-age (46-64)",
"old (65-)"
]
},
{
"id": 37,
"name": "glass",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"sunglass",
"transparent",
"other"
]
},
{
"id": 38,
"name": "beard",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"yes"
]
},
{
"id": 39,
"name": "race",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"asian",
"black",
"caucasian",
"other"
]
}
]
},
{
"id": 17,
"name": "motorcycle",
"attributes": [
{
"id": 45,
"name": "model",
"mutable": false,
"input_type": "text",
"default_value": "unknown",
"values": [
"unknown"
]
}
]
},
{
"id": 13,
"name": "person, pedestrian",
"attributes": [
{
"id": 31,
"name": "action",
"mutable": true,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"sitting",
"raising_hand",
"standing"
]
},
{
"id": 32,
"name": "age",
"mutable": false,
"input_type": "number",
"default_value": "1",
"values": [
"1",
"100",
"1"
]
},
{
"id": 33,
"name": "gender",
"mutable": false,
"input_type": "select",
"default_value": "male",
"values": [
"male",
"female"
]
},
{
"id": 34,
"name": "false positive",
"mutable": false,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
},
{
"id": 35,
"name": "clother",
"mutable": true,
"input_type": "text",
"default_value": "non, initialized",
"values": [
"non, initialized"
]
}
]
},
{
"id": 18,
"name": "road",
"attributes": []
}
],
"segments": [
{
"start_frame": 0,
"stop_frame": 4999,
"jobs": [
{
"url": "http://localhost:7000/api/v1/jobs/3",
"id": 3,
"assignee": null,
"status": "annotation"
}
]
},
{
"start_frame": 4995,
"stop_frame": 5001,
"jobs": [
{
"url": "http://localhost:7000/api/v1/jobs/4",
"id": 4,
"assignee": null,
"status": "annotation"
}
]
}
],
"image_quality": 50
},
{
"url": "http://localhost:7000/api/v1/tasks/2",
"id": 2,
"name": "Video",
"size": 75,
"mode": "interpolation",
"owner": 1,
"assignee": null,
"bug_tracker": "",
"created_date": "2019-05-15T11:40:19.487999+03:00",
"updated_date": "2019-05-15T16:58:27.992785+03:00",
"overlap": 5,
"segment_size": 0,
"z_order": false,
"flipped": false,
"status": "annotation",
"labels": [
{
"id": 10,
"name": "bicycle",
"attributes": [
{
"id": 28,
"name": "driver",
"mutable": false,
"input_type": "radio",
"default_value": "man",
"values": [
"man",
"woman"
]
},
{
"id": 29,
"name": "sport",
"mutable": true,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
}
]
},
{
"id": 9,
"name": "car",
"attributes": [
{
"id": 25,
"name": "model",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"bmw",
"mazda",
"suzuki",
"kia"
]
},
{
"id": 26,
"name": "driver",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"man",
"woman"
]
},
{
"id": 27,
"name": "parked",
"mutable": true,
"input_type": "checkbox",
"default_value": "true",
"values": [
"true"
]
}
]
},
{
"id": 8,
"name": "face",
"attributes": [
{
"id": 21,
"name": "age",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"baby (0-5)",
"child (6-12)",
"adolescent (13-19)",
"adult (20-45)",
"middle-age (46-64)",
"old (65-)"
]
},
{
"id": 22,
"name": "glass",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"sunglass",
"transparent",
"other"
]
},
{
"id": 23,
"name": "beard",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"yes"
]
},
{
"id": 24,
"name": "race",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"asian",
"black",
"caucasian",
"other"
]
}
]
},
{
"id": 11,
"name": "motorcycle",
"attributes": [
{
"id": 30,
"name": "model",
"mutable": false,
"input_type": "text",
"default_value": "unknown",
"values": [
"unknown"
]
}
]
},
{
"id": 7,
"name": "person, pedestrian",
"attributes": [
{
"id": 16,
"name": "action",
"mutable": true,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"sitting",
"raising_hand",
"standing"
]
},
{
"id": 17,
"name": "age",
"mutable": false,
"input_type": "number",
"default_value": "1",
"values": [
"1",
"100",
"1"
]
},
{
"id": 18,
"name": "gender",
"mutable": false,
"input_type": "select",
"default_value": "male",
"values": [
"male",
"female"
]
},
{
"id": 19,
"name": "false positive",
"mutable": false,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
},
{
"id": 20,
"name": "clother",
"mutable": true,
"input_type": "text",
"default_value": "non, initialized",
"values": [
"non, initialized"
]
}
]
},
{
"id": 12,
"name": "road",
"attributes": []
}
],
"segments": [
{
"start_frame": 0,
"stop_frame": 74,
"jobs": [
{
"url": "http://localhost:7000/api/v1/jobs/2",
"id": 2,
"assignee": null,
"status": "annotation"
}
]
}
],
"image_quality": 50
},
{
"url": "http://localhost:7000/api/v1/tasks/1",
"id": 1,
"name": "Labels Set",
"size": 9,
"mode": "annotation",
"owner": 1,
"assignee": null,
"bug_tracker": "http://bugtracker.com/issue12345",
"created_date": "2019-05-13T15:35:29.871003+03:00",
"updated_date": "2019-05-15T11:20:55.770587+03:00",
"overlap": 0,
"segment_size": 0,
"z_order": true,
"flipped": false,
"status": "annotation",
"labels": [
{
"id": 4,
"name": "bicycle",
"attributes": [
{
"id": 13,
"name": "driver",
"mutable": false,
"input_type": "radio",
"default_value": "man",
"values": [
"man",
"woman"
]
},
{
"id": 14,
"name": "sport",
"mutable": true,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
}
]
},
{
"id": 3,
"name": "car",
"attributes": [
{
"id": 10,
"name": "model",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"bmw",
"mazda",
"suzuki",
"kia"
]
},
{
"id": 11,
"name": "driver",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"man",
"woman"
]
},
{
"id": 12,
"name": "parked",
"mutable": true,
"input_type": "checkbox",
"default_value": "true",
"values": [
"true"
]
}
]
},
{
"id": 2,
"name": "face",
"attributes": [
{
"id": 6,
"name": "age",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"baby (0-5)",
"child (6-12)",
"adolescent (13-19)",
"adult (20-45)",
"middle-age (46-64)",
"old (65-)"
]
},
{
"id": 7,
"name": "glass",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"sunglass",
"transparent",
"other"
]
},
{
"id": 8,
"name": "beard",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"no",
"yes"
]
},
{
"id": 9,
"name": "race",
"mutable": false,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"skip",
"asian",
"black",
"caucasian",
"other"
]
}
]
},
{
"id": 5,
"name": "motorcycle",
"attributes": [
{
"id": 15,
"name": "model",
"mutable": false,
"input_type": "text",
"default_value": "unknown",
"values": [
"unknown"
]
}
]
},
{
"id": 1,
"name": "person, pedestrian",
"attributes": [
{
"id": 1,
"name": "action",
"mutable": true,
"input_type": "select",
"default_value": "__undefined__",
"values": [
"__undefined__",
"sitting",
"raising_hand",
"standing"
]
},
{
"id": 2,
"name": "age",
"mutable": false,
"input_type": "number",
"default_value": "1",
"values": [
"1",
"100",
"1"
]
},
{
"id": 3,
"name": "gender",
"mutable": false,
"input_type": "select",
"default_value": "male",
"values": [
"male",
"female"
]
},
{
"id": 4,
"name": "false positive",
"mutable": false,
"input_type": "checkbox",
"default_value": "false",
"values": [
"false"
]
},
{
"id": 5,
"name": "clother",
"mutable": true,
"input_type": "text",
"default_value": "non, initialized",
"values": [
"non, initialized"
]
}
]
},
{
"id": 6,
"name": "road",
"attributes": []
}
],
"segments": [
{
"start_frame": 0,
"stop_frame": 8,
"jobs": [
{
"url": "http://localhost:7000/api/v1/jobs/1",
"id": 1,
"assignee": null,
"status": "annotation"
}
]
}
],
"image_quality": 95
}
]
}
module.exports = {
tasksDummyData,
aboutDummyData,
shareDummyData,
usersDummyData,
}

@ -0,0 +1,237 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* eslint import/no-extraneous-dependencies: 0 */
/* global
require:false
*/
const {
tasksDummyData,
aboutDummyData,
shareDummyData,
usersDummyData,
} = require('./dummy-data.mock');
class ServerProxy {
constructor() {
async function about() {
return JSON.parse(JSON.stringify(aboutDummyData));
}
async function share(directory) {
let position = shareDummyData;
// Emulation of internal directories
if (directory.length > 1) {
const components = directory.split('/');
for (const component of components) {
const idx = position.map(x => x.name).indexOf(component);
if (idx !== -1 && 'children' in position[idx]) {
position = position[idx].children;
} else {
throw new window.cvat.exceptions.ServerError(
`${component} is not a valid directory`,
400,
);
}
}
}
return JSON.parse(JSON.stringify(position));
}
async function exception() {
return null;
}
async function login() {
return null;
}
async function getTasks(filter = '') {
function QueryStringToJSON(query) {
const pairs = [...new URLSearchParams(query).entries()];
const result = {};
for (const pair of pairs) {
const [key, value] = pair;
if (['id'].includes(key)) {
result[key] = +value;
} else {
result[key] = value;
}
}
return JSON.parse(JSON.stringify(result));
}
// Emulation of a query filter
const queries = QueryStringToJSON(filter);
const result = tasksDummyData.results.filter((x) => {
for (const key in queries) {
if (Object.prototype.hasOwnProperty.call(queries, key)) {
// TODO: Particular match for some fields is not checked
if (queries[key] !== x[key]) {
return false;
}
}
}
return true;
});
return result;
}
async function saveTask(id, taskData) {
const object = tasksDummyData.results.filter(task => task.id === id)[0];
for (const prop in taskData) {
if (Object.prototype.hasOwnProperty.call(taskData, prop)
&& Object.prototype.hasOwnProperty.call(object, prop)) {
object[prop] = taskData[prop];
}
}
}
async function createTask(taskData) {
const id = Math.max(...tasksDummyData.results.map(el => el.id)) + 1;
tasksDummyData.results.push({
id,
url: `http://localhost:7000/api/v1/tasks/${id}`,
name: taskData.name,
size: 5000,
mode: 'interpolation',
owner: 2,
assignee: null,
bug_tracker: taskData.bug_tracker,
created_date: '2019-05-16T13:08:00.621747+03:00',
updated_date: '2019-05-16T13:08:00.621797+03:00',
overlap: taskData.overlap ? taskData.overlap : 5,
segment_size: taskData.segment_size ? taskData.segment_size : 5000,
z_order: taskData.z_order,
flipped: false,
status: 'annotation',
image_quality: taskData.image_quality,
labels: JSON.parse(JSON.stringify(taskData.labels)),
});
const createdTask = await getTasks(`?id=${id}`);
return createdTask[0];
}
async function deleteTask(id) {
const tasks = tasksDummyData.results;
const task = tasks.filter(el => el.id === id)[0];
if (task) {
tasks.splice(tasks.indexOf(task), 1);
}
}
async function getJob(jobID) {
return tasksDummyData.results.reduce((acc, task) => {
for (const segment of task.segments) {
for (const job of segment.jobs) {
const copy = JSON.parse(JSON.stringify(job));
copy.start_frame = segment.start_frame;
copy.stop_frame = segment.stop_frame;
copy.task_id = task.id;
acc.push(copy);
}
}
return acc;
}, []).filter(job => job.id === jobID);
}
async function saveJob(id, jobData) {
const object = tasksDummyData.results.reduce((acc, task) => {
for (const segment of task.segments) {
for (const job of segment.jobs) {
acc.push(job);
}
}
return acc;
}, []).filter(job => job.id === id)[0];
for (const prop in jobData) {
if (Object.prototype.hasOwnProperty.call(jobData, prop)
&& Object.prototype.hasOwnProperty.call(object, prop)) {
object[prop] = jobData[prop];
}
}
}
async function getUsers() {
return JSON.parse(JSON.stringify(usersDummyData)).results;
}
async function getSelf() {
return JSON.parse(JSON.stringify(usersDummyData)).results[0];
}
async function getFrame() {
return null;
}
async function getMeta() {
return null;
}
Object.defineProperties(this, Object.freeze({
server: {
value: Object.freeze({
about,
share,
exception,
login,
}),
writable: false,
},
tasks: {
value: Object.freeze({
getTasks,
saveTask,
createTask,
deleteTask,
}),
writable: false,
},
jobs: {
value: Object.freeze({
getJob,
saveJob,
}),
writable: false,
},
users: {
value: Object.freeze({
getUsers,
getSelf,
}),
writable: false,
},
frames: {
value: Object.freeze({
getFrame,
getMeta,
}),
writable: false,
},
}));
}
}
const serverProxy = new ServerProxy();
module.exports = serverProxy;

@ -0,0 +1,59 @@
/* global
require:true,
__dirname:true,
*/
const path = require('path');
const webpack = require('webpack');
const configuration = 'production';
const target = 'web';
const plugins = [];
if (configuration === 'production' && target === 'web') {
plugins.push(
/**
* IgnorePlugin will skip any require
* that matches the following regex.
*/
new webpack.IgnorePlugin(/browser-env/),
);
}
module.exports = {
mode: configuration,
devtool: 'source-map',
entry: './src/api.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'cvat.js',
},
module: {
rules: [{
test: /.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: {
chrome: 58,
},
useBuiltIns: 'usage',
corejs: 3,
loose: false,
spec: false,
debug: false,
include: [],
exclude: [],
}],
],
sourceType: 'unambiguous',
},
},
}],
},
plugins,
target,
};
Loading…
Cancel
Save