CVAT.js API Tests (#578)

* Added annotations dummy data

* Added test file

* Weak maps instead of regular dict

* Added 14 API tests

* Fixed put tests

* Some written tests and some additional checks

* Merge tests

* Split & group tests

* Clear annotations tests

* Statistics tests

* Added frames meta

* Selection tests & bug fixes

* Tests for frame

* ObjectState tests, many fixed bugs

* Object state tests

* Renamed method FrameData.frame() => FrameData.data()
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent d939b9c34a
commit d85d777040

@ -104,8 +104,12 @@
}
class Collection {
constructor(labels) {
this.labels = labels.reduce((labelAccumulator, label) => {
constructor(data) {
this.startFrame = data.startFrame;
this.stopFrame = data.stopFrame;
this.frameMeta = data.frameMeta;
this.labels = data.labels.reduce((labelAccumulator, label) => {
labelAccumulator[label.id] = label;
return labelAccumulator;
}, {});
@ -124,6 +128,7 @@
labels: this.labels,
collectionZ: this.collectionZ,
groups: this.groups,
frameMeta: this.frameMeta,
};
}
@ -208,7 +213,7 @@
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
'The object has not been saved yet. Call ObjectState.save() before you can merge it',
'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it',
);
}
return object;
@ -216,6 +221,18 @@
const keyframes = {}; // frame: position
const { label, shapeType } = objectStates[0];
if (!(label.id in this.labels)) {
throw new window.cvat.exceptions.ArgumentError(
`Unknown label for the task: ${label.id}`,
);
}
if (!Object.values(window.cvat.enums.ObjectShape).includes(shapeType)) {
throw new window.cvat.exceptions.ArgumentError(
`Got unknown shapeType "${shapeType}"`,
);
}
const labelAttributes = label.attributes.reduce((accumulator, attribute) => {
accumulator[attribute.id] = attribute;
return accumulator;
@ -365,7 +382,9 @@
// Remove other shapes
for (const object of objectsForMerge) {
object.removed = true;
object.resetCache();
if (typeof (object.resetCache) === 'function') {
object.resetCache();
}
}
}
@ -376,7 +395,7 @@
const object = this.objects[objectState.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
'The object has not been saved yet. Call annotations.put(state) before',
'The object has not been saved yet. Call annotations.put([state]) before',
);
}
@ -385,7 +404,7 @@
}
const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b);
if (frame <= +keyframes[0]) {
if (frame <= +keyframes[0] || frame > keyframes[keyframes.length - 1]) {
return;
}
@ -463,7 +482,7 @@
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
'The object has not been saved yet. Call annotations.put(state) before',
'The object has not been saved yet. Call annotations.put([state]) before',
);
}
return object;
@ -472,8 +491,12 @@
const groupIdx = reset ? 0 : ++this.groups.max;
for (const object of objectsForGroup) {
object.group = groupIdx;
object.resetCache();
if (typeof (object.resetCache) === 'function') {
object.resetCache();
}
}
return groupIdx;
}
clear() {
@ -526,7 +549,7 @@
} else if (object instanceof Tag) {
objectType = 'tag';
} else {
throw window.cvat.exceptions.ScriptingError(
throw new window.cvat.exceptions.ScriptingError(
`Unexpected object type: "${objectType}"`,
);
}
@ -543,6 +566,7 @@
if (objectType === 'track') {
const keyframes = Object.keys(object.shapes)
.sort((a, b) => +a - +b).map(el => +el);
let prevKeyframe = keyframes[0];
let visible = false;
@ -560,6 +584,13 @@
labels[label].total++;
}
}
const lastKey = keyframes[keyframes.length - 1];
if (lastKey !== this.stopFrame && !object.shapes[lastKey].outside) {
const interpolated = this.stopFrame - lastKey;
labels[label].interpolated += interpolated;
labels[label].total += interpolated;
}
} else {
labels[label].manually++;
labels[label].total++;
@ -648,7 +679,7 @@
frame: state.frame,
group: 0,
label_id: state.label.id,
occluded: state.occluded,
occluded: state.occluded || false,
points: [...state.points],
type: state.shapeType,
z_order: 0,
@ -664,7 +695,7 @@
attributes: attributes
.filter(attr => labelAttributes[attr.spec_id].mutable),
frame: state.frame,
occluded: state.occluded,
occluded: state.occluded || false,
outside: false,
points: [...state.points],
type: state.shapeType,
@ -698,7 +729,7 @@
const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
'The object has not been saved yet. Call annotations.put(state) before',
'The object has not been saved yet. Call annotations.put([state]) before',
);
}

