Skip to content

Commit

Permalink
User interface with react and antd (cvat-ai#755)
Browse files Browse the repository at this point in the history
* Login page, router
* Registration
* Tasks view
  • Loading branch information
bsekachev authored and Chris Lee-Messer committed Mar 5, 2020
1 parent de2d92e commit b0086f1
Show file tree
Hide file tree
Showing 150 changed files with 15,812 additions and 12,565 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"directory": "./cvat-canvas",
"changeProcessCWD": true
},
{
"directory": "./cvat-ui",
"changeProcessCWD": true
},
{
"directory": ".",
"changeProcessCWD": true
Expand Down
8,581 changes: 8,581 additions & 0 deletions cvat-canvas/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions cvat-core/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist
docs
node_modules
reports

1 change: 1 addition & 0 deletions cvat-core/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@
"func-names": [0],
"valid-typeof": [0],
"no-console": [0], // this rule deprecates console.log, console.warn etc. because "it is not good in production code"
"max-classes-per-file": [0],
},
};
4 changes: 2 additions & 2 deletions cvat-core/src/annotation-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@
handler_file: initialData.handler_file,
};

data.dumpers = initialData.dumpers.map(el => new Dumper(el));
data.loaders = initialData.loaders.map(el => new Loader(el));
data.dumpers = initialData.dumpers.map((el) => new Dumper(el));
data.loaders = initialData.loaders.map((el) => new Loader(el));

// Now all fields are readonly
Object.defineProperties(this, {
Expand Down
22 changes: 11 additions & 11 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,19 @@

export() {
const data = {
tracks: this.tracks.filter(track => !track.removed)
.map(track => track.toJSON()),
tracks: this.tracks.filter((track) => !track.removed)
.map((track) => track.toJSON()),
shapes: Object.values(this.shapes)
.reduce((accumulator, value) => {
accumulator.push(...value);
return accumulator;
}, []).filter(shape => !shape.removed)
.map(shape => shape.toJSON()),
}, []).filter((shape) => !shape.removed)
.map((shape) => shape.toJSON()),
tags: Object.values(this.tags).reduce((accumulator, value) => {
accumulator.push(...value);
return accumulator;
}, []).filter(tag => !tag.removed)
.map(tag => tag.toJSON()),
}, []).filter((tag) => !tag.removed)
.map((tag) => tag.toJSON()),
};

return data;
Expand All @@ -200,7 +200,7 @@
const shapes = this.shapes[frame] || [];
const tags = this.tags[frame] || [];

const objects = tracks.concat(shapes).concat(tags).filter(object => !object.removed);
const objects = tracks.concat(shapes).concat(tags).filter((object) => !object.removed);
// filtering here

const objectStates = [];
Expand Down Expand Up @@ -370,7 +370,7 @@

const clientID = ++this.count;
const track = {
frame: Math.min.apply(null, Object.keys(keyframes).map(frame => +frame)),
frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)),
shapes: Object.values(keyframes),
group: 0,
label_id: label.id,
Expand Down Expand Up @@ -577,7 +577,7 @@

if (objectType === 'track') {
const keyframes = Object.keys(object.shapes)
.sort((a, b) => +a - +b).map(el => +el);
.sort((a, b) => +a - +b).map((el) => +el);

let prevKeyframe = keyframes[0];
let visible = false;
Expand Down Expand Up @@ -699,13 +699,13 @@
} else if (state.objectType === 'track') {
constructed.tracks.push({
attributes: attributes
.filter(attr => !labelAttributes[attr.spec_id].mutable),
.filter((attr) => !labelAttributes[attr.spec_id].mutable),
frame: state.frame,
group: 0,
label_id: state.label.id,
shapes: [{
attributes: attributes
.filter(attr => labelAttributes[attr.spec_id].mutable),
.filter((attr) => labelAttributes[attr.spec_id].mutable),
frame: state.frame,
occluded: state.occluded || false,
outside: false,
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/src/annotations-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
lock: this.lock,
zOrder: this.zOrder,
points: [...this.points],
attributes: Object.assign({}, this.attributes),
attributes: { ...this.attributes },
label: this.label,
group: this.group,
color: this.color,
Expand Down
16 changes: 12 additions & 4 deletions cvat-core/src/api-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

cvat.server.formats.implementation = async () => {
const result = await serverProxy.server.formats();
return result.map(el => new AnnotationFormat(el));
return result.map((el) => new AnnotationFormat(el));
};

cvat.server.register.implementation = async (username, firstName, lastName,
Expand Down Expand Up @@ -82,7 +82,7 @@
users = await serverProxy.users.getUsers();
}

users = users.map(user => new User(user));
users = users.map((user) => new User(user));
return users;
};

Expand Down Expand Up @@ -116,8 +116,11 @@

// If task was found by its id, then create task instance and get Job instance from it
if (tasks !== null && tasks.length) {
tasks[0].owner = await serverProxy.users.getUsers(tasks[0].owner);
tasks[0].assignee = await serverProxy.users.getUsers(tasks[0].assignee);
const task = new Task(tasks[0]);
return filter.jobID ? task.jobs.filter(job => job.id === filter.jobID) : task.jobs;
return filter.jobID ? task.jobs
.filter((job) => job.id === filter.jobID) : task.jobs;
}

return [];
Expand Down Expand Up @@ -158,8 +161,13 @@
}
}

const users = await serverProxy.users.getUsers();
const tasksData = await serverProxy.tasks.getTasks(searchParams.toString());
const tasks = tasksData.map(task => new Task(task));
const tasks = tasksData.map((task) => {
[task.owner] = users.filter((user) => user.id === task.owner);
[task.assignee] = users.filter((user) => user.id === task.assignee);
return new Task(task);
});
tasks.count = tasksData.count;

return tasks;
Expand Down
21 changes: 21 additions & 0 deletions cvat-core/src/frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,26 @@
});
};

