Updated cvat-core (#591)

* Two webpack configuration instead one

* Two webpack configuration instead one

* Setup webpack build

* Removed references window.cvat

* Removed window.cvat

* Stop frame, start frame, frame filter

* Updated save method

* Updated dashboard to create tasks with custom stopFrame/startFramre

* Fixed clientID

* Removed browser environment from nodejs

* Updated webpack.config.js

* Updated filename
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 5dd4f3981e
commit cad5ac8653

@ -13,7 +13,7 @@ npm install
- Building the module from sources in the ```dist``` directory:
```bash
npm run-script build
npm run build
npm run build -- --mode=development # without a minification
```

@ -27,11 +27,10 @@
},
"dependencies": {
"axios": "^0.18.0",
"browser-env": "^3.2.6",
"error-stack-parser": "^2.0.2",
"form-data": "^2.5.0",
"jest-config": "^24.8.0",
"js-cookie": "^2.2.0",
"platform": "^1.3.5",
"stacktrace-gps": "^3.0.2"
"platform": "^1.3.5"
}
}

@ -24,6 +24,18 @@
} = require('./annotations-objects');
const { checkObjectType } = require('./common');
const Statistics = require('./statistics');
const { Label } = require('./labels');
const {
DataError,
ArgumentError,
ScriptingError,
} = require('./exceptions');
const {
ObjectShape,
ObjectType,
} = require('./enums');
const ObjectState = require('./object-state');
const colors = [
'#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA',
@ -62,7 +74,7 @@
shapeModel = new PointsShape(shapeData, clientID, color, injection);
break;
default:
throw new window.cvat.exceptions.DataError(
throw new DataError(
`An unexpected type of shape "${type}"`,
);
}
@ -91,7 +103,7 @@
trackModel = new PointsTrack(trackData, clientID, color, injection);
break;
default:
throw new window.cvat.exceptions.DataError(
throw new DataError(
`An unexpected type of track "${type}"`,
);
}
@ -209,10 +221,10 @@
checkObjectType('shapes for merge', objectStates, null, Array);
if (!objectStates.length) return;
const objectsForMerge = objectStates.map((state) => {
checkObjectType('object state', state, null, window.cvat.classes.ObjectState);
checkObjectType('object state', state, null, ObjectState);
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it',
);
}
@ -222,13 +234,13 @@
const keyframes = {}; // frame: position
const { label, shapeType } = objectStates[0];
if (!(label.id in this.labels)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Unknown label for the task: ${label.id}`,
);
}
if (!Object.values(window.cvat.enums.ObjectShape).includes(shapeType)) {
throw new window.cvat.exceptions.ArgumentError(
if (!Object.values(ObjectShape).includes(shapeType)) {
throw new ArgumentError(
`Got unknown shapeType "${shapeType}"`,
);
}
@ -243,13 +255,13 @@
const object = objectsForMerge[i];
const state = objectStates[i];
if (state.label.id !== label.id) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`All shape labels are expected to be ${label.name}, but got ${state.label.name}`,
);
}
if (state.shapeType !== shapeType) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`All shapes are expected to be ${shapeType}, but got ${state.shapeType}`,
);
}
@ -258,7 +270,7 @@
if (object instanceof Shape) {
// Frame already saved and it is not outside
if (object.frame in keyframes && !keyframes[object.frame].outside) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Expected only one visible shape per frame',
);
}
@ -303,7 +315,7 @@
continue;
}
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Expected only one visible shape per frame',
);
}
@ -338,7 +350,7 @@
};
}
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Trying to merge unknown object type: ${object.constructor.name}. `
+ 'Only shapes and tracks are expected.',
);
@ -389,17 +401,17 @@
}
split(objectState, frame) {
checkObjectType('object state', objectState, null, window.cvat.classes.ObjectState);
checkObjectType('object state', objectState, null, ObjectState);
checkObjectType('frame', frame, 'integer', null);
const object = this.objects[objectState.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'The object has not been saved yet. Call annotations.put([state]) before',
);
}
if (objectState.objectType !== window.cvat.enums.ObjectType.TRACK) {
if (objectState.objectType !== ObjectType.TRACK) {
return;
}
@ -478,10 +490,10 @@
checkObjectType('shapes for group', objectStates, null, Array);
const objectsForGroup = objectStates.map((state) => {
checkObjectType('object state', state, null, window.cvat.classes.ObjectState);
checkObjectType('object state', state, null, ObjectState);
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'The object has not been saved yet. Call annotations.put([state]) before',
);
}
@ -549,7 +561,7 @@
} else if (object instanceof Tag) {
objectType = 'tag';
} else {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
`Unexpected object type: "${objectType}"`,
);
}
@ -637,11 +649,11 @@
}
for (const state of objectStates) {
checkObjectType('object state', state, null, window.cvat.classes.ObjectState);
checkObjectType('object state', state, null, ObjectState);
checkObjectType('state client ID', state.clientID, 'undefined', null);
checkObjectType('state frame', state.frame, 'integer', null);
checkObjectType('state attributes', state.attributes, null, Object);
checkObjectType('state label', state.label, null, window.cvat.classes.Label);
checkObjectType('state label', state.label, null, Label);
const attributes = Object.keys(state.attributes)
.reduce(convertAttributes.bind(state), []);
@ -666,10 +678,10 @@
checkObjectType('point coordinate', coord, 'number', null);
}
if (!Object.values(window.cvat.enums.ObjectShape).includes(state.shapeType)) {
throw new window.cvat.exceptions.ArgumentError(
if (!Object.values(ObjectShape).includes(state.shapeType)) {
throw new ArgumentError(
'Object shape must be one of: '
+ `${JSON.stringify(Object.values(window.cvat.enums.ObjectShape))}`,
+ `${JSON.stringify(Object.values(ObjectShape))}`,
);
}
@ -703,9 +715,9 @@
}],
});
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Object type must be one of: '
+ `${JSON.stringify(Object.values(window.cvat.enums.ObjectType))}`,
+ `${JSON.stringify(Object.values(ObjectType))}`,
);
}
}
@ -723,12 +735,12 @@
let minimumDistance = null;
let minimumState = null;
for (const state of objectStates) {
checkObjectType('object state', state, null, window.cvat.classes.ObjectState);
checkObjectType('object state', state, null, ObjectState);
if (state.outside) continue;
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'The object has not been saved yet. Call annotations.put([state]) before',
);
}