@ -25,6 +25,94 @@
return objectState;
}
function checkNumberOfPoints(shapeType, points) {
if (shapeType === window.cvat.enums.ObjectShape.RECTANGLE) {
if (points.length / 2 !== 2) {
throw new window.cvat.exceptions.DataError(
`Rectangle must have 2 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POLYGON) {
if (points.length / 2 < 3) {
throw new window.cvat.exceptions.DataError(
`Polygon must have at least 3 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) {
if (points.length / 2 < 2) {
throw new window.cvat.exceptions.DataError(
`Polyline must have at least 2 points, but got ${points.length / 2}`,
);
}
} else if (shapeType === window.cvat.enums.ObjectShape.POINTS) {
if (points.length / 2 < 1) {
throw new window.cvat.exceptions.DataError(
`Points must have at least 1 points, but got ${points.length / 2}`,
);
}
} else {
throw new window.cvat.exceptions.ArgumentError(
`Unknown value of shapeType has been recieved ${shapeType}`,
);
}
}
function checkShapeArea(shapeType, points) {
const MIN_SHAPE_LENGTH = 3;
const MIN_SHAPE_AREA = 9;
if (shapeType === window.cvat.enums.ObjectShape.POINTS) {
return true;
}
let xmin = Number.MAX_SAFE_INTEGER;
let xmax = Number.MIN_SAFE_INTEGER;
let ymin = Number.MAX_SAFE_INTEGER;
let ymax = Number.MIN_SAFE_INTEGER;
for (let i = 0; i < points.length - 1; i += 2) {
xmin = Math.min(xmin, points[i]);
xmax = Math.max(xmax, points[i]);
ymin = Math.min(ymin, points[i + 1]);
ymax = Math.max(ymax, points[i + 1]);
}
if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) {
const length = Math.max(
xmax - xmin,
ymax - ymin,
);
return length >= MIN_SHAPE_LENGTH;
}
const area = (xmax - xmin) * (ymax - ymin);
return area >= MIN_SHAPE_AREA;
}
function validateAttributeValue(value, attr) {
const { values } = attr;
const type = attr.inputType;
if (typeof (value) !== 'string') {
throw new window.cvat.exceptions.ArgumentError(
`Attribute value is expected to be string, but got ${typeof (value)}`,
);
}
if (type === window.cvat.enums.AttributeType.NUMBER) {
return +value >= +values[0]
&& +value <= +values[1]
&& !((+value - +values[0]) % +values[2]);
}
if (type === window.cvat.enums.AttributeType.CHECKBOX) {
return ['true', 'false'].includes(value.toLowerCase());
}
return values.includes(value);
}
class Annotation {
constructor(data, clientID, injection) {
this.taskLabels = injection.labels;
@ -66,10 +154,8 @@
constructor(data, clientID, color, injection) {
super(data, clientID, injection);
this.frameMeta = injection.frameMeta;
this.collectionZ = injection.collectionZ;
const z = this._getZ(this.frame);
z.max = Math.max(z.max, this.zOrder || 0);
z.min = Math.min(z.min, this.zOrder || 0);
this.color = color;
this.shapeType = null;
@ -85,19 +171,19 @@
}
save() {
throw window.cvat.exceptions.ScriptingError(
throw new window.cvat.exceptions.ScriptingError(
'Is not implemented',
);
}
get() {
throw window.cvat.exceptions.ScriptingError(
throw new window.cvat.exceptions.ScriptingError(
'Is not implemented',
);
}
toJSON() {
throw window.cvat.exceptions.ScriptingError(
throw new window.cvat.exceptions.ScriptingError(
'Is not implemented',
);
}
@ -123,6 +209,10 @@
this.points = data.points;
this.occluded = data.occluded;
this.zOrder = data.z_order;
const z = this._getZ(this.frame);
z.max = Math.max(z.max, this.zOrder || 0);
z.min = Math.min(z.min, this.zOrder || 0);
}
// Method is used to export data to the server
@ -195,22 +285,47 @@
}
if (updated.attributes) {
const labelAttributes = copy.label
.attributes.map(attr => `${attr.id}`);
const labelAttributes = copy.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator;
}, {});
for (const attrID of Object.keys(data.attributes)) {
if (labelAttributes.includes(attrID)) {
copy.attributes[attrID] = data.attributes[attrID];
const value = data.attributes[attrID];
if (attrID in labelAttributes
&& validateAttributeValue(value, labelAttributes[attrID])) {
copy.attributes[attrID] = value;
} else {
throw new window.cvat.exceptions.ArgumentError(
`Trying to save unknown attribute with id ${attrID} and value ${value}`,
);
}
}
}
if (updated.points) {
checkObjectType('points', data.points, null, Array);
copy.points = [];
for (const coordinate of data.points) {
checkObjectType('coordinate', coordinate, 'number', null);
copy.points.push(coordinate);
checkNumberOfPoints(this.shapeType, data.points);
// cut points
const { width, height } = this.frameMeta[frame];
const cutPoints = [];
for (let i = 0; i < data.points.length - 1; i += 2) {
const x = data.points[i];
const y = data.points[i + 1];
checkObjectType('coordinate', x, 'number', null);
checkObjectType('coordinate', y, 'number', null);
cutPoints.push(
Math.clamp(x, 0, width),
Math.clamp(y, 0, height),
);
}
if (checkShapeArea(this.shapeType, cutPoints)) {
copy.points = cutPoints;
}
}
@ -340,7 +455,6 @@
{}, this.getPosition(frame),
{
attributes: this.getAttributes(frame),
label: this.label,
group: this.group,
objectType: window.cvat.enums.ObjectType.TRACK,
shapeType: this.shapeType,
@ -354,7 +468,9 @@
this.cache[frame] = interpolation;
}
return JSON.parse(JSON.stringify(this.cache[frame]));
const result = JSON.parse(JSON.stringify(this.cache[frame]));
result.label = this.label;
return result;
}
neighborsFrames(targetFrame) {
@ -408,8 +524,7 @@
}
save(frame, data) {
if (this.lock || data.lock) {
this.lock = data.lock;
if (this.lock && data.lock) {
return objectStateFactory.call(this, frame, this.get(frame));
}
@ -430,34 +545,50 @@
this.appendDefaultAttributes.call(copy, copy.label);
}
if (updated.attributes) {
const labelAttributes = copy.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator;
}, {});
const labelAttributes = copy.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator;
}, {});
if (updated.attributes) {
for (const attrID of Object.keys(data.attributes)) {
if (attrID in labelAttributes) {
copy.attributes[attrID] = data.attributes[attrID];
if (!labelAttributes[attrID].mutable) {
this.attributes[attrID] = data.attributes[attrID];
} else {
// Mutable attributes will be updated later
positionUpdated = true;
}
const value = data.attributes[attrID];
if (attrID in labelAttributes
&& validateAttributeValue(value, labelAttributes[attrID])) {
copy.attributes[attrID] = value;
} else {
throw new window.cvat.exceptions.ArgumentError(
`Trying to save unknown attribute with id ${attrID} and value ${value}`,
);
}
}
}
if (updated.points) {
checkObjectType('points', data.points, null, Array);
copy.points = [];
for (const coordinate of data.points) {
checkObjectType('coordinate', coordinate, 'number', null);
copy.points.push(coordinate);
checkNumberOfPoints(this.shapeType, data.points);
// cut points
const { width, height } = this.frameMeta[frame];
const cutPoints = [];
for (let i = 0; i < data.points.length - 1; i += 2) {
const x = data.points[i];
const y = data.points[i + 1];
checkObjectType('coordinate', x, 'number', null);
checkObjectType('coordinate', y, 'number', null);
cutPoints.push(
Math.clamp(x, 0, width),
Math.clamp(y, 0, height),
);
}
if (checkShapeArea(this.shapeType, cutPoints)) {
copy.points = cutPoints;
positionUpdated = true;
}
positionUpdated = true;
}
if (updated.occluded) {
@ -499,6 +630,11 @@
copy.color = data.color;
}
if (updated.keyframe) {
// Just check here
checkObjectType('keyframe', data.keyframe, 'boolean', null);
}
// Commit all changes
for (const prop of Object.keys(copy)) {
if (prop in this) {
@ -508,8 +644,18 @@
this.cache[frame][prop] = copy[prop];
}
if (updated.attributes) {
// Mutable attributes will be updated below
for (const attrID of Object.keys(copy.attributes)) {
if (!labelAttributes[attrID].mutable) {
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
}
}
}
if (updated.label) {
for (const shape of this.shapes) {
for (const shape of Object.values(this.shapes)) {
shape.attributes = {};
}
}
@ -519,7 +665,7 @@
// Remove all cache after this keyframe because it have just become outdated
for (const cacheFrame in this.cache) {
if (+cacheFrame > frame) {
delete this.cache[frame];
delete this.cache[cacheFrame];
}
}
@ -535,7 +681,7 @@
// Remove all cache after this keyframe because it have just become outdated
for (const cacheFrame in this.cache) {
if (+cacheFrame > frame) {
delete this.cache[frame];
delete this.cache[cacheFrame];
}
}
@ -552,15 +698,9 @@
};
if (updated.attributes) {
const labelAttributes = this.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator;
}, {});
// Unmutable attributes were updated above
for (const attrID of Object.keys(data.attributes)) {
if (attrID in labelAttributes && labelAttributes[attrID].mutable) {
for (const attrID of Object.keys(copy.attributes)) {
if (labelAttributes[attrID].mutable) {
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
}
@ -743,6 +883,7 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE;
checkNumberOfPoints(this.shapeType, this.points);
}
static distance(points, x, y) {
@ -768,23 +909,25 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYGON;
checkNumberOfPoints(this.shapeType, this.points);
}
static distance(points, x, y) {
function position(x1, y1, x2, y2) {
return ((x1 - x) * (y2 - y) - (x2 - x) * (y1 - y));
return ((x2 - x1) * (y - y1) - (x - x1) * (y2 - y1));
}
let wn = 0;
const distances = [];
for (let i = 0; i < points.length; i += 2) {
for (let i = 0, j = points.length - 2; i < points.length - 1; j = i, i += 2) {
// Current point
const x1 = points[i];
const y1 = points[i + 1];
const x1 = points[j];
const y1 = points[j + 1];
// Next point
const x2 = i + 2 < points.length ? points[i + 2] : points[0];
const y2 = i + 3 < points.length ? points[i + 3] : points[1];
const x2 = points[i];
const y2 = points[i + 1];
// Check if a point is inside a polygon
// with a winding numbers algorithm
@ -795,25 +938,31 @@
wn++;
}
}
} else if (y2 < y) {
if (position(x1, y1, x2, y2) > 0) {
} else if (y2 <= y) {
if (position(x1, y1, x2, y2) < 0) {
wn--;
}
}
// Find the shortest distance from point to an edge
if (((x - x1) * (x2 - x)) >= 0 && ((y - y1) * (y2 - y)) >= 0) {
// Find the length of a perpendicular
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
distances.push(
Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / Math
.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)),
);
// Get an equation of a line in general
const aCoef = (y1 - y2);
const bCoef = (x2 - x1);
// Vector (aCoef, bCoef) is a perpendicular to line
// Now find the point where two lines
// (edge and its perpendicular through the point (x,y)) are cross
const xCross = x - aCoef;
const yCross = y - bCoef;
if (((xCross - x1) * (x2 - xCross)) >= 0
&& ((yCross - y1) * (y2 - yCross)) >= 0) {
// Cross point is on segment between p1(x1,y1) and p2(x2,y2)
distances.push(Math.sqrt(
Math.pow(x - xCross, 2)
+ Math.pow(y - yCross, 2),
));
} else {
// The link below works for lines (which have infinit length)
// There is a case when perpendicular doesn't cross the edge
// In this case we don't use the computed distance
// Instead we use just distance to the nearest point
distances.push(
Math.min(
Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)),
@ -835,6 +984,7 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYLINE;
checkNumberOfPoints(this.shapeType, this.points);
}
static distance(points, x, y) {
@ -878,6 +1028,7 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POINTS;
checkNumberOfPoints(this.shapeType, this.points);
}
static distance(points, x, y) {
@ -899,6 +1050,9 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
}
interpolatePosition(leftPosition, rightPosition, targetFrame) {
@ -1220,7 +1374,7 @@
if (!targetMatched.length) {
// Prevent infinity loop
throw window.cvat.exceptions.ScriptingError('Interpolation mapping is empty');
throw new window.cvat.exceptions.ScriptingError('Interpolation mapping is empty');
}
while (!targetMatched.includes(prev)) {
@ -1310,6 +1464,9 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYGON;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
}
}
@ -1317,6 +1474,9 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYLINE;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
}
}
@ -1324,6 +1484,9 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POINTS;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
}
}
}

