Getting annotations with CVAT.js (#529)

* Fixed authorization in case of invalid login/password
* The first version of frames
* Frames work in browser
* Init version of getting annotations
* Getting annotations from the server
* Couple of fixes
* Removed extra line
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 5290882259
commit a82546210c

@ -47,5 +47,6 @@
"security/detect-object-injection": 0,
"indent": ["warn", 4],
"no-useless-constructor": 0,
"func-names": [0],
},
};

@ -0,0 +1,573 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const serverProxy = require('./server-proxy');
const ObjectState = require('./object-state');
class Annotation {
constructor(data, clientID, injection) {
this.clientID = clientID;
this.serverID = data.id;
this.labelID = data.label_id;
this.frame = data.frame;
this.attributes = data.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator;
}, {});
this.taskLabels = injection.labels;
}
}
class Shape extends Annotation {
constructor(data, clientID, color, injection) {
super(data, clientID, injection);
this.points = data.points;
this.occluded = data.occluded;
this.zOrder = data.z_order;
this.group = data.group;
this.color = color;
this.shape = null;
}
toJSON() {
return {
occluded: this.occluded,
z_order: this.zOrder,
points: [...this.points],
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({
spec_id: attrId,
value: this.attributes[attrId],
});
return attributeAccumulator;
}, []),
id: this.serverID,
frame: this.frame,
label_id: this.labelID,
group: this.group,
};
}
get(frame) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
return {
type: window.cvat.enums.ObjectType.SHAPE,
shape: this.shape,
clientID: this.clientID,
occluded: this.occluded,
zOrder: this.zOrder,
points: [...this.points],
attributes: Object.assign({}, this.attributes),
label: this.taskLabels[this.labelID],
group: this.group,
};
}
}
class Track extends Annotation {
constructor(data, clientID, color, injection) {
super(data, clientID, injection);
this.shapes = data.shapes.reduce((shapeAccumulator, value) => {
shapeAccumulator[value.frame] = {
serverID: value.id,
occluded: value.occluded,
zOrder: value.z_order,
points: value.points,
id: value.id,
frame: value.frame,
outside: value.outside,
attributes: value.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator;
}, {}),
};
return shapeAccumulator;
}, {});
this.group = data.group;
this.attributes = data.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator;
}, {});
this.color = color;
this.shape = null;
}
toJSON() {
return {
occluded: this.occluded,
z_order: this.zOrder,
points: [...this.points],
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({
spec_id: attrId,
value: this.attributes[attrId],
});
return attributeAccumulator;
}, []),
id: this.serverID,
frame: this.frame,
label_id: this.labelID,
group: this.group,
shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => {
shapesAccumulator.push({
type: this.type,
occluded: this.shapes[frame].occluded,
z_order: this.shapes[frame].zOrder,
points: [...this.shapes[frame].points],
outside: [...this.shapes[frame].outside],
attributes: Object.keys(...this.shapes[frame].attributes)
.reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({
spec_id: attrId,
value: this.shapes[frame].attributes[attrId],
});
return attributeAccumulator;
}, []),
id: this.shapes[frame].serverID,
frame: +frame,
});
return shapesAccumulator;
}, []),
};
}
get(targetFrame) {
return Object.assign(
{}, this.interpolatePosition(targetFrame),
{
attributes: this.interpolateAttributes(targetFrame),
label: this.taskLabels[this.labelID],
group: this.group,
type: window.cvat.enums.ObjectType.TRACK,
shape: this.shape,
clientID: this.clientID,
},
);
}
neighborsFrames(targetFrame) {
const frames = Object.keys(this.shapes).map(frame => +frame);
let lDiff = Number.MAX_SAFE_INTEGER;
let rDiff = Number.MAX_SAFE_INTEGER;
for (const frame of frames) {
const diff = Math.abs(targetFrame - frame);
if (frame <= targetFrame && diff < lDiff) {
lDiff = diff;
} else if (diff < rDiff) {
rDiff = diff;
}
}
const leftFrame = lDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame - lDiff;
const rightFrame = rDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame + rDiff;
return {
leftFrame,
rightFrame,
};
}
interpolateAttributes(targetFrame) {
const result = {};
// First of all copy all unmutable attributes
for (const attrID in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attrID)) {
result[attrID] = this.attributes[attrID];
}
}
// Secondly get latest mutable attributes up to target frame
const frames = Object.keys(this.shapes).sort((a, b) => +a - +b);
for (const frame of frames) {
if (frame <= targetFrame) {
const { attributes } = this.shapes[frame];
for (const attrID in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrID)) {
result[attrID] = attributes[attrID];
}
}
}
}
// Finally fill up remained attributes if they exist
const labelAttributes = this.taskLabels[this.labelID].attributes;
const defValuesByID = labelAttributes.reduce((accumulator, attr) => {
accumulator[attr.id] = attr.defaultValue;
return accumulator;
}, {});
for (const attrID of Object.keys(defValuesByID)) {
if (!(attrID in result)) {
result[attrID] = defValuesByID[attrID];
}
}
return result;
}
}
class Tag extends Annotation {
constructor(data, clientID, injection) {
super(data, clientID, injection);
}
toJSON() {
// TODO: Tags support
return {};
}
get(frame) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
}
}
class RectangleShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.RECTANGLE;
}
}
class PolyShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
}
}
class PolygonShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POLYGON;
}
}
class PolylineShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POLYLINE;
}
}
class PointsShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POINTS;
}
}
class RectangleTrack extends Track {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.RECTANGLE;
}
interpolatePosition(targetFrame) {
const {
leftFrame,
rightFrame,
} = this.neighborsFrames(targetFrame);
const rightPosition = rightFrame ? this.shapes[rightFrame] : null;
const leftPosition = leftFrame ? this.shapes[leftFrame] : null;
if (leftPosition && leftFrame === targetFrame) {
return {
points: [...leftPosition.points],
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
};
}
if (rightPosition && leftPosition) {
const offset = (targetFrame - leftFrame) / (rightPosition - leftPosition);
const positionOffset = [
rightPosition.points[0] - leftPosition.points[0],
rightPosition.points[1] - leftPosition.points[1],
rightPosition.points[2] - leftPosition.points[2],
rightPosition.points[3] - leftPosition.points[3],
];
return { // xtl, ytl, xbr, ybr
points: [
leftPosition.points[0] + positionOffset[0] * offset,
leftPosition.points[1] + positionOffset[1] * offset,
leftPosition.points[2] + positionOffset[2] * offset,
leftPosition.points[3] + positionOffset[3] * offset,
],
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
};
}
if (rightPosition) {
return {
points: [...rightPosition.points],
occluded: rightPosition.occluded,
outside: true,
zOrder: 0,
};
}
if (leftPosition) {
return {
points: [...leftPosition.points],
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: 0,
};
}
throw new window.cvat.exceptions.ScriptingError(
`No one neightbour frame found for the track with client ID: "${this.id}"`,
);
}
}
class PolyTrack extends Track {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
}
}
class PolygonTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POLYGON;
}
}
class PolylineTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POLYLINE;
}
}
class PointsTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shape = window.cvat.enums.ObjectShape.POINTS;
}
}
const colors = [
'#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA',
'#8359A3', '#FBE870', '#C5E17A', '#03BB85', '#FFDF00', '#8B8680', '#0A6B0D',
'#8FD8D8', '#A36F40', '#F653A6', '#CA3435', '#FFCBA4', '#FF99CC', '#FA9D5A',
'#FFAE42', '#A78B00', '#788193', '#514E49', '#1164B4', '#F4FA9F', '#FED8B1',
'#C32148', '#01796F', '#E90067', '#FF91A4', '#404E5A', '#6CDAE7', '#FFC1CC',
'#006A93', '#867200', '#E2B631', '#6EEB6E', '#FFC800', '#CC99BA', '#FF007C',
'#BC6CAC', '#DCCCD7', '#EBE1C2', '#A6AAAE', '#B99685', '#0086A7', '#5E4330',
'#C8A2C8', '#708EB3', '#BC8777', '#B2592D', '#497E48', '#6A2963', '#E6335F',
'#00755E', '#B5A895', '#0048ba', '#EED9C4', '#C88A65', '#FF6E4A', '#87421F',
'#B2BEB5', '#926F5B', '#00B9FB', '#6456B7', '#DB5079', '#C62D42', '#FA9C44',
'#DA8A67', '#FD7C6E', '#93CCEA', '#FCF686', '#503E32', '#FF5470', '#9DE093',
'#FF7A00', '#4F69C6', '#A50B5E', '#F0E68C', '#FDFF00', '#F091A9', '#FFFF66',
'#6F9940', '#FC74FD', '#652DC1', '#D6AEDD', '#EE34D2', '#BB3385', '#6B3FA0',
'#33CC99', '#FFDB00', '#87FF2A', '#6EEB6E', '#FFC800', '#CC99BA', '#7A89B8',
'#006A93', '#867200', '#E2B631', '#D9D6CF',
];
class Collection {
constructor(labels) {
this.labels = labels.reduce((labelAccumulator, label) => {
labelAccumulator[label.id] = label;
return labelAccumulator;
}, {});
this.empty();
}
import(data) {
this.empty();
const injection = {
labels: this.labels,
};
function shapeFactory(shapeData, clientID) {
const { type } = shapeData;
const color = colors[clientID % colors.length];
let shapeModel = null;
switch (type) {
case 'rectangle':
shapeModel = new RectangleShape(shapeData, clientID, color, injection);
break;
case 'polygon':
shapeModel = new PolygonShape(shapeData, clientID, color, injection);
break;
case 'polyline':
shapeModel = new PolylineShape(shapeData, clientID, color, injection);
break;
case 'points':
shapeModel = new PointsShape(shapeData, clientID, color, injection);
break;
default:
throw new window.cvat.exceptions.DataError(
`An unexpected type of shape "${type}"`,
);
}
return shapeModel;
}
function trackFactory(trackData, clientID) {
if (trackData.shapes.length) {
const { type } = trackData.shapes[0];
const color = colors[clientID % colors.length];
let trackModel = null;
switch (type) {
case 'rectangle':
trackModel = new RectangleTrack(trackData, clientID, color, injection);
break;
case 'polygon':
trackModel = new PolygonTrack(trackData, clientID, color, injection);
break;
case 'polyline':
trackModel = new PolylineTrack(trackData, clientID, color, injection);
break;
case 'points':
trackModel = new PointsTrack(trackData, clientID, color, injection);
break;
default:
throw new window.cvat.exceptions.DataError(
`An unexpected type of track "${type}"`,
);
}
return trackModel;
}
console.warn('The track without any shapes had been found. It was ignored.');
return null;
}
for (const tag of data.tags) {
const clientID = ++this.count;
const tagModel = new Tag(tag, clientID, injection);
this.tags[tagModel.frame] = this.tags[tagModel.frame] || [];
this.tags[tagModel.frame].push(tagModel);
this.objects[clientID] = tagModel;
}
for (const shape of data.shapes) {
const clientID = ++this.count;
const shapeModel = shapeFactory(shape, clientID);
this.shapes[shapeModel.frame] = this.shapes[shapeModel.frame] || [];
this.shapes[shapeModel.frame].push(shapeModel);
this.objects[clientID] = shapeModel;
}
for (const track of data.tracks) {
const clientID = ++this.count;
const trackModel = trackFactory(track, clientID);
// The function can return null if track doesn't have any shapes.
// In this case a corresponded message will be sent to the console
if (trackModel) {
this.tracks.push(trackModel);
this.objects[clientID] = trackModel;
}
}
}
export() {
const data = {
tracks: Object.values(this.tracks).reduce((accumulator, value) => {
accumulator.push(...value);
return accumulator;
}, []).map(track => track.toJSON()),
shapes: this.shapes.map(shape => shape.toJSON()),
tags: this.shapes.map(tag => tag.toJSON()),
};
return data;
}
empty() {
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.count = 0;
}
get(frame) {
const { tracks } = this;
const shapes = this.shapes[frame] || [];
const tags = this.tags[frame] || [];
const states = tracks.map(track => track.get(frame))
.concat(shapes.map(shape => shape.get(frame)))
.concat(tags.map(tag => tag.get(frame)));
// filtering here
const objectStates = [];
for (const state of states) {
const objectState = new ObjectState(state);
objectStates.push(objectState);
}
return objectStates;
}
}
const jobCache = {};
const taskCache = {};
async function getJobAnnotations(job, frame, filter) {
if (!(job.id in jobCache)) {
const rawAnnotations = await serverProxy.annotations.getJobAnnotations(job.id);
jobCache[job.id] = new Collection(job.task.labels);
jobCache[job.id].import(rawAnnotations);
}
return jobCache[job.id].get(frame, filter);
}
async function getTaskAnnotations(task, frame, filter) {
if (!(task.id in jobCache)) {
const rawAnnotations = await serverProxy.annotations.getTaskAnnotations(task.id);
taskCache[task.id] = new Collection(task.labels);
taskCache[task.id].import(rawAnnotations);
}
return taskCache[task.id].get(frame, filter);
}
module.exports = {
getJobAnnotations,
getTaskAnnotations,
};
})();