@ -10,6 +10,19 @@
(() => {
const ObjectState = require('./object-state');
const { checkObjectType } = require('./common');
const {
ObjectShape,
ObjectType,
AttributeType,
} = require('./enums');
const {
DataError,
ArgumentError,
ScriptingError,
} = require('./exceptions');
const { Label } = require('./labels');
// Called with the Annotation context
function objectStateFactory(frame, data) {
@ -26,32 +39,32 @@
}
function checkNumberOfPoints(shapeType, points) {
if (shapeType === window.cvat.enums.ObjectShape.RECTANGLE) {
if (shapeType === ObjectShape.RECTANGLE) {
if (points.length / 2 !== 2) {
throw new window.cvat.exceptions.DataError(
throw new DataError(
`Rectangle must have 2 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POLYGON) {
} else if (shapeType === ObjectShape.POLYGON) {
if (points.length / 2 < 3) {
throw new window.cvat.exceptions.DataError(
throw new DataError(
`Polygon must have at least 3 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) {
} else if (shapeType === ObjectShape.POLYLINE) {
if (points.length / 2 < 2) {
throw new window.cvat.exceptions.DataError(
throw new DataError(
`Polyline must have at least 2 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POINTS) {
} else if (shapeType === ObjectShape.POINTS) {
if (points.length / 2 < 1) {
throw new window.cvat.exceptions.DataError(
throw new DataError(
`Points must have at least 1 points, but got ${points.length / 2}`,
);
}
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Unknown value of shapeType has been recieved ${shapeType}`,
);
}
@ -61,7 +74,7 @@
const MIN_SHAPE_LENGTH = 3;
const MIN_SHAPE_AREA = 9;
if (shapeType === window.cvat.enums.ObjectShape.POINTS) {
if (shapeType === ObjectShape.POINTS) {
return true;
}
@ -77,7 +90,7 @@
ymax = Math.max(ymax, points[i + 1]);
}
if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) {
if (shapeType === ObjectShape.POLYLINE) {
const length = Math.max(
xmax - xmin,
ymax - ymin,
@ -95,18 +108,18 @@
const type = attr.inputType;
if (typeof (value) !== 'string') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Attribute value is expected to be string, but got ${typeof (value)}`,
);
}
if (type === window.cvat.enums.AttributeType.NUMBER) {
if (type === AttributeType.NUMBER) {
return +value >= +values[0]
&& +value <= +values[1]
&& !((+value - +values[0]) % +values[2]);
}
if (type === window.cvat.enums.AttributeType.CHECKBOX) {
if (type === AttributeType.CHECKBOX) {
return ['true', 'false'].includes(value.toLowerCase());
}
@ -171,19 +184,19 @@
}
save() {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Is not implemented',
);
}
get() {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Is not implemented',
);
}
toJSON() {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Is not implemented',
);
}
@ -241,13 +254,13 @@
// Method is used to construct ObjectState objects
get(frame) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
return {
objectType: window.cvat.enums.ObjectType.SHAPE,
objectType: ObjectType.SHAPE,
shapeType: this.shapeType,
clientID: this.clientID,
serverID: this.serverID,
@ -264,7 +277,7 @@
save(frame, data) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
@ -278,7 +291,7 @@
const updated = data.updateFlags;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
checkObjectType('label', data.label, null, Label);
copy.label = data.label;
copy.attributes = {};
this.appendDefaultAttributes.call(copy, copy.label);
@ -297,7 +310,7 @@
&& validateAttributeValue(value, labelAttributes[attrID])) {
copy.attributes[attrID] = value;
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Trying to save unknown attribute with id ${attrID} and value ${value}`,
);
}
@ -352,7 +365,7 @@
if (updated.color) {
checkObjectType('color', data.color, 'string', null);
if (/^#[0-9A-F]{6}$/i.test(data.color)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Got invalid color value: "${data.color}"`,
);
}
@ -456,7 +469,7 @@
{
attributes: this.getAttributes(frame),
group: this.group,
objectType: window.cvat.enums.ObjectType.TRACK,
objectType: ObjectType.TRACK,
shapeType: this.shapeType,
clientID: this.clientID,
serverID: this.serverID,
@ -537,7 +550,7 @@
let positionUpdated = false;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
checkObjectType('label', data.label, null, Label);
copy.label = data.label;
copy.attributes = {};
@ -558,7 +571,7 @@
&& validateAttributeValue(value, labelAttributes[attrID])) {
copy.attributes[attrID] = value;
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Trying to save unknown attribute with id ${attrID} and value ${value}`,
);
}
@ -622,7 +635,7 @@
if (updated.color) {
checkObjectType('color', data.color, 'string', null);
if (/^#[0-9A-F]{6}$/i.test(data.color)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Got invalid color value: "${data.color}"`,
);
}
@ -762,7 +775,7 @@
};
}
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
`No one neightbour frame found for the track with client ID: "${this.id}"`,
);
}
@ -808,13 +821,13 @@
// Method is used to construct ObjectState objects
get(frame) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
return {
objectType: window.cvat.enums.ObjectType.TAG,
objectType: ObjectType.TAG,
clientID: this.clientID,
serverID: this.serverID,
lock: this.lock,
@ -826,7 +839,7 @@
save(frame, data) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
@ -840,7 +853,7 @@
const updated = data.updateFlags;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
checkObjectType('label', data.label, null, Label);
copy.label = data.label;
copy.attributes = {};
this.appendDefaultAttributes.call(copy, copy.label);
@ -882,7 +895,7 @@
class RectangleShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE;
this.shapeType = ObjectShape.RECTANGLE;
checkNumberOfPoints(this.shapeType, this.points);
}
@ -908,7 +921,7 @@
class PolygonShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYGON;
this.shapeType = ObjectShape.POLYGON;
checkNumberOfPoints(this.shapeType, this.points);
}
@ -983,7 +996,7 @@
class PolylineShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYLINE;
this.shapeType = ObjectShape.POLYLINE;
checkNumberOfPoints(this.shapeType, this.points);
}
@ -1027,7 +1040,7 @@
class PointsShape extends PolyShape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POINTS;
this.shapeType = ObjectShape.POINTS;
checkNumberOfPoints(this.shapeType, this.points);
}
@ -1049,7 +1062,7 @@
class RectangleTrack extends Track {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE;
this.shapeType = ObjectShape.RECTANGLE;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
@ -1374,7 +1387,7 @@
if (!targetMatched.length) {
// Prevent infinity loop
throw new window.cvat.exceptions.ScriptingError('Interpolation mapping is empty');
throw new ScriptingError('Interpolation mapping is empty');
}
while (!targetMatched.includes(prev)) {
@ -1463,7 +1476,7 @@
class PolygonTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYGON;
this.shapeType = ObjectShape.POLYGON;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
@ -1473,7 +1486,7 @@
class PolylineTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYLINE;
this.shapeType = ObjectShape.POLYLINE;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
@ -1483,7 +1496,7 @@
class PointsTrack extends PolyTrack {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POINTS;
this.shapeType = ObjectShape.POINTS;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}

@ -9,10 +9,12 @@
(() => {
const serverProxy = require('./server-proxy');
const { Task } = require('./session');
const { ScriptingError } = ('./exceptions');
class AnnotationsSaver {
constructor(version, collection, session) {
this.sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
this.sessionType = session instanceof Task ? 'task' : 'job';
this.id = session.id;
this.version = version;
this.collection = collection;
@ -102,7 +104,7 @@
} else if (typeof (object.id) === 'undefined') {
splitted.created[type].push(object);
} else {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
`Id of object is defined "${object.id}"`
+ 'but it absents in initial state',
);
@ -140,7 +142,7 @@
+ indexes.shapes.length + indexes.tags.length;
if (indexesLength !== savedLength) {
throw new window.cvat.exception.ScriptingError(
throw new ScriptingError(
'Number of indexes is differed by number of saved objects'
+ `${indexesLength} vs ${savedLength}`,
);

@ -12,6 +12,11 @@
const Collection = require('./annotations-collection');
const AnnotationsSaver = require('./annotations-saver');
const { checkObjectType } = require('./common');
const { Task } = require('./session');
const {
ScriptingError,
DataError,
} = require('./exceptions');
const jobCache = new WeakMap();
const taskCache = new WeakMap();
@ -25,13 +30,13 @@
return jobCache;
}
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
`Unknown session type was received ${sessionType}`,
);
}
async function getAnnotationsFromServer(session) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (!cache.has(session)) {
@ -65,13 +70,13 @@
async function getAnnotations(session, frame, filter) {
await getAnnotationsFromServer(session);
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
return cache.get(session).collection.get(frame, filter);
}
async function saveAnnotations(session, onUpdate) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
@ -82,46 +87,46 @@
}
function mergeAnnotations(session, objectStates) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.merge(objectStates);
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
function splitAnnotations(session, objectState, frame) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.split(objectState, frame);
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
function groupAnnotations(session, objectStates, reset) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.group(objectStates, reset);
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
function hasUnsavedChanges(session) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
@ -133,7 +138,7 @@
async function clearAnnotations(session, reload) {
checkObjectType('reload', reload, 'boolean', null);
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
@ -147,51 +152,51 @@
}
function annotationsStatistics(session) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.statistics();
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
function putAnnotations(session, objectStates) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.put(objectStates);
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
function selectObject(session, objectStates, x, y) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
return cache.get(session).collection.select(objectStates, x, y);
}
throw new window.cvat.exceptions.DataError(
throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
async function uploadAnnotations(session, file, format) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
await serverProxy.annotations.uploadAnnotations(sessionType, session.id, file, format);
}
async function dumpAnnotations(session, name, format) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const sessionType = session instanceof Task ? 'task' : 'job';
const result = await serverProxy.annotations
.dumpAnnotations(sessionType, session.id, name, format);
return result;

@ -21,9 +21,18 @@
checkFilter,
} = require('./common');
const {
TaskStatus,
TaskMode,
} = require('./enums');
const User = require('./user');
const { ArgumentError } = require('./exceptions');
const { Task } = require('./session');
function implementAPI(cvat) {
cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register;
cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat);
cvat.server.about.implementation = async () => {
const result = await serverProxy.server.about();
@ -56,7 +65,7 @@
users = await serverProxy.users.getUsers();
}
users = users.map(user => new window.cvat.classes.User(user));
users = users.map(user => new User(user));
return users;
};
@ -67,13 +76,13 @@
});
if (('taskID' in filter) && ('jobID' in filter)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Only one of fields "taskID" and "jobID" allowed simultaneously',
);
}
if (!Object.keys(filter).length) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Job filter must not be empty',
);
}
@ -90,7 +99,7 @@
// If task was found by its id, then create task instance and get Job instance from it
if (tasks !== null && tasks.length) {
const task = new window.cvat.classes.Task(tasks[0]);
const task = new Task(tasks[0]);
return filter.jobID ? task.jobs.filter(job => job.id === filter.jobID) : task.jobs;
}
@ -105,13 +114,13 @@
owner: isString,
assignee: isString,
search: isString,
status: isEnum.bind(window.cvat.enums.TaskStatus),
mode: isEnum.bind(window.cvat.enums.TaskMode),
status: isEnum.bind(TaskStatus),
mode: isEnum.bind(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(
throw new ArgumentError(
'Do not use the filter field "search" with others',
);
}
@ -119,7 +128,7 @@
if ('id' in filter && Object.keys(filter).length > 1) {
if (!('page' in filter && Object.keys(filter).length === 2)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Do not use the filter field "id" with others',
);
}
@ -133,7 +142,7 @@
}
const tasksData = await serverProxy.tasks.getTasks(searchParams.toString());
const tasks = tasksData.map(task => new window.cvat.classes.Task(task));
const tasks = tasksData.map(task => new Task(task));
tasks.count = tasksData.count;
return tasks;

