You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cvat/cvat-core/src/session-implementation.ts

827 lines
28 KiB
TypeScript

import { ArgumentError, DataError } from './exceptions';
import { HistoryActions } from './enums';
import loggerStorage from './logger-storage';
import serverProxy from './server-proxy';
import {
getFrame,
deleteFrame,
restoreFrame,
getRanges,
getPreview,
clear as clearFrames,
findNotDeletedFrame,
getContextImage,
patchMeta,
getDeletedFrames,
} from './frames';
import Issue from './issue';
import { checkObjectType } from './common';
import {
getAnnotations, putAnnotations, saveAnnotations,
hasUnsavedChanges, searchAnnotations, searchEmptyFrame,
mergeAnnotations, splitAnnotations, groupAnnotations,
clearAnnotations, selectObject, annotationsStatistics,
importCollection, exportCollection, importDataset,
exportDataset, undoActions, redoActions,
freezeHistory, clearActions, getActions,
clearCache, getHistory,
} from './annotations';
// must be called with task/job context
async function deleteFrameWrapper(jobID, frame) {
const history = getHistory(this);
const redo = async () => {
deleteFrame(jobID, frame);
};
await redo();
history.do(HistoryActions.REMOVED_FRAME, async () => {
restoreFrame(jobID, frame);
}, redo, [], frame);
}
async function restoreFrameWrapper(jobID, frame) {
const history = getHistory(this);
const redo = async () => {
restoreFrame(jobID, frame);
};
await redo();
history.do(HistoryActions.RESTORED_FRAME, async () => {
deleteFrame(jobID, frame);
}, redo, [], frame);
}
export function implementJob(Job) {
Job.prototype.save.implementation = async function () {
if (this.id) {
const jobData = this._updateTrigger.getUpdated(this);
if (jobData.assignee) {
jobData.assignee = jobData.assignee.id;
}
const data = await serverProxy.jobs.save(this.id, jobData);
this._updateTrigger.reset();
return new Job(data);
}
throw new ArgumentError('Could not save job without id');
};
Job.prototype.issues.implementation = async function () {
const result = await serverProxy.issues.get(this.id);
return result.map((issue) => new Issue(issue));
};
Job.prototype.openIssue.implementation = async function (issue, message) {
checkObjectType('issue', issue, null, Issue);
checkObjectType('message', message, 'string');
const result = await serverProxy.issues.create({
...issue.serialize(),
message,
});
return new Issue(result);
};
Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) {
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.id,
this.dataChunkSize,
this.dataChunkType,
this.mode,
frame,
this.startFrame,
this.stopFrame,
isPlaying,
step,
this.dimension,
);
return frameData;
};
Job.prototype.frames.delete.implementation = async function (frame) {
if (!Number.isInteger(frame)) {
throw new Error(`Frame must be an integer. Got: "${frame}"`);
}
if (frame < this.startFrame || frame > this.stopFrame) {
throw new Error('The frame is out of the job');
}
await deleteFrameWrapper.call(this, this.id, frame);
};
Job.prototype.frames.restore.implementation = async function (frame) {
if (!Number.isInteger(frame)) {
throw new Error(`Frame must be an integer. Got: "${frame}"`);
}
if (frame < this.startFrame || frame > this.stopFrame) {
throw new Error('The frame is out of the job');
}
await restoreFrameWrapper.call(this, this.id, frame);
};
Job.prototype.frames.save.implementation = async function () {
const result = await patchMeta(this.id);
return result;
};
Job.prototype.frames.ranges.implementation = async function () {
const rangesData = await getRanges(this.id);
return rangesData;
};
Job.prototype.frames.preview.implementation = async function () {
if (this.id === null || this.taskId === null) {
return '';
}
const frameData = await getPreview(this.taskId, this.id);
return frameData;
};
Job.prototype.frames.contextImage.implementation = async function (frameId) {
const result = await getContextImage(this.id, frameId);
return result;
};
Job.prototype.frames.search.implementation = async function (filters, frameFrom, frameTo) {
if (typeof filters !== 'object') {
throw new ArgumentError('Filters should be an object');
}
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError('The start frame is out of the job');
}
if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError('The stop frame is out of the job');
}
if (filters.notDeleted) {
return findNotDeletedFrame(this.id, frameFrom, frameTo, filters.offset || 1);
}
return null;
};
// TODO: Check filter for annotations
Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) {
if (!Array.isArray(filters)) {
throw new ArgumentError('Filters must be an array');
}
if (!Number.isInteger(frame)) {
throw new ArgumentError('The frame argument must be an integer');
}
if (frame < this.startFrame || frame > this.stopFrame) {
throw new ArgumentError(`Frame ${frame} does not exist in the job`);
}
const annotationsData = await getAnnotations(this, frame, allTracks, filters);
const deletedFrames = await getDeletedFrames('job', this.id);
if (frame in deletedFrames) {
return [];
}
return annotationsData;
};
Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) {
if (!Array.isArray(filters)) {
throw new ArgumentError('Filters must be an array');
}
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError('The start frame is out of the job');
}
if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError('The stop frame is out of the job');
}
const result = searchAnnotations(this, filters, frameFrom, frameTo);
return result;
};
Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError('The start frame is out of the job');
}
if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError('The stop frame is out of the job');
}
const result = searchEmptyFrame(this, frameFrom, frameTo);
return result;
};
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, startframe, endframe, delTrackKeyframesOnly,
) {
const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
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 (
format: string,
useDefaultLocation: boolean,
sourceStorage: Storage,
file: File | string,
options?: { convMaskToPoly?: boolean },
) {
const result = await importDataset(this, format, useDefaultLocation, sourceStorage, file, options);
return result;
};
Job.prototype.annotations.import.implementation = function (data) {
const result = importCollection(this, data);
return result;
};
Job.prototype.annotations.export.implementation = function () {
const result = exportCollection(this);
return result;
};
Job.prototype.annotations.exportDataset.implementation = async function (
format: string,
saveImages: boolean,
useDefaultSettings: boolean,
targetStorage: Storage,
customName?: string,
) {
const result = await exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
return result;
};
Job.prototype.actions.undo.implementation = async function (count) {
const result = await undoActions(this, count);
return result;
};
Job.prototype.actions.redo.implementation = async function (count) {
const result = await redoActions(this, count);
return result;
};
Job.prototype.actions.freeze.implementation = function (frozen) {
const result = freezeHistory(this, frozen);
return result;
};
Job.prototype.actions.clear.implementation = function () {
const result = clearActions(this);
return result;
};
Job.prototype.actions.get.implementation = function () {
const result = getActions(this);
return result;
};
Job.prototype.logger.log.implementation = async function (logType, payload, wait) {
const result = await loggerStorage.log(logType, { ...payload, task_id: this.taskId, job_id: this.id }, wait);
return result;
};
Job.prototype.predictor.status.implementation = async function () {
if (!Number.isInteger(this.projectId)) {
throw new DataError('The job must belong to a project to use the feature');
}
const result = await serverProxy.predictor.status(this.projectId);
return {
message: result.message,
progress: result.progress,
projectScore: result.score,
timeRemaining: result.time_remaining,
mediaAmount: result.media_amount,
annotationAmount: result.annotation_amount,
};
};
Job.prototype.predictor.predict.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`);
}
if (!Number.isInteger(this.projectId)) {
throw new DataError('The job must belong to a project to use the feature');
}
const result = await serverProxy.predictor.predict(this.taskId, frame);
return result;
};
Job.prototype.close.implementation = function closeTask() {
clearFrames(this.id);
clearCache(this);
return this;
};
return Job;
}
export function implementTask(Task) {
Task.prototype.close.implementation = function closeTask() {
for (const job of this.jobs) {
clearFrames(job.id);
clearCache(job);
}
clearCache(this);
return this;
};
Task.prototype.save.implementation = async function (onUpdate) {
// TODO: Add ability to change an owner and an assignee
if (typeof this.id !== 'undefined') {
// If the task has been already created, we update it
const taskData = this._updateTrigger.getUpdated(this, {
bugTracker: 'bug_tracker',
projectId: 'project_id',
assignee: 'assignee_id',
});
if (taskData.assignee_id) {
taskData.assignee_id = taskData.assignee_id.id;
}
if (taskData.labels) {
taskData.labels = this._internalData.labels;
taskData.labels = taskData.labels.map((el) => el.toJSON());
}
const data = await serverProxy.tasks.save(this.id, taskData);
this._updateTrigger.reset();
return new Task(data);
}
const taskSpec: any = {
name: this.name,
labels: this.labels.map((el) => el.toJSON()),
};
if (typeof this.bugTracker !== 'undefined') {
taskSpec.bug_tracker = this.bugTracker;
}
if (typeof this.segmentSize !== 'undefined') {
taskSpec.segment_size = this.segmentSize;
}
if (typeof this.overlap !== 'undefined') {
taskSpec.overlap = this.overlap;
}
if (typeof this.projectId !== 'undefined') {
taskSpec.project_id = this.projectId;
}
if (typeof this.subset !== 'undefined') {
taskSpec.subset = this.subset;
}
if (this.targetStorage) {
taskSpec.target_storage = this.targetStorage.toJSON();
}
if (this.sourceStorage) {
taskSpec.source_storage = this.sourceStorage.toJSON();
}
const taskDataSpec = {
client_files: this.clientFiles,
server_files: this.serverFiles,
remote_files: this.remoteFiles,
image_quality: this.imageQuality,
use_zip_chunks: this.useZipChunks,
use_cache: this.useCache,
sorting_method: this.sortingMethod,
};
if (typeof this.startFrame !== 'undefined') {
taskDataSpec.start_frame = this.startFrame;
}
if (typeof this.stopFrame !== 'undefined') {
taskDataSpec.stop_frame = this.stopFrame;
}
if (typeof this.frameFilter !== 'undefined') {
taskDataSpec.frame_filter = this.frameFilter;
}
if (typeof this.dataChunkSize !== 'undefined') {
taskDataSpec.chunk_size = this.dataChunkSize;
}
if (typeof this.copyData !== 'undefined') {
taskDataSpec.copy_data = this.copyData;
}
if (typeof this.cloudStorageId !== 'undefined') {
taskDataSpec.cloud_storage_id = this.cloudStorageId;
}
const task = await serverProxy.tasks.create(taskSpec, taskDataSpec, onUpdate);
return new Task(task);
};
Task.prototype.delete.implementation = async function () {
const result = await serverProxy.tasks.delete(this.id);
return result;
};
Task.prototype.backup.implementation = async function (
targetStorage: Storage,
useDefaultSettings: boolean,
fileName?: string,
) {
const result = await serverProxy.tasks.backup(this.id, targetStorage, useDefaultSettings, fileName);
return result;
};
Task.restore.implementation = async function (storage: Storage, file: File | string) {
// eslint-disable-next-line no-unsanitized/method
const result = await serverProxy.tasks.restore(storage, file);
return result;
};
Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
}
if (frame >= this.size) {
throw new ArgumentError(`The frame with number ${frame} is out of the task`);
}
const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0];
const result = await getFrame(
job.id,
this.dataChunkSize,
this.dataChunkType,
this.mode,
frame,
job.startFrame,
job.stopFrame,
isPlaying,
step,
);
return result;
};
Task.prototype.frames.ranges.implementation = async function () {
const rangesData = {
decoded: [],
buffered: [],
};
for (const job of this.jobs) {
const { decoded, buffered } = await getRanges(job.id);
rangesData.decoded.push(decoded);
rangesData.buffered.push(buffered);
}
return rangesData;
};
Task.prototype.frames.preview.implementation = async function () {
if (this.id === null) {
return '';
}
const frameData = await getPreview(this.id);
return frameData;
};
Task.prototype.frames.delete.implementation = async function (frame) {
if (!Number.isInteger(frame)) {
throw new Error(`Frame must be an integer. Got: "${frame}"`);
}
if (frame < 0 || frame >= this.size) {
throw new Error('The frame is out of the task');
}
const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0];
if (job) {
await deleteFrameWrapper.call(this, job.id, frame);
}
};
Task.prototype.frames.restore.implementation = async function (frame) {
if (!Number.isInteger(frame)) {
throw new Error(`Frame must be an integer. Got: "${frame}"`);
}
if (frame < 0 || frame >= this.size) {
throw new Error('The frame is out of the task');
}
const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0];
if (job) {
await restoreFrameWrapper.call(this, job.id, frame);
}
};
Task.prototype.frames.save.implementation = async function () {
return Promise.all(this.jobs.map((job) => patchMeta(job.id)));
};
Task.prototype.frames.search.implementation = async function (filters, frameFrom, frameTo) {
if (typeof filters !== 'object') {
throw new ArgumentError('Filters should be an object');
}
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < 0 || frameFrom > this.size) {
throw new ArgumentError('The start frame is out of the task');
}
if (frameTo < 0 || frameTo > this.size) {
throw new ArgumentError('The stop frame is out of the task');
}
const jobs = this.jobs.filter((_job) => (
(frameFrom >= _job.startFrame && frameFrom <= _job.stopFrame) ||
(frameTo >= _job.startFrame && frameTo <= _job.stopFrame) ||
(frameFrom < _job.startFrame && frameTo > _job.stopFrame)
));
if (filters.notDeleted) {
for (const job of jobs) {
const result = await findNotDeletedFrame(
job.id, Math.max(frameFrom, job.startFrame), Math.min(frameTo, job.stopFrame), 1,
);
if (result !== null) return result;
}
}
return null;
};
// TODO: Check filter for annotations
Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) {
if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError('The filters argument must be an array of strings');
}
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
}
if (frame >= this.size) {
throw new ArgumentError(`Frame ${frame} does not exist in the task`);
}
const result = await getAnnotations(this, frame, allTracks, filters);
const deletedFrames = await getDeletedFrames('task', this.id);
if (frame in deletedFrames) {
return [];
}
return result;
};
Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) {
if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
throw new ArgumentError('The filters argument must be an array of strings');
}
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < 0 || frameFrom >= this.size) {
throw new ArgumentError('The start frame is out of the task');
}
if (frameTo < 0 || frameTo >= this.size) {
throw new ArgumentError('The stop frame is out of the task');
}
const result = searchAnnotations(this, filters, frameFrom, frameTo);
return result;
};
Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError('The start and end frames both must be an integer');
}
if (frameFrom < 0 || frameFrom >= this.size) {
throw new ArgumentError('The start frame is out of the task');
}
if (frameTo < 0 || frameTo >= this.size) {
throw new ArgumentError('The stop frame is out of the task');
}
const result = searchEmptyFrame(this, frameFrom, frameTo);
return result;
};
Task.prototype.annotations.save.implementation = async function (onUpdate) {
const result = await saveAnnotations(this, onUpdate);
return result;
};
Task.prototype.annotations.merge.implementation = async function (objectStates) {
const result = await mergeAnnotations(this, objectStates);
return result;
};
Task.prototype.annotations.split.implementation = async function (objectState, frame) {
const result = await splitAnnotations(this, objectState, frame);
return result;
};
Task.prototype.annotations.group.implementation = async function (objectStates, reset) {
const result = await groupAnnotations(this, objectStates, reset);
return result;
};
Task.prototype.annotations.hasUnsavedChanges.implementation = function () {
const result = hasUnsavedChanges(this);
return result;
};
Task.prototype.annotations.clear.implementation = async function (reload) {
const result = await clearAnnotations(this, reload);
return result;
};
Task.prototype.annotations.select.implementation = function (frame, x, y) {
const result = selectObject(this, frame, x, y);
return result;
};
Task.prototype.annotations.statistics.implementation = function () {
const result = annotationsStatistics(this);
return result;
};
Task.prototype.annotations.put.implementation = function (objectStates) {
const result = putAnnotations(this, objectStates);
return result;
};
Task.prototype.annotations.upload.implementation = async function (
format: string,
useDefaultLocation: boolean,
sourceStorage: Storage,
file: File | string,
options?: { convMaskToPoly?: boolean },
) {
const result = await importDataset(this, format, useDefaultLocation, sourceStorage, file, options);
return result;
};
Task.prototype.annotations.import.implementation = function (data) {
const result = importCollection(this, data);
return result;
};
Task.prototype.annotations.export.implementation = function () {
const result = exportCollection(this);
return result;
};
Task.prototype.annotations.exportDataset.implementation = async function (
format: string,
saveImages: boolean,
useDefaultSettings: boolean,
targetStorage: Storage,
customName?: string,
) {
const result = await exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
return result;
};
Task.prototype.actions.undo.implementation = function (count) {
const result = undoActions(this, count);
return result;
};
Task.prototype.actions.redo.implementation = function (count) {
const result = redoActions(this, count);
return result;
};
Task.prototype.actions.freeze.implementation = function (frozen) {
const result = freezeHistory(this, frozen);
return result;
};
Task.prototype.actions.clear.implementation = function () {
const result = clearActions(this);
return result;
};
Task.prototype.actions.get.implementation = function () {
const result = getActions(this);
return result;
};
Task.prototype.logger.log.implementation = async function (logType, payload, wait) {
const result = await loggerStorage.log(logType, { ...payload, task_id: this.id }, wait);
return result;
};
Task.prototype.predictor.status.implementation = async function () {
if (!Number.isInteger(this.projectId)) {
throw new DataError('The task must belong to a project to use the feature');
}
const result = await serverProxy.predictor.status(this.projectId);
return {
message: result.message,
progress: result.progress,
projectScore: result.score,
timeRemaining: result.time_remaining,
mediaAmount: result.media_amount,
annotationAmount: result.annotation_amount,
};
};
Task.prototype.predictor.predict.implementation = async function (frame) {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
}
if (frame >= this.size) {
throw new ArgumentError(`The frame with number ${frame} is out of the task`);
}
if (!Number.isInteger(this.projectId)) {
throw new DataError('The task must belong to a project to use the feature');
}
const result = await serverProxy.predictor.predict(this.id, frame);
return result;
};
return Task;
}