@ -14,11 +14,6 @@
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const {
Task,
Job,
} = require('./session');
function isBoolean(value) {
return typeof (value) === 'boolean';
}
@ -60,28 +55,6 @@
}
}
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;
@ -194,75 +167,6 @@
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: this.remoteFiles,
};
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;
}

@ -34,134 +34,14 @@
const {
Exception,
ArgumentError,
DataError,
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();
const clientID = +Date.now().toString().substr(-6);
/**
* API entrypoint
@ -463,10 +343,22 @@
* @property {integer} preloadFrames the number of subsequent frames which are
* loaded in background
* @memberof module:API.cvat.config
* @property {integer} taskID this value is displayed in a logs if available
* @memberof module:API.cvat.config
* @property {integer} jobID this value is displayed in a logs if available
* @memberof module:API.cvat.config
* @property {integer} clientID read only auto-generated
* value which is displayed in a logs
* @memberof module:API.cvat.config
*/
preloadFrames: 300,
backendAPI: 'http://localhost:7000/api/v1',
proxy: false,
taskID: undefined,
jobID: undefined,
clientID: {
get: () => clientID,
},
},
/**
* Namespace contains some library information e.g. api version
@ -509,6 +401,7 @@
exceptions: {
Exception,
ArgumentError,
DataError,
ScriptingError,
PluginError,
ServerError,
@ -539,104 +432,6 @@
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
@ -644,7 +439,4 @@
}
window.cvat = Object.freeze(implementAPI(cvat));
const hidden = require('./hidden');
hidden.location = cvat.config.backendAPI.slice(0, -7); // TODO: Use JS server instead
})();

@ -11,8 +11,6 @@
const Platform = require('platform');
const ErrorStackParser = require('error-stack-parser');
const hidden = require('./hidden');
/**
* Base exception class
* @memberof module:API.cvat.exceptions
@ -30,14 +28,14 @@
const system = Platform.os.toString();
const client = `${Platform.name} ${Platform.version}`;
const info = ErrorStackParser.parse(this)[0];
const filename = `${hidden.location}${info.fileName}`;
const filename = `${info.fileName}`;
const line = info.lineNumber;
const column = info.columnNumber;
const {
jobID,
taskID,
clientID,
} = hidden;
} = window.cvat.config;
const projID = undefined; // wasn't implemented
@ -192,6 +190,20 @@
}
}
/**
* Unexpected problems with data which are not connected with a user input
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
class DataError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
/**
* Unexpected situations in code
* @memberof module:API.cvat.exceptions
@ -251,6 +263,7 @@
module.exports = {
Exception,
ArgumentError,
DataError,
ScriptingError,
PluginError,
ServerError,

@ -5,10 +5,16 @@
/* global
require:false
global:false
*/
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
// This is the frames storage
const frameDataCache = {};
const frameCache = {};
/**
* Class provides meta information about specific frame and frame itself
@ -53,7 +59,7 @@
/**
* Method returns URL encoded image which can be placed in the img tag
* @method image
* @method frame
* @returns {string}
* @memberof module:API.cvat.classes.FrameData
* @instance
@ -61,12 +67,62 @@
* @throws {module:API.cvat.exception.ServerError}
* @throws {module:API.cvat.exception.PluginError}
*/
async image() {
async frame() {
const result = await PluginRegistry
.apiWrapper.call(this, FrameData.prototype.image);
.apiWrapper.call(this, FrameData.prototype.frame);
return result;
}
}
module.exports = FrameData;
FrameData.prototype.frame.implementation = async function () {
if (!(this.number in frameCache[this.tid])) {
const frame = await serverProxy.frames.getFrame(this.tid, this.number);
if (window.URL.createObjectURL) { // browser env
const url = window.URL.createObjectURL(new Blob([frame]));
frameCache[this.tid][this.number] = url;
} else {
frameCache[this.tid][this.number] = global.Buffer.from(frame, 'binary').toString('base64');
}
}
return frameCache[this.tid][this.number];
};
async function getFrame(taskID, mode, frame) {
if (!(taskID in frameDataCache)) {
frameDataCache[taskID] = {};
frameDataCache[taskID].meta = await serverProxy.frames.getMeta(taskID);
frameCache[taskID] = {};
}
if (!(frame in frameDataCache[taskID])) {
let size = null;
if (mode === 'interpolation') {
[size] = frameDataCache[taskID].meta;
} else if (mode === 'annotation') {
if (frame >= frameDataCache[taskID].meta.length) {
throw new window.cvat.exceptions.ArgumentError(
`Meta information about frame ${frame} can't be received from the server`,
);
} else {
size = frameDataCache[taskID].meta[frame];
}
} else {
throw new window.cvat.exceptions.ArgumentError(
`Invalid mode is specified ${mode}`,
);
}
frameDataCache[taskID][frame] = new FrameData(size.width, size.height, taskID, frame);
}
return frameDataCache[taskID][frame];
}
module.exports = {
FrameData,
getFrame,
};
})();