@ -12,7 +12,7 @@
* @module API
*/
(() => {
function build() {
const PluginRegistry = require('./plugins');
const User = require('./user');
const ObjectState = require('./object-state');
@ -41,7 +41,7 @@
} = require('./exceptions');
const pjson = require('../package.json');
const clientID = +Date.now().toString().substr(-6);
const config = require('./config');
/**
* API entrypoint
@ -274,7 +274,7 @@
* 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()
* // Job.annotations.put()
* // It contains an array of objects to put
* // In this sample we round objects coordinates and save them
* enter(self, objects) {
@ -301,7 +301,7 @@
* internal: {
* async getPlugins() {
* // Collect information about installed plugins
* const plugins = await window.cvat.plugins.list();
* const plugins = await cvat.plugins.list();
* return plugins.map((el) => {
* return {
* name: el.name,
@ -362,12 +362,32 @@
* value which is displayed in a logs
* @memberof module:API.cvat.config
*/
backendAPI: 'http://localhost:7000/api/v1',
proxy: false,
taskID: undefined,
jobID: undefined,
clientID: {
get: () => clientID,
get backendAPI() {
return config.backendAPI;
},
set backendAPI(value) {
config.backendAPI = value;
},
get proxy() {
return config.proxy;
},
set proxy(value) {
config.proxy = value;
},
get taskID() {
return config.taskID;
},
set taskID(value) {
config.taskID = value;
},
get jobID() {
return config.jobID;
},
set jobID(value) {
config.jobID = value;
},
get clientID() {
return config.clientID;
},
},
/**
@ -439,18 +459,15 @@
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);
const implementAPI = require('./api-implementation');
if (typeof (window) === 'undefined') {
// Dummy browser environment
require('browser-env')();
}
Math.clamp = function (value, min, max) {
return Math.min(Math.max(value, min), max);
};
window.cvat = Object.freeze(implementAPI(cvat));
})();
const implemented = Object.freeze(implementAPI(cvat));
return implemented;
}
module.exports = build();

@ -3,7 +3,13 @@
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const { ArgumentError } = require('./exceptions');
function isBoolean(value) {
return typeof (value) === 'boolean';
}
@ -33,11 +39,11 @@
for (const prop in filter) {
if (Object.prototype.hasOwnProperty.call(filter, prop)) {
if (!(prop in fields)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Unsupported filter property has been recieved: "${prop}"`,
);
} else if (!fields[prop](filter[prop])) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Received filter property "${prop}" is not satisfied for checker`,
);
}
@ -53,20 +59,20 @@
return;
}
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`"${name}" is expected to be "${type}", but "${typeof (value)}" has been got.`,
);
}
} else if (instance) {
if (!(value instanceof instance)) {
if (value !== undefined) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but `
+ `"${value.constructor.name}" has been got`,
);
}
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`,
);
}

@ -0,0 +1,12 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
module.exports = {
backendAPI: 'http://localhost:7000/api/v1',
proxy: false,
taskID: undefined,
jobID: undefined,
clientID: +Date.now().toString().substr(-6),
};

@ -10,6 +10,7 @@
(() => {
const Platform = require('platform');
const ErrorStackParser = require('error-stack-parser');
const config = require('./config');
/**
* Base exception class
@ -35,7 +36,7 @@
jobID,
taskID,
clientID,
} = window.cvat.config;
} = config;
const projID = undefined; // wasn't implemented

@ -11,6 +11,7 @@
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const { ArgumentError } = require('./exceptions');
// This is the frames storage
const frameDataCache = {};
@ -78,11 +79,11 @@
if (!(this.number in frameCache[this.tid])) {
const frame = await serverProxy.frames.getData(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 {
if (typeof (module) !== 'undefined' && module.exports) {
frameCache[this.tid][this.number] = global.Buffer.from(frame, 'binary').toString('base64');
} else {
const url = URL.createObjectURL(new Blob([frame]));
frameCache[this.tid][this.number] = url;
}
}
@ -103,14 +104,14 @@
[size] = frameDataCache[taskID].meta;
} else if (mode === 'annotation') {
if (frame >= frameDataCache[taskID].meta.length) {
throw new window.cvat.exceptions.ArgumentError(
throw new 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(
throw new ArgumentError(
`Invalid mode is specified ${mode}`,
);
}

@ -3,7 +3,14 @@
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
(() => {
const { AttributeType } = require('./enums');
const { ArgumentError } = require('./exceptions');
/**
* Class representing an attribute
* @memberof module:API.cvat.classes
@ -32,8 +39,8 @@
}
}
if (!Object.values(window.cvat.enums.AttributeType).includes(data.input_type)) {
throw new window.cvat.exceptions.ArgumentError(
if (!Object.values(AttributeType).includes(data.input_type)) {
throw new ArgumentError(
`Got invalid attribute type ${data.input_type}`,
);
}
@ -144,7 +151,7 @@
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));
data.attributes.push(new Attribute(attrData));
}
}

@ -9,6 +9,7 @@
(() => {
const PluginRegistry = require('./plugins');
const { ArgumentError } = require('./exceptions');
/**
* Class representing a state of an object on a specific frame
@ -162,7 +163,7 @@
data.updateFlags.points = true;
data.points = [...points];
} else {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Points are expected to be an array '
+ `but got ${typeof (points) === 'object'
? points.constructor.name : typeof (points)}`,
@ -261,7 +262,7 @@
get: () => data.attributes,
set: (attributes) => {
if (typeof (attributes) !== 'object') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Attributes are expected to be an object '
+ `but got ${typeof (attributes) === 'object'
? attributes.constructor.name : typeof (attributes)}`,

@ -14,7 +14,7 @@
class PluginRegistry {
static async apiWrapper(wrappedFunc, ...args) {
// I have to optimize the wrapper
const pluginList = await window.cvat.plugins.list.implementation();
const pluginList = await PluginRegistry.list();
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions
.filter(obj => obj.callback === wrappedFunc)[0];
@ -52,6 +52,7 @@
return result;
}
// Called with cvat context
static async register(plug) {
const functions = [];
@ -92,7 +93,7 @@
functions.push(decorator);
}
}(plug, {
cvat: window.cvat,
cvat: this,
}));
Object.defineProperty(plug, 'functions', {

@ -9,6 +9,14 @@
*/
(() => {
const FormData = require('form-data');
const {
ServerError,
ScriptingError,
} = require('./exceptions');
const config = require('./config');
class ServerProxy {
constructor() {
const Cookie = require('js-cookie');
@ -25,16 +33,16 @@
}
async function about() {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/server/about`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get "about" information from the server',
code,
);
@ -44,17 +52,17 @@
}
async function share(directory) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
directory = encodeURIComponent(directory);
let response = null;
try {
response = await Axios.get(`${backendAPI}/server/share?directory=${directory}`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get "share" information from the server',
code,
);
@ -64,18 +72,18 @@
}
async function exception(exceptionObject) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
try {
await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not send an exception to the server',
code,
);
@ -111,7 +119,7 @@
if (csrftoken) {
setCSRFHeader(csrftoken);
} else {
throw new window.cvat.exceptions.ScriptingError(
throw new ScriptingError(
'An environment has been detected as a browser'
+ ', but CSRF token has not been found in cookies',
);
@ -120,15 +128,15 @@
}
}
const host = window.cvat.config.backendAPI.slice(0, -7);
const host = config.backendAPI.slice(0, -7);
let csrf = null;
try {
csrf = await Axios.get(`${host}/auth/csrf`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get CSRF token from a server',
code,
);
@ -148,7 +156,7 @@
authentificationData,
{
'Content-Type': 'application/x-www-form-urlencoded',
proxy: window.cvat.config.proxy,
proxy: config.proxy,
// do not redirect to a dashboard,
// otherwise we don't get a session id in a response
maxRedirects: 0,
@ -161,7 +169,7 @@
} else {
const code = errorData.response
? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not login on a server',
code,
);
@ -170,7 +178,7 @@
// TODO: Perhaps we should redesign the authorization method on the server.
if (authentificationResponse.data.includes('didn\'t match')) {
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'The pair login/password is invalid',
403,
);
@ -180,15 +188,15 @@
}
async function logout() {
const host = window.cvat.config.backendAPI.slice(0, -7);
const host = config.backendAPI.slice(0, -7);
try {
await Axios.get(`${host}/auth/logout`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not logout from the server',
code,
);
@ -196,16 +204,16 @@
}
async function getTasks(filter = '') {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks?${filter}`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get tasks from a server',
code,
);
@ -216,18 +224,18 @@
}
async function saveTask(id, taskData) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
try {
await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not save the task on the server',
code,
);
@ -235,13 +243,13 @@
}
async function deleteTask(id) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = 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(
throw new ServerError(
'Could not delete the task from the server',
code,
);
@ -249,7 +257,7 @@
}
async function createTask(taskData, files, onUpdate) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
async function wait(id) {
return new Promise((resolve, reject) => {
@ -266,14 +274,14 @@
} 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(
reject(new 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(
reject(new ServerError(
`Unknown task state has been recieved: ${response.data.state}`,
500,
));
@ -282,7 +290,7 @@
const code = errorData.response
? errorData.response.status : errorData.code;
reject(new window.cvat.exceptions.ServerError(
reject(new ServerError(
'Data uploading error occured',
code,
));
@ -293,7 +301,7 @@
});
}
const batchOfFiles = new window.FormData();
const batchOfFiles = new FormData();
for (const key in files) {
if (Object.prototype.hasOwnProperty.call(files, key)) {
for (let i = 0; i < files[key].length; i++) {
@ -307,14 +315,14 @@
onUpdate('The task is being created on the server..');
try {
response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskData), {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not put task to the server',
code,
);
@ -323,12 +331,12 @@
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,
proxy: config.proxy,
});
} catch (errorData) {
await deleteTask(response.data.id);
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not put data to the server',
code,
);
@ -347,16 +355,16 @@
}
async function getJob(jobID) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/jobs/${jobID}`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get jobs from a server',
code,
);
@ -366,18 +374,18 @@
}
async function saveJob(id, jobData) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
try {
await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not save the job on the server',
code,
);
@ -385,16 +393,16 @@
}
async function getUsers() {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/users`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get users from the server',
code,
);
@ -404,16 +412,16 @@
}
async function getSelf() {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/users/self`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
'Could not get users from the server',
code,
);
@ -423,17 +431,17 @@
}
async function getData(tid, frame) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/${frame}`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
responseType: 'blob',
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
`Could not get frame ${frame} for the task ${tid} from the server`,
code,
);
@ -443,16 +451,16 @@
}
async function getMeta(tid) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/meta`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
`Could not get frame meta info for the task ${tid} from the server`,
code,
);
@ -463,16 +471,16 @@
// Session is 'task' or 'job'
async function getAnnotations(session, id) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/${session}s/${id}/annotations`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
`Could not get annotations for the ${session} ${id} from the server`,
code,
);
@ -483,7 +491,7 @@
// Session is 'task' or 'job'
async function updateAnnotations(session, id, data, action) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let requestFunc = null;
let url = null;
if (action.toUpperCase() === 'PUT') {
@ -497,14 +505,14 @@
let response = null;
try {
response = await requestFunc(url, JSON.stringify(data), {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new window.cvat.exceptions.ServerError(
throw new ServerError(
`Could not updated annotations for the ${session} ${id} on the server`,
code,
);
@ -515,7 +523,7 @@
// Session is 'task' or 'job'
async function uploadAnnotations(session, id, file, format) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
let annotationData = new FormData();
annotationData.append('annotation_file', file);
@ -525,7 +533,7 @@
try {
const response = await Axios
.post(`${backendAPI}/${session}s/${id}/annotations?upload_format=${format}`, annotationData, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
if (response.status === 202) {
annotationData = new FormData();
@ -536,7 +544,7 @@
} catch (errorData) {
const code = errorData.response
? errorData.response.status : errorData.code;
const error = new window.cvat.exceptions.ServerError(
const error = new ServerError(
`Could not upload annotations for the ${session} ${id}`,
code,
);
@ -550,7 +558,7 @@
// Session is 'task' or 'job'
async function dumpAnnotations(id, name, format) {
const { backendAPI } = window.cvat.config;
const { backendAPI } = config;
const filename = name.replace(/\//g, '_');
let url = `${backendAPI}/tasks/${id}/annotations/${filename}?dump_format=${format}`;
@ -559,7 +567,7 @@
try {
const response = await Axios
.get(`${url}`, {
proxy: window.cvat.config.proxy,
proxy: config.proxy,
});
if (response.status === 202) {
setTimeout(request, 3000);
@ -570,7 +578,7 @@
} catch (errorData) {
const code = errorData.response
? errorData.response.status : errorData.code;
const error = new window.cvat.exceptions.ServerError(
const error = new ServerError(
`Could not dump annotations for the task ${id} from the server`,
code,
);

@ -11,20 +11,9 @@
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const { getFrame } = require('./frames');
const {
getAnnotations,
putAnnotations,
saveAnnotations,
hasUnsavedChanges,
mergeAnnotations,
splitAnnotations,
groupAnnotations,
clearAnnotations,
selectObject,
annotationsStatistics,
uploadAnnotations,
dumpAnnotations,
} = require('./annotations');
const { ArgumentError } = require('./exceptions');
const { TaskStatus } = require('./enums');
const { Label } = require('./labels');
function buildDublicatedAPI(prototype) {
Object.defineProperties(prototype, {
@ -509,7 +498,7 @@
}
if (data[property] === undefined) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Job field "${property}" was not initialized`,
);
}
@ -539,7 +528,7 @@
get: () => data.assignee,
set: () => (assignee) => {
if (!Number.isInteger(assignee) || assignee < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a non negative integer',
);
}
@ -556,7 +545,7 @@
status: {
get: () => data.status,
set: (status) => {
const type = window.cvat.enums.TaskStatus;
const type = TaskStatus;
let valueInEnum = false;
for (const value in type) {
if (type[value] === status) {
@ -566,7 +555,7 @@
}
if (!valueInEnum) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a value from the enumeration cvat.enums.TaskStatus',
);
}
@ -647,128 +636,24 @@
}
}
// Fill up the prototype by properties. Class syntax doesn't allow do it
// So, we do it seperately
buildDublicatedAPI(Job.prototype);
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 new 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(
`The frame with number ${frame} is out of 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 getAnnotations(this, frame, filter);
return annotationsData;
};
Job.prototype.annotations.save.implementation = async function (onUpdate) {
const result = await saveAnnotations(this, onUpdate);
return result;
};
Job.prototype.annotations.merge.implementation = async function (objectStates) {
const result = await mergeAnnotations(this, objectStates);
return result;
};
Job.prototype.annotations.split.implementation = async function (objectState, frame) {
const result = await splitAnnotations(this, objectState, frame);
return result;
};
Job.prototype.annotations.group.implementation = async function (objectStates, reset) {
const result = await groupAnnotations(this, objectStates, reset);
return result;
};
Job.prototype.annotations.hasUnsavedChanges.implementation = function () {
const result = hasUnsavedChanges(this);
return result;
};
Job.prototype.annotations.clear.implementation = async function (reload) {
const result = await clearAnnotations(this, reload);
return result;
};
Job.prototype.annotations.select.implementation = function (frame, x, y) {
const result = selectObject(this, frame, x, y);
return result;
};
Job.prototype.annotations.statistics.implementation = function () {
const result = annotationsStatistics(this);
return result;
};
Job.prototype.annotations.put.implementation = function (objectStates) {
const result = putAnnotations(this, objectStates);
return result;
};
Job.prototype.annotations.upload.implementation = async function (file, format) {
const result = await uploadAnnotations(this, file, format);
return result;
};
Job.prototype.annotations.dump.implementation = async function (name, format) {
const result = await dumpAnnotations(this, name, format);
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
*/
/**
* 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 = {
@ -786,6 +671,9 @@
segment_size: undefined,
z_order: undefined,
image_quality: undefined,
start_frame: undefined,
stop_frame: undefined,
frame_filter: undefined,
};
for (const property in data) {
@ -807,7 +695,7 @@
for (const segment of initialData.segments) {
if (Array.isArray(segment.jobs)) {
for (const job of segment.jobs) {
const jobInstance = new window.cvat.classes.Job({
const jobInstance = new Job({
url: job.url,
id: job.id,
assignee: job.assignee,
@ -824,7 +712,7 @@
if (Array.isArray(initialData.labels)) {
for (const label of initialData.labels) {
const classInstance = new window.cvat.classes.Label(label);
const classInstance = new Label(label);
data.labels.push(classInstance);
}
}
@ -851,7 +739,7 @@
get: () => data.name,
set: (value) => {
if (!value.trim().length) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must not be empty',
);
}
@ -911,7 +799,7 @@
get: () => data.assignee,
set: () => (assignee) => {
if (!Number.isInteger(assignee) || assignee < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a non negative integer',
);
}
@ -962,7 +850,7 @@
get: () => data.overlap,
set: (overlap) => {
if (!Number.isInteger(overlap) || overlap < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a non negative integer',
);
}
@ -980,7 +868,7 @@
get: () => data.segment_size,
set: (segment) => {
if (!Number.isInteger(segment) || segment < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a positive integer',
);
}
@ -998,7 +886,7 @@
get: () => data.z_order,
set: (zOrder) => {
if (typeof (zOrder) !== 'boolean') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a boolean',
);
}
@ -1016,7 +904,7 @@
get: () => data.image_quality,
set: (quality) => {
if (!Number.isInteger(quality) || quality < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
'Value must be a positive integer',
);
}
@ -1035,14 +923,14 @@
get: () => [...data.labels],
set: (labels) => {
if (!Array.isArray(labels)) {
throw new window.cvat.exceptions.ArgumentError(
throw new 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(
if (!(label instanceof Label)) {
throw new ArgumentError(
'Each array value must be an instance of Label. '
+ `${typeof (label)} was found`,
);
@ -1078,14 +966,14 @@
get: () => [...data.files.server_files],
set: (serverFiles) => {
if (!Array.isArray(serverFiles)) {
throw new window.cvat.exceptions.ArgumentError(
throw new 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(
throw new ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`,
);
}
@ -1106,14 +994,14 @@
get: () => [...data.files.client_files],
set: (clientFiles) => {
if (!Array.isArray(clientFiles)) {
throw new window.cvat.exceptions.ArgumentError(
throw new 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(
if (!(value instanceof File)) {
throw new ArgumentError(
`Array values must be a File. But ${value.constructor.name} has been got.`,
);
}
@ -1134,14 +1022,14 @@
get: () => [...data.files.remote_files],
set: (remoteFiles) => {
if (!Array.isArray(remoteFiles)) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Value must be an array. But ${typeof (remoteFiles)} has been got.`,
);
}
for (const value of remoteFiles) {
if (typeof (value) !== 'string') {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`,
);
}
@ -1150,6 +1038,64 @@
Array.prototype.push.apply(data.files.remote_files, remoteFiles);
},
},
/**
* The first frame of a video to annotation
* @name startFrame
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
startFrame: {
get: () => data.start_frame,
set: (frame) => {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(
'Value must be a not negative integer',
);
}
data.start_frame = frame;
},
},
/**
* The last frame of a video to annotation
* @name stopFrame
* @type {integer}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
stopFrame: {
get: () => data.stop_frame,
set: (frame) => {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(
'Value must be a not negative integer',
);
}
data.stop_frame = frame;
},
},
/**
* Filter to ignore some frames during task creation
* @name frameFilter
* @type {string}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
frameFilter: {
get: () => data.frame_filter,
set: (filter) => {
if (typeof (filter) !== 'string') {
throw new ArgumentError(
`Filter value must be a string. But ${typeof (filter)} has been got.`,
);
}
data.frame_filter = filter;
},
},
}));
// When we call a function, for example: task.annotations.get()
@ -1213,10 +1159,129 @@
}
}
// Fill up the prototype by properties. Class syntax doesn't allow do it
// So, we do it seperately
module.exports = {
Job,
Task,
};
const {
getAnnotations,
putAnnotations,
saveAnnotations,
hasUnsavedChanges,
mergeAnnotations,
splitAnnotations,
groupAnnotations,
clearAnnotations,
selectObject,
annotationsStatistics,
uploadAnnotations,
dumpAnnotations,
} = require('./annotations');
buildDublicatedAPI(Job.prototype);
buildDublicatedAPI(Task.prototype);
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 new ArgumentError(
'Can not save job without and id',
);
};
Job.prototype.frames.get.implementation = async function (frame) {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(
`Frame must be a positive integer. Got: "${frame}"`,
);
}
if (frame < this.startFrame || frame > this.stopFrame) {
throw new ArgumentError(
`The frame with number ${frame} is out of the job`,
);
}
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 ArgumentError(
`Frame ${frame} does not exist in the job`,
);
}
const annotationsData = await getAnnotations(this, frame, filter);
return annotationsData;
};
Job.prototype.annotations.save.implementation = async function (onUpdate) {
const result = await saveAnnotations(this, onUpdate);
return result;
};
Job.prototype.annotations.merge.implementation = async function (objectStates) {
const result = await mergeAnnotations(this, objectStates);
return result;
};
Job.prototype.annotations.split.implementation = async function (objectState, frame) {
const result = await splitAnnotations(this, objectState, frame);
return result;
};
Job.prototype.annotations.group.implementation = async function (objectStates, reset) {
const result = await groupAnnotations(this, objectStates, reset);
return result;
};
Job.prototype.annotations.hasUnsavedChanges.implementation = function () {
const result = hasUnsavedChanges(this);
return result;
};
Job.prototype.annotations.clear.implementation = async function (reload) {
const result = await clearAnnotations(this, reload);
return result;
};
Job.prototype.annotations.select.implementation = function (frame, x, y) {
const result = selectObject(this, frame, x, y);
return result;
};
Job.prototype.annotations.statistics.implementation = function () {
const result = annotationsStatistics(this);
return result;
};
Job.prototype.annotations.put.implementation = function (objectStates) {
const result = putAnnotations(this, objectStates);
return result;
};
Job.prototype.annotations.upload.implementation = async function (file, format) {
const result = await uploadAnnotations(this, file, format);
return result;
};
Job.prototype.annotations.dump.implementation = async function (name, format) {
const result = await dumpAnnotations(this, name, format);
return result;
};
Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) {
// TODO: Add ability to change an owner and an assignee
if (typeof (this.id) !== 'undefined') {
@ -1248,6 +1313,15 @@
if (this.overlap) {
taskData.overlap = this.overlap;
}
if (this.startFrame) {
taskData.start_frame = this.startFrame;
}
if (this.stopFrame) {
taskData.stop_frame = this.stopFrame;
}
if (this.frameFilter) {
taskData.frame_filter = this.frameFilter;
}
const taskFiles = {
client_files: this.clientFiles,
@ -1266,13 +1340,13 @@
Task.prototype.frames.get.implementation = async function (frame) {
if (!Number.isInteger(frame) || frame < 0) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Frame must be a positive integer. Got: "${frame}"`,
);
}
if (frame >= this.size) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`The frame with number ${frame} is out of the task`,
);
}
@ -1284,13 +1358,13 @@
// 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(
throw new ArgumentError(
`Frame must be a positive integer. Got: "${frame}"`,
);
}
if (frame >= this.size) {
throw new window.cvat.exceptions.ArgumentError(
throw new ArgumentError(
`Frame ${frame} does not exist in the task`,
);
}
@ -1353,9 +1427,4 @@
const result = await dumpAnnotations(this, name, format);
return result;
};
module.exports = {
Job,
Task,
};
})();

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
// Test cases
describe('Feature: get annotations', () => {

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
const { FrameData } = require('../../src/frames');

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
const { Job } = require('../../src/session');

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
describe('Feature: set attributes for an object state', () => {
test('set a valid value', () => {

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
describe('Feature: dummy feature', () => {
test('dummy test', async () => {

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
// Test cases
describe('Feature: get info about cvat', () => {

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
const { Task } = require('../../src/session');

@ -16,7 +16,7 @@ jest.mock('../../src/server-proxy', () => {
});
// Initialize api
require('../../src/api');
window.cvat = require('../../src/api');
const User = require('../../src/user');

@ -19,7 +19,6 @@ const {
frameMetaDummyData,
} = require('./dummy-data.mock');
class ServerProxy {
constructor() {
async function about() {

@ -4,29 +4,42 @@
*/
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/),
);
}
const nodeConfig = {
target: 'node',
mode: 'production',
devtool: 'source-map',
entry: './src/api.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'cvat-core.node.js',
library: 'cvat',
libraryTarget: 'commonjs',
},
module: {
rules: [{
test: /.js?$/,
exclude: /node_modules/,
}],
},
externals: {
canvas: 'commonjs canvas',
},
stats: {
warnings: false,
},
};
module.exports = {
mode: configuration,
const webConfig = {
target: 'web',
mode: 'production',
devtool: 'source-map',
entry: './src/api.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'cvat.js',
filename: 'cvat-core.js',
library: 'cvat',
libraryTarget: 'window',
},
module: {
rules: [{
@ -54,6 +67,6 @@ module.exports = {
},
}],
},
plugins,
target,
};
module.exports = [nodeConfig, webConfig];

@ -408,81 +408,6 @@ class DashboardView {
}
_setupCreateDialog() {
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(name) {
const math = name.match('[a-zA-Z0-9_]+');
return math !== null;
}
function validateLabels(labels) {
try {
LabelsInfo.deserialize(labels)
return true;
} catch (error) {
return false;
}
}
function validateBugTracker(bugTracker) {
return !bugTracker || !!bugTracker.match(/^http[s]?/);
}
function validateSegmentSize(segmentSize) {
return (segmentSize >= 100 && segmentSize <= 50000);
}
function validateOverlapSize(overlapSize, segmentSize) {
return (overlapSize >= 0 && overlapSize <= segmentSize - 1);
}
function validateStopFrame(stopFrame, startFrame) {
return !customStopFrame.prop('checked') || stopFrame >= startFrame;
}
function requestCreatingStatus(tid, onUpdateStatus, onSuccess, onError) {
function checkCallback() {
$.get(`/api/v1/tasks/${tid}/status`).done((data) => {
if (['Queued', 'Started'].includes(data.state)) {
if (data.message !== '') {
onUpdateStatus(data.message);
}
setTimeout(checkCallback, 1000);
} else {
if (data.state === 'Finished') {
onSuccess();
}
else if (data.state === 'Failed') {
const message = `Can not create task. ${data.message}`;
onError(message);
} else {
const message = `Unknown state has been received: ${data.state}`;
onError(message);
}
}
}).fail((errorData) => {
const message = `Can not check task status. Code: ${errorData.status}. ` +
`Message: ${errorData.responseText || errorData.statusText}`;
onError(message);
});
}
setTimeout(checkCallback, 1000);
}
const dashboardCreateTaskButton = $('#dashboardCreateTaskButton');
const createModal = $('#dashboardCreateModal');
const nameInput = $('#dashboardNameInput');
@ -570,6 +495,10 @@ class DashboardView {
return (overlapSize >= 0 && overlapSize <= segmentSize - 1);
}
function validateStopFrame(stop, start) {
return !customStopFrame.prop('checked') || stop >= start;
}
dashboardCreateTaskButton.on('click', () => {
$('#dashboardCreateModal').removeClass('hidden');
});
@ -589,7 +518,8 @@ class DashboardView {
localSourceRadio.on('click', () => {
if (source === 'local') {
return;
} else if (source === 'remote') {
}
if (source === 'remote') {
selectFiles.parent().removeClass('hidden');
remoteFileInput.parent().addClass('hidden');
}
@ -612,7 +542,8 @@ class DashboardView {
shareSourceRadio.on('click', () => {
if (source === 'share') {
return;
} else if (source === 'remote') {
}
if (source === 'remote') {
selectFiles.parent().removeClass('hidden');
remoteFileInput.parent().addClass('hidden');
}
@ -658,8 +589,8 @@ class DashboardView {
updateSelectedFiles();
});
remoteFileInput.on('change', function(e) {
let text = remoteFileInput.prop('value');
remoteFileInput.on('change', () => {
const text = remoteFileInput.prop('value');
files = text.split('\n').map(f => f.trim()).filter(f => f.length > 0);
});
@ -676,12 +607,12 @@ class DashboardView {
zOrder = e.target.checked;
});
customSegmentSize.on('change', (e) => segmentSizeInput.prop('disabled', !e.target.checked));
customOverlapSize.on('change', (e) => overlapSizeInput.prop('disabled', !e.target.checked));
customCompressQuality.on('change', (e) => imageQualityInput.prop('disabled', !e.target.checked));
customStartFrame.on('change', (e) => startFrameInput.prop('disabled', !e.target.checked));
customStopFrame.on('change', (e) => stopFrameInput.prop('disabled', !e.target.checked));
customFrameFilter.on('change', (e) => frameFilterInput.prop('disabled', !e.target.checked));
customSegmentSize.on('change', e => segmentSizeInput.prop('disabled', !e.target.checked));
customOverlapSize.on('change', e => overlapSizeInput.prop('disabled', !e.target.checked));
customCompressQuality.on('change', e => imageQualityInput.prop('disabled', !e.target.checked));
customStartFrame.on('change', e => startFrameInput.prop('disabled', !e.target.checked));
customStopFrame.on('change', e => stopFrameInput.prop('disabled', !e.target.checked));
customFrameFilter.on('change', e => frameFilterInput.prop('disabled', !e.target.checked));
segmentSizeInput.on('change', () => {
const value = Math.clamp(
@ -716,8 +647,8 @@ class DashboardView {
compressQuality = value;
});
startFrameInput.on('change', function() {
let value = Math.max(
startFrameInput.on('change', () => {
const value = Math.max(
+startFrameInput.prop('value'),
+startFrameInput.prop('min')
);
@ -725,8 +656,9 @@ class DashboardView {
startFrameInput.prop('value', value);
startFrame = value;
});
stopFrameInput.on('change', function() {
let value = Math.max(
stopFrameInput.on('change', () => {
const value = Math.max(
+stopFrameInput.prop('value'),
+stopFrameInput.prop('min')
);
@ -734,7 +666,8 @@ class DashboardView {
stopFrameInput.prop('value', value);
stopFrame = value;
});
frameFilterInput.on('change', function() {
frameFilterInput.on('change', () => {
frameFilter = frameFilterInput.prop('value');
});
@ -825,6 +758,15 @@ class DashboardView {
if (customFrameFilter.prop('checked')) {
description.frame_filter = frameFilter;
}
if (customStartFrame.prop('checked')) {
description.start_frame = startFrame;
}
if (customStopFrame.prop('checked')) {
description.stop_frame = stopFrame;
}
if (customFrameFilter.prop('checked')) {
description.frame_filter = frameFilter;
}
try {
let task = new window.cvat.classes.Task(description);

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save