@ -102,7 +102,7 @@
} else if (typeof (object.id) === 'undefined') {
splitted.created[type].push(object);
} else {
throw window.cvat.exceptions.ScriptingError(
throw new window.cvat.exceptions.ScriptingError(
`Id of object is defined "${object.id}"`
+ 'but it absents in initial state',
);
@ -140,7 +140,7 @@
+ indexes.shapes.length + indexes.tags.length;
if (indexesLength !== savedLength) {
throw window.cvat.exception.ScriptingError(
throw new window.cvat.exception.ScriptingError(
'Number of indexes is differed by number of saved objects'
+ `${indexesLength} vs ${savedLength}`,
);

@ -11,9 +11,10 @@
const serverProxy = require('./server-proxy');
const Collection = require('./annotations-collection');
const AnnotationsSaver = require('./annotations-saver');
const { checkObjectType } = require('./common');
const jobCache = {};
const taskCache = {};
const jobCache = new WeakMap();
const taskCache = new WeakMap();
function getCache(sessionType) {
if (sessionType === 'task') {
@ -33,17 +34,32 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (!(session.id in cache)) {
if (!cache.has(session)) {
const rawAnnotations = await serverProxy.annotations
.getAnnotations(sessionType, session.id);
const collection = new Collection(session.labels || session.task.labels)
.import(rawAnnotations);
// Get meta information about frames
const startFrame = sessionType === 'job' ? session.startFrame : 0;
const stopFrame = sessionType === 'job' ? session.stopFrame : session.size - 1;
const frameMeta = {};
for (let i = startFrame; i <= stopFrame; i++) {
frameMeta[i] = await session.frames.get(i);
}
const collection = new Collection({
labels: session.labels || session.task.labels,
startFrame,
stopFrame,
frameMeta,
}).import(rawAnnotations);
const saver = new AnnotationsSaver(rawAnnotations.version, collection, session);
cache[session.id] = {
cache.set(session, {
collection,
saver,
};
});
}
}
@ -51,15 +67,15 @@
await getAnnotationsFromServer(session);
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
return cache[session.id].collection.get(frame, filter);
return cache.get(session).collection.get(frame, filter);
}
async function saveAnnotations(session, onUpdate) {
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
await cache[session.id].saver.save(onUpdate);
if (cache.has(session)) {
await cache.get(session).saver.save(onUpdate);
}
// If a collection wasn't uploaded, than it wasn't changed, finally we shouldn't save it
@ -69,11 +85,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.merge(objectStates);
if (cache.has(session)) {
return cache.get(session).collection.merge(objectStates);
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
@ -82,11 +98,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.split(objectState, frame);
if (cache.has(session)) {
return cache.get(session).collection.split(objectState, frame);
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
@ -95,11 +111,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.group(objectStates, reset);
if (cache.has(session)) {
return cache.get(session).collection.group(objectStates, reset);
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
@ -108,23 +124,24 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].saver.hasUnsavedChanges();
if (cache.has(session)) {
return cache.get(session).saver.hasUnsavedChanges();
}
return false;
}
async function clearAnnotations(session, reload) {
checkObjectType('reload', reload, 'boolean', null);
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
cache[session.id].collection.clear();
if (cache.has(session)) {
cache.get(session).collection.clear();
}
if (reload) {
delete cache[session.id];
cache.delete(session);
await getAnnotationsFromServer(session);
}
}
@ -133,11 +150,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.statistics();
if (cache.has(session)) {
return cache.get(session).collection.statistics();
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
@ -146,11 +163,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.put(objectStates);
if (cache.has(session)) {
return cache.get(session).collection.put(objectStates);
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}
@ -159,11 +176,11 @@
const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (session.id in cache) {
return cache[session.id].collection.select(objectStates, x, y);
if (cache.has(session)) {
return cache.get(session).collection.select(objectStates, x, y);
}
throw window.cvat.exceptions.DataError(
throw new window.cvat.exceptions.DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}

@ -348,14 +348,12 @@
*/
config: {
/**
* @memberof module:API.cvat.config
* @property {string} backendAPI host with a backend api
* @memberof module:API.cvat.config
* @property {string} proxy Axios proxy settings.
* For more details please read <a href="https://github.com/axios/axios"> here </a>
* @memberof module:API.cvat.config
* @property {integer} preloadFrames the number of subsequent frames which are
* loaded in background
* @memberof module:API.cvat.config
* @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
@ -364,7 +362,6 @@
* value which is displayed in a logs
* @memberof module:API.cvat.config
*/
preloadFrames: 300,
backendAPI: 'http://localhost:7000/api/v1',
proxy: false,
taskID: undefined,
@ -451,5 +448,9 @@
require('browser-env')();
}
Math.clamp = function (value, min, max) {
return Math.min(Math.max(value, min), max);
};
window.cvat = Object.freeze(implementAPI(cvat));
})();

@ -38,7 +38,7 @@
);
} else if (!fields[prop](filter[prop])) {
throw new window.cvat.exceptions.ArgumentError(
`Received filter property ${prop} is not satisfied for checker`,
`Received filter property "${prop}" is not satisfied for checker`,
);
}
}
@ -61,7 +61,7 @@
if (!(value instanceof instance)) {
if (value !== undefined) {
throw new window.cvat.exceptions.ArgumentError(
`${name} is expected to be ${instance.name}, but `
`"${name}" is expected to be ${instance.name}, but `
+ `"${value.constructor.name}" has been got`,
);
}

@ -59,7 +59,7 @@
/**
* Method returns URL encoded image which can be placed in the img tag
* @method frame
* @method data
* @returns {string}
* @memberof module:API.cvat.classes.FrameData
* @instance
@ -67,16 +67,16 @@
* @throws {module:API.cvat.exception.ServerError}
* @throws {module:API.cvat.exception.PluginError}
*/
async frame() {
async data() {
const result = await PluginRegistry
.apiWrapper.call(this, FrameData.prototype.frame);
.apiWrapper.call(this, FrameData.prototype.data);
return result;
}
}
FrameData.prototype.frame.implementation = async function () {
FrameData.prototype.data.implementation = async function () {
if (!(this.number in frameCache[this.tid])) {
const frame = await serverProxy.frames.getFrame(this.tid, this.number);
const frame = await serverProxy.frames.getData(this.tid, this.number);
if (window.URL.createObjectURL) { // browser env
const url = window.URL.createObjectURL(new Blob([frame]));

@ -153,12 +153,21 @@
* @name points
* @type {number[]}
* @memberof module:API.cvat.classes.ObjectState
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
*/
get: () => data.points,
set: (points) => {
data.updateFlags.points = true;
data.points = [...points];
if (Array.isArray(points)) {
data.updateFlags.points = true;
data.points = [...points];
} else {
throw new window.cvat.exceptions.ArgumentError(
'Points are expected to be an array '
+ `but got ${typeof (points) === 'object'
? points.constructor.name : typeof (points)}`,
);
}
},
},
group: {
@ -252,14 +261,10 @@
get: () => data.attributes,
set: (attributes) => {
if (typeof (attributes) !== 'object') {
if (typeof (attributes) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError(
'Expected attributes are object, but got undefined',
);
}
throw new window.cvat.exceptions.ArgumentError(
`Expected attributes are object, but got ${attributes.constructor.name}`,
'Attributes are expected to be an object '
+ `but got ${typeof (attributes) === 'object'
? attributes.constructor.name : typeof (attributes)}`,
);
}
@ -277,11 +282,17 @@
this.outside = serialized.outside;
this.keyframe = serialized.keyframe;
this.occluded = serialized.occluded;
this.attributes = serialized.attributes;
this.points = serialized.points;
this.color = serialized.color;
this.lock = serialized.lock;
// It can be undefined in a constructor and it can be defined later
if (typeof (serialized.points) !== 'undefined') {
this.points = serialized.points;
}
if (typeof (serialized.attributes) !== 'undefined') {
this.attributes = serialized.attributes;
}
data.updateFlags.reset();
}

@ -421,7 +421,7 @@
return response.data;
}
async function getFrame(tid, frame) {
async function getData(tid, frame) {
const { backendAPI } = window.cvat.config;
let response = null;
@ -629,7 +629,7 @@
frames: {
value: Object.freeze({
getFrame,
getData,
getMeta,
}),
writable: false,

@ -259,6 +259,7 @@
* @param {module:API.cvat.classes.ObjectState[]} data
* array of objects on the specific frame
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.DataError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
@ -302,11 +303,13 @@
* @async
*/
/**
* Select shape under a cursor using math alghorithms
* Select shape under a cursor by using minimal distance
* between a cursor and a shape edge or a shape point
* For closed shapes a cursor is placed inside a shape
* @method select
* @memberof Session.annotations
* @param {module:API.cvat.classes.ObjectState[]} objectStates
* object which can be selected
* objects which can be selected
* @param {float} x horizontal coordinate
* @param {float} y vertical coordinate
* @returns {Object}
@ -659,7 +662,7 @@
return this;
}
throw window.cvat.exceptions.ArgumentError(
throw new window.cvat.exceptions.ArgumentError(
'Can not save job without and id',
);
};
@ -673,7 +676,7 @@
if (frame < this.startFrame || frame > this.stopFrame) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the job`,
`The frame with number ${frame} is out of the job`,
);
}
@ -1262,9 +1265,15 @@
};
Task.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.size) {
throw new window.cvat.exceptions.ArgumentError(
`Frame ${frame} does not exist in the task`,
`The frame with number ${frame} is out of the task`,
);
}

@ -0,0 +1,685 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
// Test cases
describe('Feature: get annotations', () => {
test('get annotations from a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
expect(Array.isArray(annotations)).toBeTruthy();
expect(annotations).toHaveLength(11);
for (const state of annotations) {
expect(state).toBeInstanceOf(window.cvat.classes.ObjectState);
}
});
test('get annotations from a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 101 }))[0];
const annotations0 = await job.annotations.get(0);
const annotations10 = await job.annotations.get(10);
expect(Array.isArray(annotations0)).toBeTruthy();
expect(Array.isArray(annotations10)).toBeTruthy();
expect(annotations0).toHaveLength(1);
expect(annotations10).toHaveLength(2);
for (const state of annotations0.concat(annotations10)) {
expect(state).toBeInstanceOf(window.cvat.classes.ObjectState);
}
});
test('get annotations for frame out of task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
// Out of task
expect(task.annotations.get(500))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
// Out of task
expect(task.annotations.get(-1))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get annotations for frame out of job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 101 }))[0];
// Out of segment
expect(job.annotations.get(500))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
// Out of segment
expect(job.annotations.get(-1))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
// TODO: Test filter (hasn't been implemented yet)
});
describe('Feature: put annotations', () => {
test('put a shape to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
let annotations = await task.annotations.get(1);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
await task.annotations.put([state]);
annotations = await task.annotations.get(1);
expect(annotations).toHaveLength(length + 1);
});
test('put a shape to a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(5);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 5,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
points: [0, 0, 100, 100],
occluded: false,
label: job.task.labels[0],
});
await job.annotations.put([state]);
annotations = await job.annotations.get(5);
expect(annotations).toHaveLength(length + 1);
});
test('put a track to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
let annotations = await task.annotations.get(1);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.TRACK,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
await task.annotations.put([state]);
annotations = await task.annotations.get(1);
expect(annotations).toHaveLength(length + 1);
});
test('put a track to a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(5);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 5,
objectType: window.cvat.enums.ObjectType.TRACK,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
points: [0, 0, 100, 100],
occluded: false,
label: job.task.labels[0],
});
await job.annotations.put([state]);
annotations = await job.annotations.get(5);
expect(annotations).toHaveLength(length + 1);
});
test('put object without objectType to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 1,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape with bad attributes to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
attributes: { 'bad key': 55 },
occluded: true,
label: task.labels[0],
});
expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape without points and with invalud points to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
occluded: true,
points: [],
label: task.labels[0],
});
await expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.DataError);
delete state.points;
await expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.DataError);
state.points = ['150,50 250,30'];
expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape without type to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape without label and with bad label to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
});
await expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = 'bad label';
await expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = {};
await expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape with bad frame to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: '5',
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
expect(task.annotations.put([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: check unsaved changes', () => {
test('check unsaved changes in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
const annotations = await task.annotations.get(0);
annotations[0].keyframe = true;
await annotations[0].save();
expect(await task.annotations.hasUnsavedChanges()).toBe(true);
});
test('check unsaved changes in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
const annotations = await job.annotations.get(0);
annotations[0].occluded = true;
await annotations[0].save();
expect(await job.annotations.hasUnsavedChanges()).toBe(true);
});
});
describe('Feature: save annotations', () => {
test('create & save annotations for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
let annotations = await task.annotations.get(0);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
await task.annotations.put([state]);
expect(await task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
annotations = await task.annotations.get(0);
expect(annotations).toHaveLength(length + 1);
});
test('update & save annotations for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
annotations[0].occluded = true;
await annotations[0].save();
expect(await task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
});
test('delete & save annotations for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
await annotations[0].delete();
expect(await task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
});
test('create & save annotations for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
const { length } = annotations;
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: job.task.labels[0],
});
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
await job.annotations.put([state]);
expect(await job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
annotations = await job.annotations.get(0);
expect(annotations).toHaveLength(length + 1);
});
test('update & save annotations for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
annotations[0].points = [0, 100, 200, 300];
await annotations[0].save();
expect(await job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
});
test('delete & save annotations for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
await annotations[0].delete();
expect(await job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
});
});
describe('Feature: merge annotations', () => {
test('merge annotations in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const annotations1 = await task.annotations.get(1);
const states = [annotations0[0], annotations1[0]];
await task.annotations.merge(states);
const merged0 = (await task.annotations.get(0))
.filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK);
const merged1 = (await task.annotations.get(1))
.filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK);
expect(merged0).toHaveLength(1);
expect(merged1).toHaveLength(1);
expect(merged0[0].points).toEqual(states[0].points);
expect(merged1[0].points).toEqual(states[1].points);
});
test('merge annotations in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations0 = await job.annotations.get(0);
const annotations1 = await job.annotations.get(1);
const states = [annotations0[0], annotations1[0]];
await job.annotations.merge(states);
const merged0 = (await job.annotations.get(0))
.filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK);
const merged1 = (await job.annotations.get(1))
.filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK);
expect(merged0).toHaveLength(1);
expect(merged1).toHaveLength(1);
expect(merged0[0].points).toEqual(states[0].points);
expect(merged1[0].points).toEqual(states[1].points);
});
test('trying to merge not object state', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const states = [annotations0[0], {}];
expect(task.annotations.merge(states))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to merge object state which is not saved in a collection', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
const states = [annotations0[0], state];
expect(task.annotations.merge(states))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to merge with bad label', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const annotations1 = await task.annotations.get(1);
const states = [annotations0[0], annotations1[0]];
states[0].label = new window.cvat.classes.Label({
id: 500,
name: 'new_label',
attributes: [],
});
expect(task.annotations.merge(states))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to merge with different shape types', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const annotations1 = (await task.annotations.get(1))
.filter(state => state.shapeType === window.cvat.enums.ObjectShape.POLYGON);
const states = [annotations0[0], annotations1[0]];
expect(task.annotations.merge(states))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to merge with different labels', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const annotations1 = await task.annotations.get(1);
const states = [annotations0[0], annotations1[0]];
states[1].label = new window.cvat.classes.Label({
id: 500,
name: 'new_label',
attributes: [],
});
expect(task.annotations.merge(states))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: split annotations', () => {
test('split annotations in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations4 = await task.annotations.get(4);
const annotations5 = await task.annotations.get(5);
expect(annotations4[0].clientID).toBe(annotations5[0].clientID);
await task.annotations.split(annotations5[0], 5);
const splitted4 = await task.annotations.get(4);
const splitted5 = (await task.annotations.get(5)).filter(state => !state.outside);
expect(splitted4[0].clientID).not.toBe(splitted5[0].clientID);
});
test('split annotations in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 101 }))[0];
const annotations4 = await job.annotations.get(4);
const annotations5 = await job.annotations.get(5);
expect(annotations4[0].clientID).toBe(annotations5[0].clientID);
await job.annotations.split(annotations5[0], 5);
const splitted4 = await job.annotations.get(4);
const splitted5 = (await job.annotations.get(5)).filter(state => !state.outside);
expect(splitted4[0].clientID).not.toBe(splitted5[0].clientID);
});
test('split on a bad frame', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations4 = await task.annotations.get(4);
const annotations5 = await task.annotations.get(5);
expect(annotations4[0].clientID).toBe(annotations5[0].clientID);
expect(task.annotations.split(annotations5[0], 'bad frame'))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: group annotations', () => {
test('group annotations in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0);
const groupID = await task.annotations.group(annotations);
expect(typeof (groupID)).toBe('number');
annotations = await task.annotations.get(0);
for (const state of annotations) {
expect(state.group).toBe(groupID);
}
});
test('group annotations in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
const groupID = await job.annotations.group(annotations);
expect(typeof (groupID)).toBe('number');
annotations = await job.annotations.get(0);
for (const state of annotations) {
expect(state.group).toBe(groupID);
}
});
test('trying to group object state which has not been saved in a collection', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
});
expect(task.annotations.group([state]))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to group not object state', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
expect(task.annotations.group(annotations.concat({})))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: clear annotations', () => {
test('clear annotations in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
await task.annotations.clear();
annotations = await task.annotations.get(0);
expect(annotations.length).toBe(0);
});
test('clear annotations in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
await job.annotations.clear();
annotations = await job.annotations.get(0);
expect(annotations.length).toBe(0);
});
test('clear annotations with reload in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
let annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
annotations[0].occluded = true;
await annotations[0].save();
expect(await task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.clear(true);
annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false);
});
test('clear annotations with reload in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
let annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
annotations[0].occluded = true;
await annotations[0].save();
expect(await job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.clear(true);
annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false);
});
test('clear annotations with bad reload parameter', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
await task.annotations.clear(true);
expect(task.annotations.clear('reload'))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: get statistics', () => {
test('get statistics from a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
await task.annotations.clear(true);
const statistics = await task.annotations.statistics();
expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics);
expect(statistics.total.total).toBe(29);
});
test('get statistics from a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 101 }))[0];
await job.annotations.clear(true);
const statistics = await job.annotations.statistics();
expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics);
expect(statistics.total.total).toBe(512);
});
});
describe('Feature: select object', () => {
test('select object in a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
let result = await task.annotations.select(annotations, 1430, 765);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
result = await task.annotations.select(annotations, 1415, 765);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(10);
result = await task.annotations.select(annotations, 1083, 543);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POINTS);
expect(result.state.points.length).toBe(16);
result = await task.annotations.select(annotations, 613, 811);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(94);
});
test('select object in a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0);
let result = await job.annotations.select(annotations, 490, 540);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
result = await job.annotations.select(annotations, 430, 260);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYLINE);
result = await job.annotations.select(annotations, 1473, 250);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
result = await job.annotations.select(annotations, 1490, 237);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.points.length).toBe(94);
});
test('trying to select from not object states', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
expect(task.annotations.select(annotations.concat({}), 500, 500))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to select with invalid coordinates', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
expect(task.annotations.select(annotations, null, null))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.annotations.select(annotations, null, null))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.annotations.select(annotations, '5', '10'))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});

@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
const { FrameData } = require('../../src/frames');
describe('Feature: get frame meta', () => {
test('get meta for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const frame = await task.frames.get(0);
expect(frame).toBeInstanceOf(FrameData);
});
test('get meta for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const frame = await job.frames.get(0);
expect(frame).toBeInstanceOf(FrameData);
});
test('pass frame number out of a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get(100))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(task.frames.get(-1))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('pass bad frame number', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get('5'))
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('do not pass any frame number', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
expect(task.frames.get())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: get frame data', () => {
test('get frame data for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const frame = await task.frames.get(0);
const frameData = await frame.data();
expect(typeof (frameData)).toBe('string');
});
test('get frame data for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const frame = await job.frames.get(0);
const frameData = await frame.data();
expect(typeof (frameData)).toBe('string');
});
});

@ -64,26 +64,26 @@ describe('Feature: get a list of jobs', () => {
});
test('get jobs by invalid filter with both taskID and jobID', async () => {
await expect(window.cvat.jobs.get({
expect(window.cvat.jobs.get({
taskID: 1,
jobID: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by invalid job id', async () => {
await expect(window.cvat.jobs.get({
expect(window.cvat.jobs.get({
jobID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by invalid task id', async () => {
await expect(window.cvat.jobs.get({
expect(window.cvat.jobs.get({
taskID: '1',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get jobs by unknown filter', async () => {
await expect(window.cvat.jobs.get({
expect(window.cvat.jobs.get({
unknown: 50,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});

@ -0,0 +1,347 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
describe('Feature: set attributes for an object state', () => {
test('set a valid value', () => {
const state = new window.cvat.classes.ObjectState({
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
frame: 5,
});
const attributes = {
5: 'man',
6: 'glasses',
};
state.attributes = attributes;
expect(state.attributes).toEqual(attributes);
});
test('trying to set a bad value', () => {
const state = new window.cvat.classes.ObjectState({
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
frame: 5,
});
let attributes = 'bad attribute';
expect(() => {
state.attributes = attributes;
}).toThrow(window.cvat.exceptions.ArgumentError);
attributes = 5;
expect(() => {
state.attributes = attributes;
}).toThrow(window.cvat.exceptions.ArgumentError);
attributes = false;
expect(() => {
state.attributes = attributes;
}).toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: set points for an object state', () => {
test('set a valid value', () => {
const state = new window.cvat.classes.ObjectState({
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
frame: 5,
});
const points = [1, 2, 3, 4];
state.points = points;
expect(state.points).toEqual(points);
});
test('trying to set a bad value', () => {
const state = new window.cvat.classes.ObjectState({
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
frame: 5,
});
let points = 'bad points';
expect(() => {
state.points = points;
}).toThrow(window.cvat.exceptions.ArgumentError);
points = 5;
expect(() => {
state.points = points;
}).toThrow(window.cvat.exceptions.ArgumentError);
points = false;
expect(() => {
state.points = points;
}).toThrow(window.cvat.exceptions.ArgumentError);
points = {};
expect(() => {
state.points = points;
}).toThrow(window.cvat.exceptions.ArgumentError);
});
});
describe('Feature: save object from its state', () => {
test('save valid values for a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
let state = annotations[0];
expect(state.objectType).toBe(window.cvat.enums.ObjectType.SHAPE);
expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
state.points = [0, 0, 100, 100];
state.occluded = true;
[, state.label] = task.labels;
state.lock = true;
state = await state.save();
expect(state).toBeInstanceOf(window.cvat.classes.ObjectState);
expect(state.label.id).toBe(task.labels[1].id);
expect(state.lock).toBe(true);
expect(state.occluded).toBe(true);
expect(state.points).toEqual([0, 0, 100, 100]);
});
test('save valid values for a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(10);
let state = annotations[1];
expect(state.objectType).toBe(window.cvat.enums.ObjectType.TRACK);
expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
state.occluded = true;
state.lock = true;
state.points = [100, 200, 200, 400];
state.attributes = {
1: 'sitting',
3: 'female',
2: '10',
4: 'true',
};
state = await state.save();
expect(state).toBeInstanceOf(window.cvat.classes.ObjectState);
expect(state.lock).toBe(true);
expect(state.occluded).toBe(true);
expect(state.points).toEqual([100, 200, 200, 400]);
expect(state.attributes[1]).toBe('sitting');
expect(state.attributes[2]).toBe('10');
expect(state.attributes[3]).toBe('female');
expect(state.attributes[4]).toBe('true');
state.lock = false;
[state.label] = task.labels;
state = await state.save();
expect(state.label.id).toBe(task.labels[0].id);
state.outside = true;
state = await state.save();
expect(state.lock).toBe(false);
expect(state.outside).toBe(true);
state.keyframe = false;
state = await state.save();
expect(state.keyframe).toBe(false);
});
test('save bad values for a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
state.occluded = 'false';
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points;
state.occluded = false;
state.points = ['100', '50', '100', {}];
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints;
state.lock = 'true';
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldLabel = state.label;
state.lock = false;
state.label = 1;
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = oldLabel;
state.attributes = { 1: {}, 2: false, 3: () => {} };
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('save bad values for a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
state.occluded = 'false';
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points;
state.occluded = false;
state.points = ['100', '50', '100', {}];
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints;
state.lock = 'true';
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldLabel = state.label;
state.lock = false;
state.label = 1;
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = oldLabel;
state.outside = 5;
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.outside = false;
state.keyframe = '10';
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.keyframe = true;
state.attributes = { 1: {}, 2: false, 3: () => {} };
await expect(state.save())
.rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('trying to change locked shape', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
let state = annotations[0];
state.lock = true;
state = await state.save();
const { points } = state;
state.points = [0, 0, 500, 500];
state = await state.save();
expect(state.points).toEqual(points);
});
test('trying to set too small area of a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
let state = annotations[0];
const { points } = state;
state.points = [0, 0, 2, 2]; // area is 4
state = await state.save();
expect(state.points).toEqual(points);
});
test('trying to set too small area of a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
let state = annotations[0];
const { points } = state;
state.points = [0, 0, 2, 2]; // area is 4
state = await state.save();
expect(state.points).toEqual(points);
});
test('trying to set too small length of a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
let state = annotations[8];
const { points } = state;
state.points = [0, 0, 2, 2]; // length is 2
state = await state.save();
expect(state.points).toEqual(points);
});
});
describe('Feature: delete object', () => {
test('delete a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotationsBefore = await task.annotations.get(0);
const { length } = annotationsBefore;
await annotationsBefore[0].delete();
const annotationsAfter = await task.annotations.get(0);
expect(annotationsAfter).toHaveLength(length - 1);
});
test('delete a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotationsBefore = await task.annotations.get(0);
const { length } = annotationsBefore;
await annotationsBefore[0].delete();
const annotationsAfter = await task.annotations.get(0);
expect(annotationsAfter).toHaveLength(length - 1);
});
});
describe('Feature: change z order of an object', () => {
test('up z order for a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
const { zOrder } = state;
await state.up();
expect(state.zOrder).toBeGreaterThan(zOrder);
});
test('up z order for a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
const { zOrder } = state;
await state.up();
expect(state.zOrder).toBeGreaterThan(zOrder);
});
test('down z order for a shape', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
const { zOrder } = state;
await state.down();
expect(state.zOrder).toBeLessThan(zOrder);
});
test('down z order for a track', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0);
const state = annotations[0];
const { zOrder } = state;
await state.down();
expect(state.zOrder).toBeLessThan(zOrder);
});
});

@ -0,0 +1,98 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
jest:false
describe:false
*/
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
// Initialize api
require('../../src/api');
describe('Feature: dummy feature', () => {
test('dummy test', async () => {
// TODO: Write test after design of plugin system
});
});
/*
const plugin = {
name: 'Example Plugin',
description: 'This example plugin demonstrates how plugin system in CVAT works',
cvat: {
server: {
about: {
async leave(self, result) {
result.plugins = await self.internal.getPlugins();
return result;
},
},
},
classes: {
Job: {
prototype: {
annotations: {
put: {
enter(self, objects) {
for (const obj of objects) {
if (obj.type !== 'tag') {
const points = obj.position.map((point) => {
const roundPoint = {
x: Math.round(point.x),
y: Math.round(point.y),
};
return roundPoint;
});
obj.points = points;
}
}
},
},
},
},
},
},
},
internal: {
async getPlugins() {
const plugins = await window.cvat.plugins.list();
return plugins.map((el) => {
const obj = {
name: el.name,
description: el.description,
};
return obj;
});
},
},
};
async function test() {
await window.cvat.plugins.register(plugin);
await window.cvat.server.login('admin', 'nimda760');
try {
console.log(JSON.stringify(await window.cvat.server.about()));
console.log(await window.cvat.users.get({ self: false }));
console.log(await window.cvat.users.get({ self: true }));
console.log(JSON.stringify(await window.cvat.jobs.get({ taskID: 8 })));
console.log(JSON.stringify(await window.cvat.jobs.get({ jobID: 10 })));
console.log(await window.cvat.tasks.get());
console.log(await window.cvat.tasks.get({ id: 8 }));
console.log('Done.');
} catch (exception) {
console.log(exception.constructor.name);
console.log(exception.message);
}
}
*/

@ -44,7 +44,7 @@ describe('Feature: get share storage info', () => {
});
test('get files in a some unknown dir of a share storage', async () => {
await expect(window.cvat.server.share(
expect(window.cvat.server.share(
'Unknown Directory',
)).rejects.toThrow(window.cvat.exceptions.ServerError);
});

@ -26,7 +26,7 @@ describe('Feature: get a list of tasks', () => {
test('get all tasks', async () => {
const result = await window.cvat.tasks.get();
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(3);
expect(result).toHaveLength(5);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
}
@ -51,7 +51,7 @@ describe('Feature: get a list of tasks', () => {
});
test('get a task by an invalid id', async () => {
await expect(window.cvat.tasks.get({
expect(window.cvat.tasks.get({
id: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
@ -61,7 +61,7 @@ describe('Feature: get a list of tasks', () => {
mode: 'interpolation',
});
expect(Array.isArray(result)).toBeTruthy();
expect(result).toHaveLength(2);
expect(result).toHaveLength(3);
for (const el of result) {
expect(el).toBeInstanceOf(Task);
expect(el.mode).toBe('interpolation');
@ -69,7 +69,7 @@ describe('Feature: get a list of tasks', () => {
});
test('get tasks by invalid filters', async () => {
await expect(window.cvat.tasks.get({
expect(window.cvat.tasks.get({
unknown: '5',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
@ -184,77 +184,3 @@ describe('Feature: delete a task', () => {
expect(result).toHaveLength(0);
});
});
/*
const plugin = {
name: 'Example Plugin',
description: 'This example plugin demonstrates how plugin system in CVAT works',
cvat: {
server: {
about: {
async leave(self, result) {
result.plugins = await self.internal.getPlugins();
return result;
},
},
},
classes: {
Job: {
prototype: {
annotations: {
put: {
enter(self, objects) {
for (const obj of objects) {
if (obj.type !== 'tag') {
const points = obj.position.map((point) => {
const roundPoint = {
x: Math.round(point.x),
y: Math.round(point.y),
};
return roundPoint;
});
obj.points = points;
}
}
},
},
},
},
},
},
},
internal: {
async getPlugins() {
const plugins = await window.cvat.plugins.list();
return plugins.map((el) => {
const obj = {
name: el.name,
description: el.description,
};
return obj;
});
},
},
};
async function test() {
await window.cvat.plugins.register(plugin);
await window.cvat.server.login('admin', 'nimda760');
try {
console.log(JSON.stringify(await window.cvat.server.about()));
console.log(await window.cvat.users.get({ self: false }));
console.log(await window.cvat.users.get({ self: true }));
console.log(JSON.stringify(await window.cvat.jobs.get({ taskID: 8 })));
console.log(JSON.stringify(await window.cvat.jobs.get({ jobID: 10 })));
console.log(await window.cvat.tasks.get());
console.log(await window.cvat.tasks.get({ id: 8 }));
console.log('Done.');
} catch (exception) {
console.log(exception.constructor.name);
console.log(exception.message);
}
}
*/

@ -41,13 +41,13 @@ describe('Feature: get a list of users', () => {
});
test('get users with unknown filter key', async () => {
await expect(window.cvat.users.get({
expect(window.cvat.users.get({
unknown: '50',
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});
test('get users with invalid filter key', async () => {
await expect(window.cvat.users.get({
expect(window.cvat.users.get({
self: 1,
})).rejects.toThrow(window.cvat.exceptions.ArgumentError);
});

File diff suppressed because it is too large Load Diff

@ -14,6 +14,9 @@ const {
aboutDummyData,
shareDummyData,
usersDummyData,
taskAnnotationsDummyData,
jobAnnotationsDummyData,
frameMetaDummyData,
} = require('./dummy-data.mock');
@ -185,19 +188,50 @@ class ServerProxy {
return JSON.parse(JSON.stringify(usersDummyData)).results[0];
}
async function getFrame() {
return null;
async function getData() {
return 'DUMMY_IMAGE';
}
async function getMeta() {
return null;
async function getMeta(tid) {
return JSON.parse(JSON.stringify(frameMetaDummyData[tid]));
}
async function getAnnotations() {
async function getAnnotations(session, id) {
if (session === 'task') {
return JSON.parse(JSON.stringify(taskAnnotationsDummyData[id]));
}
if (session === 'job') {
return JSON.parse(JSON.stringify(jobAnnotationsDummyData[id]));
}
return null;
}
async function updateAnnotations() {
async function updateAnnotations(session, id, data, action) {
// Actually we do not change our dummy data
// We just update the argument in some way and return it
data.version += 1;
if (action === 'create') {
let idGenerator = 1000;
data.tracks.concat(data.tags).concat(data.shapes).map((el) => {
el.id = ++idGenerator;
return el;
});
return data;
}
if (action === 'update') {
return data;
}
if (action === 'delete') {
return data;
}
return null;
}
@ -241,7 +275,7 @@ class ServerProxy {
frames: {
value: Object.freeze({
getFrame,
getData,
getMeta,
}),
writable: false,

Loading…
Cancel
Save