@ -1,17 +0,0 @@
/*
* 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;
})();

@ -16,30 +16,29 @@
*/
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
* @param {Object} type - an object which contains initialization information
* about points, group, zOrder, outside, occluded,
* attributes, lock, type, label, mode, etc.
* Types of data equal to listed below
*/
constructor(type, shape, label) {
constructor(serialized) {
const data = {
position: null,
points: null,
group: null,
zOrder: null,
outside: false,
occluded: false,
attributes: null,
lock: false,
type,
shape,
label,
outside: null,
occluded: null,
lock: null,
attributes: {},
type: serialized.type,
shape: serialized.shape,
};
Object.defineProperties(this, Object.freeze({
type: {
/**
* @name type
* @type {module:API.cvat.enums.ShapeType}
* @type {module:API.cvat.enums.ObjectType}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
@ -49,7 +48,7 @@
shape: {
/**
* @name shape
* @type {module:API.cvat.enums.ShapeType}
* @type {module:API.cvat.enums.ObjectShape}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
@ -67,7 +66,7 @@
get: () => data.label,
set: (labelInstance) => {
if (!(labelInstance instanceof window.cvat.classes.Label)) {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Expected Label instance, but got "${typeof (labelInstance.constructor.name)}"`,
);
}
@ -75,7 +74,7 @@
data.label = labelInstance;
},
},
position: {
points: {
/**
* @typedef {Object} Point
* @property {number} x
@ -83,7 +82,7 @@
* @global
*/
/**
* @name position
* @name points
* @type {Point[]}
* @memberof module:API.cvat.classes.ObjectState
* @instance
@ -95,13 +94,13 @@
for (const point of position) {
if (typeof (point) !== 'object'
|| !('x' in point) || !('y' in point)) {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Got invalid point ${point}`,
);
}
}
} else {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Got invalid type "${typeof (position.constructor.name)}"`,
);
}
@ -120,7 +119,7 @@
get: () => data.group,
set: (groupID) => {
if (!Number.isInteger(groupID)) {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Expected integer, but got ${groupID.constructor.name}`,
);
}
@ -139,7 +138,7 @@
get: () => data.zOrder,
set: (zOrder) => {
if (!Number.isInteger(zOrder)) {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Expected integer, but got ${zOrder.constructor.name}`,
);
}
@ -157,9 +156,9 @@
*/
get: () => data.outside,
set: (outside) => {
if (!(typeof (outside) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${outside.constructor.name}`,
if (typeof (outside) !== 'boolean') {
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${outside.constructor.name}`,
);
}
@ -176,9 +175,9 @@
*/
get: () => data.occluded,
set: (occluded) => {
if (!(typeof (occluded) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${occluded.constructor.name}`,
if (typeof (occluded) !== 'boolean') {
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${occluded.constructor.name}`,
);
}
@ -195,9 +194,9 @@
*/
get: () => data.lock,
set: (lock) => {
if (!(typeof (lock) !== 'boolean')) {
throw new window.cvat.exceptions.ArgumentException(
`Expected integer, but got ${lock.constructor.name}`,
if (typeof (lock) !== 'boolean') {
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${lock.constructor.name}`,
);
}
@ -217,7 +216,7 @@
get: () => data.attributes,
set: (attributes) => {
if (typeof (attributes) !== 'object') {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Expected object, but got ${attributes.constructor.name}`,
);
}
@ -226,7 +225,7 @@
if (Object.prototype.hasOwnProperty.call(attributes, attrId)) {
attrId = +attrId;
if (!Number.isInteger(attrId)) {
throw new window.cvat.exceptions.ArgumentException(
throw new window.cvat.exceptions.ArgumentError(
`Expected integer attribute id, but got ${attrId.constructor.name}`,
);
}
@ -236,7 +235,25 @@
}
},
},
}));
this.label = serialized.label;
this.group = serialized.group;
this.zOrder = serialized.zOrder;
this.outside = serialized.outside;
this.occluded = serialized.occluded;
this.attributes = serialized.attributes;
this.lock = false;
const points = [];
for (let i = 0; i < serialized.points.length; i += 2) {
points.push({
x: serialized.points[i],
y: serialized.points[i + 1],
});
}
this.points = points;
}
/**

@ -167,6 +167,14 @@
}
}
// TODO: Perhaps we should redesign the authorization method on the server.
if (authentificationResponse.data.includes('didn\'t match')) {
throw new window.cvat.exceptions.ServerError(
'The pair login/password is invalid',
403,
);
}
setCookie(authentificationResponse);
}
@ -370,7 +378,7 @@
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get users from a server',
'Could not get users from the server',
code,
);
}
@ -389,7 +397,7 @@
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
'Could not get users from a server',
'Could not get users from the server',
code,
);
}
@ -404,11 +412,12 @@
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/${frame}`, {
proxy: window.cvat.config.proxy,
responseType: 'blob',
});
} 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`,
`Could not get frame ${frame} for the task ${tid} from the server`,
code,
);
}
@ -427,7 +436,45 @@
} 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`,
`Could not get frame meta info for the task ${tid} from the server`,
code,
);
}
return response.data;
}
async function getTaskAnnotations(tid) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/annotations`, {
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 annotations for the task ${tid} from the server`,
code,
);
}
return response.data;
}
async function getJobAnnotations(jid) {
const { backendAPI } = window.cvat.config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/jobs/${jid}/annotations`, {
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 annotations for the job ${jid} from the server`,
code,
);
}
@ -487,6 +534,14 @@
}),
writable: false,
},
annotations: {
value: Object.freeze({
getTaskAnnotations,
getJobAnnotations,
}),
writable: false,
},
}));
}
}

@ -9,6 +9,141 @@
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const { getFrame } = require('./frames');
const {
getJobAnnotations,
getTaskAnnotations,
} = require('./annotations');
function buildDublicatedAPI() {
const annotations = Object.freeze({
value: {
async upload(file) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.upload, file);
return result;
},
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.save);
return result;
},
async clear() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.clear);
return result;
},
async dump() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.dump);
return result;
},
async statistics() {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.statistics);
return result;
},
async put(arrayOfObjects = []) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.put, arrayOfObjects);
return result;
},
async get(frame, filter = {}) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.get, frame, filter);
return result;
},
async search(filter, frameFrom, frameTo) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.search,
filter, frameFrom, frameTo);
return result;
},
async select(frame, x, y) {
const result = await PluginRegistry
.apiWrapper.call(this, annotations.value.select, frame, x, y);
return result;
},
},
});
const frames = Object.freeze({
value: {
async get(frame) {
const result = await PluginRegistry
.apiWrapper.call(this, frames.value.get, frame);
return result;
},
},
});
const logs = Object.freeze({
value: {
async put(logType, details) {
const result = await PluginRegistry
.apiWrapper.call(this, logs.value.put, logType, details);
return result;
},
async save() {
const result = await PluginRegistry
.apiWrapper.call(this, logs.value.save);
return result;
},
},
});
const actions = Object.freeze({
value: {
async undo(count) {
const result = await PluginRegistry
.apiWrapper.call(this, actions.value.undo, count);
return result;
},
async redo(count) {
const result = await PluginRegistry
.apiWrapper.call(this, actions.value.redo, count);
return result;
},
async clear() {
const result = await PluginRegistry
.apiWrapper.call(this, actions.value.clear);
return result;
},
},
});
const events = Object.freeze({
value: {
async subscribe(eventType, callback) {
const result = await PluginRegistry
.apiWrapper.call(this, events.value.subscribe, eventType, callback);
return result;
},
async unsubscribe(eventType, callback = null) {
const result = await PluginRegistry
.apiWrapper.call(this, events.value.unsubscribe, eventType, callback);
return result;
},
},
});
return Object.freeze({
annotations,
frames,
logs,
actions,
events,
});
}
/**
* Base abstract class for Task and Job. It contains common members.
@ -370,6 +505,9 @@
get: () => data.task,
},
}));
this.frames.get.implementation = this.frames.get.implementation.bind(this);
this.annotations.get.implementation = this.annotations.get.implementation.bind(this);
}
/**
@ -389,6 +527,54 @@
}
}
// Fill up the prototype by properties. Class syntax doesn't allow do it
// So, we do it seperately
Object.defineProperties(Job.prototype, buildDublicatedAPI());
Job.prototype.save.implementation = async function () {
// 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',
);
};
Job.prototype.frames.get.implementation = async function (frame) {
if (!Number.isInteger(frame) || frame < 0) {
throw new window.cvat.exceptions.ArgumentError(
`Frame must be a positive integer. Got: "${frame}"`,
);
}
if (frame < this.startFrame || frame > this.stopFrame) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the job`,
);
}
const frameData = await getFrame(this.task.id, this.task.mode, frame);
return frameData;
};
// TODO: Check filter for annotations
Job.prototype.annotations.get.implementation = async function (frame, filter) {
if (frame < this.startFrame || frame > this.stopFrame) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the job`,
);
}
const annotationsData = await getJobAnnotations(this, frame, filter);
return annotationsData;
};
/**
* Class representing a task
@ -790,6 +976,9 @@
},
},
}));
this.frames.get.implementation = this.frames.get.implementation.bind(this);
this.annotations.get.implementation = this.annotations.get.implementation.bind(this);
}
/**
@ -829,6 +1018,85 @@
}
}
// Fill up the prototype by properties. Class syntax doesn't allow do it
// So, we do it seperately
Object.defineProperties(Task.prototype, buildDublicatedAPI());
Task.prototype.save.implementation = 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 = async function () {
serverProxy.tasks.deleteTask(this.id);
};
Task.prototype.frames.get.implementation = async function (frame) {
if (frame >= this.size) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the task`,
);
}
const frameData = await getFrame(this.id, this.mode, frame);
return frameData;
};
// TODO: Check filter for annotations
Task.prototype.annotations.get.implementation = async function (frame, filter) {
if (!Number.isInteger(frame) || frame < 0) {
throw new window.cvat.exceptions.ArgumentError(
`Frame must be a positive integer. Got: "${frame}"`,
);
}
if (frame >= this.size) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the task`,
);
}
const annotationsData = await getTaskAnnotations(this, frame, filter);
return annotationsData;
};
module.exports = {
Job,
Task,

@ -175,7 +175,7 @@ describe('Feature: delete a task', () => {
id: 3,
});
result[0].delete();
await result[0].delete();
result = await window.cvat.tasks.get({
id: 3,
});

Loading…
Cancel
Save