async function getPreview(taskID) {
return new Promise(async (resolve, reject) => {
try {
// Just go to server and get preview (no any cache)
const result = await serverProxy.frames.getPreview(taskID);
if (isNode) {
resolve(global.Buffer.from(result, 'binary').toString('base64'));
} else if (isBrowser) {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(result);
}
} catch (error) {
reject(error);
}
});
}

async function getFrame(taskID, mode, frame) {
if (!(taskID in frameDataCache)) {
frameDataCache[taskID] = {};
Expand Down Expand Up @@ -140,5 +160,6 @@
module.exports = {
FrameData,
getFrame,
getPreview,
};
})();
36 changes: 32 additions & 4 deletions cvat-core/src/server-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,20 @@
}
}

async function getUsers() {
async function getUsers(id = null) {
const { backendAPI } = config;

let response = null;
try {
response = await Axios.get(`${backendAPI}/users`, {
proxy: config.proxy,
});
if (id === null) {
response = await Axios.get(`${backendAPI}/users`, {
proxy: config.proxy,
});
} else {
response = await Axios.get(`${backendAPI}/users/${id}`, {
proxy: config.proxy,
});
}
} catch (errorData) {
throw generateError(errorData, 'Could not get users from the server');
}
Expand All @@ -372,6 +378,27 @@
return response.data;
}

async function getPreview(tid) {
const { backendAPI } = config;

let response = null;
try {
// TODO: change 0 frame to preview
response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/0`, {
proxy: config.proxy,
responseType: 'blob',
});
} catch (errorData) {
const code = errorData.response ? errorData.response.status : errorData.code;
throw new ServerError(
`Could not get preview frame for the task ${tid} from the server`,
code,
);
}

return response.data;
}

async function getData(tid, frame) {
const { backendAPI } = config;

Expand Down Expand Up @@ -567,6 +594,7 @@
value: Object.freeze({
getData,
getMeta,
getPreview,
}),
writable: false,
},
Expand Down
42 changes: 35 additions & 7 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const { getFrame } = require('./frames');
const { getFrame, getPreview } = require('./frames');
const { ArgumentError } = require('./exceptions');
const { TaskStatus } = require('./enums');
const { Label } = require('./labels');
Expand Down Expand Up @@ -109,6 +109,11 @@
.apiWrapper.call(this, prototype.frames.get, frame);
return result;
},
async preview() {
const result = await PluginRegistry
.apiWrapper.call(this, prototype.frames.preview);
return result;
},
},
writable: true,
}),
Expand Down Expand Up @@ -380,6 +385,17 @@
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
/**
* Get the first frame of a task for preview
* @method preview
* @memberof Session.frames
* @returns {string} - jpeg encoded image
* @instance
* @async
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/

/**
* Namespace is used for an interaction with logs
Expand Down Expand Up @@ -619,6 +635,7 @@

this.frames = {
get: Object.getPrototypeOf(this).frames.get.bind(this),
preview: Object.getPrototypeOf(this).frames.preview.bind(this),
};
}

Expand Down Expand Up @@ -780,9 +797,9 @@
get: () => data.mode,
},
/**
* Identificator of a user who has created the task
* Instance of a user who has created the task
* @name owner
* @type {integer}
* @type {module:API.cvat.classes.User}
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
Expand All @@ -791,9 +808,9 @@
get: () => data.owner,
},
/**
* Identificator of a user who is responsible for the task
* Instance of a user who is responsible for the task
* @name assignee
* @type {integer}
* @type {module:API.cvat.classes.User}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
Expand Down Expand Up @@ -1122,6 +1139,7 @@

this.frames = {
get: Object.getPrototypeOf(this).frames.get.bind(this),
preview: Object.getPrototypeOf(this).frames.preview.bind(this),
};
}

Expand Down Expand Up @@ -1218,6 +1236,11 @@
return frameData;
};

Job.prototype.frames.preview.implementation = async function () {
const frameData = await getPreview(this.task.id);
return frameData;
};

// TODO: Check filter for annotations
Job.prototype.annotations.get.implementation = async function (frame, filter) {
if (frame < this.startFrame || frame > this.stopFrame) {
Expand Down Expand Up @@ -1293,7 +1316,7 @@
name: this.name,
bug_tracker: this.bugTracker,
z_order: this.zOrder,
labels: [...this.labels.map(el => el.toJSON())],
labels: [...this.labels.map((el) => el.toJSON())],
};

await serverProxy.tasks.saveTask(this.id, taskData);
Expand All @@ -1302,7 +1325,7 @@

const taskData = {
name: this.name,
labels: this.labels.map(el => el.toJSON()),
labels: this.labels.map((el) => el.toJSON()),
image_quality: this.imageQuality,
z_order: Boolean(this.zOrder),
};
Expand Down Expand Up @@ -1358,6 +1381,11 @@
return result;
};

Task.prototype.frames.preview.implementation = async function () {
const frameData = await getPreview(this.id);
return frameData;
};

// TODO: Check filter for annotations
Task.prototype.annotations.get.implementation = async function (frame, filter) {
if (!Number.isInteger(frame) || frame < 0) {
Expand Down
14 changes: 14 additions & 0 deletions cvat-core/tests/api/frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,17 @@ describe('Feature: get frame data', () => {
expect(typeof (frameData)).toBe('string');
});
});

describe('Feature: get frame preview', () => {
test('get frame preview for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const frame = await task.frames.preview();
expect(typeof (frame)).toBe('string');
});

test('get frame preview for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const frame = await job.frames.preview();
expect(typeof (frame)).toBe('string');
});
});
Loading

0 comments on commit b0086f1

Please sign in to comment.