diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts
index a4c773b6..92fba355 100644
--- a/cvat-core/src/api.ts
+++ b/cvat-core/src/api.ts
@@ -16,6 +16,7 @@ import Statistics from './statistics';
import Comment from './comment';
import Issue from './issue';
import { Job, Task } from './session';
+import { implementJob, implementTask } from './session-implementation';
import Project from './project';
import implementProject from './project-implementation';
import { Attribute, Label } from './labels';
@@ -918,8 +919,8 @@ function build() {
classes: {
User,
Project: implementProject(Project),
- Task,
- Job,
+ Task: implementTask(Task),
+ Job: implementJob(Job),
Log,
Attribute,
Label,
diff --git a/cvat-core/src/session-implementation.ts b/cvat-core/src/session-implementation.ts
new file mode 100644
index 00000000..578f7cf7
--- /dev/null
+++ b/cvat-core/src/session-implementation.ts
@@ -0,0 +1,826 @@
+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;
+}
diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts
index 8c7362ac..57a4a73d 100644
--- a/cvat-core/src/session.ts
+++ b/cvat-core/src/session.ts
@@ -3,32 +3,14 @@
//
// SPDX-License-Identifier: MIT
-import { StorageLocation } from './enums';
+import { JobStage, JobState, StorageLocation } from './enums';
import { Storage } from './storage';
-const PluginRegistry = require('./plugins').default;
-const loggerStorage = require('./logger-storage').default;
-const serverProxy = require('./server-proxy').default;
-const {
- getFrame,
- deleteFrame,
- restoreFrame,
- getRanges,
- getPreview,
- clear: clearFrames,
- findNotDeletedFrame,
- getContextImage,
- patchMeta,
- getDeletedFrames,
-} = require('./frames');
-const { ArgumentError, DataError } = require('./exceptions');
-const {
- JobStage, JobState, HistoryActions,
-} = require('./enums');
-const { Label } = require('./labels');
-const User = require('./user').default;
-const Issue = require('./issue').default;
-const { FieldUpdateTrigger, checkObjectType } = require('./common');
+import PluginRegistry from './plugins';
+import { ArgumentError } from './exceptions';
+import { Label } from './labels';
+import User from './user';
+import { FieldUpdateTrigger } from './common';
function buildDuplicatedAPI(prototype) {
Object.defineProperties(prototype, {
@@ -335,469 +317,467 @@ function buildDuplicatedAPI(prototype) {
* @virtual
*/
export class Session {
- constructor() {
- /**
- * An interaction with annotations
- * @namespace annotations
- * @memberof Session
- */
- /**
- * Upload annotations from a dump file
- * You need upload annotations from a server again after successful executing
- * @method upload
- * @memberof Session.annotations
- * @param {File} annotations - a file with annotations
- * @param {module:API.cvat.classes.Loader} loader - a loader
- * which will be used to upload
- * @instance
- * @async
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- */
- /**
- * Save all changes in annotations on a server
- * Objects which hadn't been saved on a server before,
- * get a serverID after saving. But received object states aren't updated.
- * So, after successful saving it's recommended to update them manually
- * (call the annotations.get() again)
- * @method save
- * @memberof Session.annotations
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @instance
- * @async
- * @param {function} [onUpdate] saving can be long.
- * This callback can be used to notify a user about current progress
- * Its argument is a text string
- */
- /**
- * Remove all annotations and optionally reinitialize it
- * @method clear
- * @memberof Session.annotations
- * @param {boolean} [reload = false] reset all changes and
- * reinitialize annotations by data from a server
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @instance
- * @async
- */
- /**
- * Collect short statistics about a task or a job.
- * @method statistics
- * @memberof Session.annotations
- * @returns {module:API.cvat.classes.Statistics} statistics object
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Create new objects from one-frame states
- * After successful adding you need to update object states on a frame
- * @method put
- * @memberof Session.annotations
- * @param {module:API.cvat.classes.ObjectState[]} data
- * @returns {number[]} identificators of added objects
- * 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
- */
- /**
- * Get annotations for a specific frame
- * Filter supports following operators:
- * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping.
- * Filter supports properties:
- * width, height, label, serverID, clientID, type, shape, occluded
- * All prop values are case-sensitive. CVAT uses json queries for search.
- * Examples:
- *
- * - label=="car" | label==["road sign"]
- * - width >= height
- * - attr["Attribute 1"] == attr["Attribute 2"]
- * - type=="track" & shape="rectangle"
- * - clientID == 50
- * - (label=="car" & attr["parked"]==true)
- * | (label=="pedestrian" & width > 150)
- * - (( label==["car \"mazda\""]) &
- * (attr["sunglass ( help ) es"]==true |
- * (width > 150 | height > 150 & (clientID == serverID)))))
- *
- * If you have double quotes in your query string,
- * please escape them using back slash: \"
- * @method get
- * @param {number} frame get objects from the frame
- * @param {boolean} allTracks show all tracks
- * even if they are outside and not keyframe
- * @param {any[]} [filters = []]
- * get only objects that satisfied to specific filters
- * @returns {module:API.cvat.classes.ObjectState[]}
- * @memberof Session.annotations
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Find a frame in the range [from, to]
- * that contains at least one object satisfied to a filter
- * @method search
- * @memberof Session.annotations
- * @param {ObjectFilter} [filter = []] filter
- * @param {number} from lower bound of a search
- * @param {number} to upper bound of a search
- * @returns {number|null} a frame that contains objects according to the filter
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Find the nearest empty frame without any annotations
- * @method searchEmpty
- * @memberof Session.annotations
- * @param {number} from lower bound of a search
- * @param {number} to upper bound of a search
- * @returns {number|null} a empty frame according boundaries
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * 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
- * objects which can be selected
- * @param {float} x horizontal coordinate
- * @param {float} y vertical coordinate
- * @returns {Object}
- * a pair of {state: ObjectState, distance: number} for selected object.
- * Pair values can be null if there aren't any sutisfied objects
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Method unites several shapes and tracks into the one
- * All shapes must be the same (rectangle, polygon, etc)
- * All labels must be the same
- * After successful merge you need to update object states on a frame
- * @method merge
- * @memberof Session.annotations
- * @param {module:API.cvat.classes.ObjectState[]} objectStates
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Method splits a track into two parts
- * (start frame: previous frame), (frame, last frame)
- * After successful split you need to update object states on a frame
- * @method split
- * @memberof Session.annotations
- * @param {module:API.cvat.classes.ObjectState} objectState
- * @param {number} frame
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Method creates a new group and put all passed objects into it
- * After successful split you need to update object states on a frame
- * @method group
- * @memberof Session.annotations
- * @param {module:API.cvat.classes.ObjectState[]} objectStates
- * @param {boolean} reset pass "true" to reset group value (set it to 0)
- * @returns {number} an ID of created group
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Method indicates if there are any changes in
- * annotations which haven't been saved on a server
- * This function cannot be wrapped with a plugin
- * @method hasUnsavedChanges
- * @memberof Session.annotations
- * @returns {boolean}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- */
- /**
- *
- * Import raw data in a collection
- * @method import
- * @memberof Session.annotations
- * @param {Object} data
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- *
- * Export a collection as a row data
- * @method export
- * @memberof Session.annotations
- * @returns {Object} data
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Export as a dataset.
- * Method builds a dataset in the specified format.
- * @method exportDataset
- * @memberof Session.annotations
- * @param {module:String} format - a format
- * @returns {string} An URL to the dataset file
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Namespace is used for an interaction with frames
- * @namespace frames
- * @memberof Session
- */
- /**
- * Get frame by its number
- * @method get
- * @memberof Session.frames
- * @param {number} frame number of frame which you want to get
- * @returns {module:API.cvat.classes.FrameData}
- * @instance
- * @async
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @throws {module:API.cvat.exceptions.DataError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- */
- /**
- * @typedef {Object} FrameSearchFilters
- * @property {boolean} notDeleted if true will search for non-deleted frames
- * @property {number} offset defines frame step during search
- /**
- * Find frame that match the condition
- * @method search
- * @memberof Session.frames
- * @param {FrameSearchFilters} filters filters to search frame for
- * @param {number} from lower bound of a search
- * @param {number} to upper bound of a search
- * @returns {number|null} a non-deleted frame according boundaries
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Delete frame from the job
- * @method delete
- * @memberof Session.frames
- * @param {number} frame number of frame which you want to delete
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Restore frame from the job
- * @method delete
- * @memberof Session.frames
- * @param {number} frame number of frame which you want to restore
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Save any changes in frames if some of them were deleted/restored
- * @method save
- * @memberof Session.frames
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * 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}
- */
- /**
- * Returns the ranges of cached frames
- * @method ranges
- * @memberof Session.frames
- * @returns {Array.}
- * @instance
- * @async
- */
- /**
- * Namespace is used for an interaction with logs
- * @namespace logger
- * @memberof Session
- */
- /**
- * Create a log and add it to a log collection
- * Durable logs will be added after "close" method is called for them
- * The fields "task_id" and "job_id" automatically added when add logs
- * through a task or a job
- * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
- * Payload of ignored logs are shallowly combined to previous logs of the same type
- * @method log
- * @memberof Session.logger
- * @param {module:API.cvat.enums.LogType | string} type - log type
- * @param {Object} [payload = {}] - any other data that will be appended to the log
- * @param {boolean} [wait = false] - specifies if log is durable
- * @returns {module:API.cvat.classes.Log}
- * @instance
- * @async
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- */
- /**
- * Namespace is used for an interaction with actions
- * @namespace actions
- * @memberof Session
- */
- /**
- * @typedef {Object} HistoryActions
- * @property {string[]} [undo] - array of possible actions to undo
- * @property {string[]} [redo] - array of possible actions to redo
- * @global
- */
- /**
- * Make undo
- * @method undo
- * @memberof Session.actions
- * @param {number} [count=1] number of actions to undo
- * @returns {number[]} Array of affected objects
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Make redo
- * @method redo
- * @memberof Session.actions
- * @param {number} [count=1] number of actions to redo
- * @returns {number[]} Array of affected objects
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Freeze history (do not save new actions)
- * @method freeze
- * @memberof Session.actions
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Remove all actions from history
- * @method clear
- * @memberof Session.actions
- * @throws {module:API.cvat.exceptions.PluginError}
- * @instance
- * @async
- */
- /**
- * Get actions
- * @method get
- * @memberof Session.actions
- * @returns {HistoryActions}
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @returns {Array.>}
- * array of pairs [action name, frame number]
- * @instance
- * @async
- */
- /**
- * Namespace is used for an interaction with events
- * @namespace events
- * @memberof Session
- */
- /**
- * Subscribe on an event
- * @method subscribe
- * @memberof Session.events
- * @param {module:API.cvat.enums.EventType} type - event type
- * @param {functions} callback - function which will be called on event
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * Unsubscribe from an event. If callback is not provided,
- * all callbacks will be removed from subscribers for the event
- * @method unsubscribe
- * @memberof Session.events
- * @param {module:API.cvat.enums.EventType} type - event type
- * @param {functions} [callback = null] - function which is called on event
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @instance
- * @async
- */
- /**
- * @typedef {Object} PredictorStatus
- * @property {string} message - message for a user to be displayed somewhere
- * @property {number} projectScore - model accuracy
- * @global
- */
- /**
- * Namespace is used for an interaction with events
- * @namespace predictor
- * @memberof Session
- */
- /**
- * Subscribe to updates of a ML model binded to the project
- * @method status
- * @memberof Session.predictor
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @returns {PredictorStatus}
- * @instance
- * @async
- */
- /**
- * Get predictions from a ML model binded to the project
- * @method predict
- * @memberof Session.predictor
- * @param {number} frame - number of frame to inference
- * @throws {module:API.cvat.exceptions.PluginError}
- * @throws {module:API.cvat.exceptions.ArgumentError}
- * @throws {module:API.cvat.exceptions.ServerError}
- * @throws {module:API.cvat.exceptions.DataError}
- * @returns {object[] | null} annotations
- * @instance
- * @async
- */
- }
+ /**
+ * An interaction with annotations
+ * @namespace annotations
+ * @memberof Session
+ */
+ /**
+ * Upload annotations from a dump file
+ * You need upload annotations from a server again after successful executing
+ * @method upload
+ * @memberof Session.annotations
+ * @param {File} annotations - a file with annotations
+ * @param {module:API.cvat.classes.Loader} loader - a loader
+ * which will be used to upload
+ * @instance
+ * @async
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ */
+ /**
+ * Save all changes in annotations on a server
+ * Objects which hadn't been saved on a server before,
+ * get a serverID after saving. But received object states aren't updated.
+ * So, after successful saving it's recommended to update them manually
+ * (call the annotations.get() again)
+ * @method save
+ * @memberof Session.annotations
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @instance
+ * @async
+ * @param {function} [onUpdate] saving can be long.
+ * This callback can be used to notify a user about current progress
+ * Its argument is a text string
+ */
+ /**
+ * Remove all annotations and optionally reinitialize it
+ * @method clear
+ * @memberof Session.annotations
+ * @param {boolean} [reload = false] reset all changes and
+ * reinitialize annotations by data from a server
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @instance
+ * @async
+ */
+ /**
+ * Collect short statistics about a task or a job.
+ * @method statistics
+ * @memberof Session.annotations
+ * @returns {module:API.cvat.classes.Statistics} statistics object
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Create new objects from one-frame states
+ * After successful adding you need to update object states on a frame
+ * @method put
+ * @memberof Session.annotations
+ * @param {module:API.cvat.classes.ObjectState[]} data
+ * @returns {number[]} identificators of added objects
+ * 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
+ */
+ /**
+ * Get annotations for a specific frame
+ * Filter supports following operators:
+ * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping.
+ * Filter supports properties:
+ * width, height, label, serverID, clientID, type, shape, occluded
+ * All prop values are case-sensitive. CVAT uses json queries for search.
+ * Examples:
+ *
+ * - label=="car" | label==["road sign"]
+ * - width >= height
+ * - attr["Attribute 1"] == attr["Attribute 2"]
+ * - type=="track" & shape="rectangle"
+ * - clientID == 50
+ * - (label=="car" & attr["parked"]==true)
+ * | (label=="pedestrian" & width > 150)
+ * - (( label==["car \"mazda\""]) &
+ * (attr["sunglass ( help ) es"]==true |
+ * (width > 150 | height > 150 & (clientID == serverID)))))
+ *
+ * If you have double quotes in your query string,
+ * please escape them using back slash: \"
+ * @method get
+ * @param {number} frame get objects from the frame
+ * @param {boolean} allTracks show all tracks
+ * even if they are outside and not keyframe
+ * @param {any[]} [filters = []]
+ * get only objects that satisfied to specific filters
+ * @returns {module:API.cvat.classes.ObjectState[]}
+ * @memberof Session.annotations
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Find a frame in the range [from, to]
+ * that contains at least one object satisfied to a filter
+ * @method search
+ * @memberof Session.annotations
+ * @param {ObjectFilter} [filter = []] filter
+ * @param {number} from lower bound of a search
+ * @param {number} to upper bound of a search
+ * @returns {number|null} a frame that contains objects according to the filter
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Find the nearest empty frame without any annotations
+ * @method searchEmpty
+ * @memberof Session.annotations
+ * @param {number} from lower bound of a search
+ * @param {number} to upper bound of a search
+ * @returns {number|null} a empty frame according boundaries
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * 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
+ * objects which can be selected
+ * @param {float} x horizontal coordinate
+ * @param {float} y vertical coordinate
+ * @returns {Object}
+ * a pair of {state: ObjectState, distance: number} for selected object.
+ * Pair values can be null if there aren't any sutisfied objects
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Method unites several shapes and tracks into the one
+ * All shapes must be the same (rectangle, polygon, etc)
+ * All labels must be the same
+ * After successful merge you need to update object states on a frame
+ * @method merge
+ * @memberof Session.annotations
+ * @param {module:API.cvat.classes.ObjectState[]} objectStates
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Method splits a track into two parts
+ * (start frame: previous frame), (frame, last frame)
+ * After successful split you need to update object states on a frame
+ * @method split
+ * @memberof Session.annotations
+ * @param {module:API.cvat.classes.ObjectState} objectState
+ * @param {number} frame
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Method creates a new group and put all passed objects into it
+ * After successful split you need to update object states on a frame
+ * @method group
+ * @memberof Session.annotations
+ * @param {module:API.cvat.classes.ObjectState[]} objectStates
+ * @param {boolean} reset pass "true" to reset group value (set it to 0)
+ * @returns {number} an ID of created group
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Method indicates if there are any changes in
+ * annotations which haven't been saved on a server
+ * This function cannot be wrapped with a plugin
+ * @method hasUnsavedChanges
+ * @memberof Session.annotations
+ * @returns {boolean}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ */
+ /**
+ *
+ * Import raw data in a collection
+ * @method import
+ * @memberof Session.annotations
+ * @param {Object} data
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ *
+ * Export a collection as a row data
+ * @method export
+ * @memberof Session.annotations
+ * @returns {Object} data
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Export as a dataset.
+ * Method builds a dataset in the specified format.
+ * @method exportDataset
+ * @memberof Session.annotations
+ * @param {module:String} format - a format
+ * @returns {string} An URL to the dataset file
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Namespace is used for an interaction with frames
+ * @namespace frames
+ * @memberof Session
+ */
+ /**
+ * Get frame by its number
+ * @method get
+ * @memberof Session.frames
+ * @param {number} frame number of frame which you want to get
+ * @returns {module:API.cvat.classes.FrameData}
+ * @instance
+ * @async
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @throws {module:API.cvat.exceptions.DataError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ */
+ /**
+ * @typedef {Object} FrameSearchFilters
+ * @property {boolean} notDeleted if true will search for non-deleted frames
+ * @property {number} offset defines frame step during search
+ /**
+ * Find frame that match the condition
+ * @method search
+ * @memberof Session.frames
+ * @param {FrameSearchFilters} filters filters to search frame for
+ * @param {number} from lower bound of a search
+ * @param {number} to upper bound of a search
+ * @returns {number|null} a non-deleted frame according boundaries
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Delete frame from the job
+ * @method delete
+ * @memberof Session.frames
+ * @param {number} frame number of frame which you want to delete
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Restore frame from the job
+ * @method delete
+ * @memberof Session.frames
+ * @param {number} frame number of frame which you want to restore
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Save any changes in frames if some of them were deleted/restored
+ * @method save
+ * @memberof Session.frames
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * 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}
+ */
+ /**
+ * Returns the ranges of cached frames
+ * @method ranges
+ * @memberof Session.frames
+ * @returns {Array.}
+ * @instance
+ * @async
+ */
+ /**
+ * Namespace is used for an interaction with logs
+ * @namespace logger
+ * @memberof Session
+ */
+ /**
+ * Create a log and add it to a log collection
+ * Durable logs will be added after "close" method is called for them
+ * The fields "task_id" and "job_id" automatically added when add logs
+ * through a task or a job
+ * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
+ * Payload of ignored logs are shallowly combined to previous logs of the same type
+ * @method log
+ * @memberof Session.logger
+ * @param {module:API.cvat.enums.LogType | string} type - log type
+ * @param {Object} [payload = {}] - any other data that will be appended to the log
+ * @param {boolean} [wait = false] - specifies if log is durable
+ * @returns {module:API.cvat.classes.Log}
+ * @instance
+ * @async
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ */
+ /**
+ * Namespace is used for an interaction with actions
+ * @namespace actions
+ * @memberof Session
+ */
+ /**
+ * @typedef {Object} HistoryActions
+ * @property {string[]} [undo] - array of possible actions to undo
+ * @property {string[]} [redo] - array of possible actions to redo
+ * @global
+ */
+ /**
+ * Make undo
+ * @method undo
+ * @memberof Session.actions
+ * @param {number} [count=1] number of actions to undo
+ * @returns {number[]} Array of affected objects
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Make redo
+ * @method redo
+ * @memberof Session.actions
+ * @param {number} [count=1] number of actions to redo
+ * @returns {number[]} Array of affected objects
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Freeze history (do not save new actions)
+ * @method freeze
+ * @memberof Session.actions
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Remove all actions from history
+ * @method clear
+ * @memberof Session.actions
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @instance
+ * @async
+ */
+ /**
+ * Get actions
+ * @method get
+ * @memberof Session.actions
+ * @returns {HistoryActions}
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @returns {Array.>}
+ * array of pairs [action name, frame number]
+ * @instance
+ * @async
+ */
+ /**
+ * Namespace is used for an interaction with events
+ * @namespace events
+ * @memberof Session
+ */
+ /**
+ * Subscribe on an event
+ * @method subscribe
+ * @memberof Session.events
+ * @param {module:API.cvat.enums.EventType} type - event type
+ * @param {functions} callback - function which will be called on event
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * Unsubscribe from an event. If callback is not provided,
+ * all callbacks will be removed from subscribers for the event
+ * @method unsubscribe
+ * @memberof Session.events
+ * @param {module:API.cvat.enums.EventType} type - event type
+ * @param {functions} [callback = null] - function which is called on event
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @instance
+ * @async
+ */
+ /**
+ * @typedef {Object} PredictorStatus
+ * @property {string} message - message for a user to be displayed somewhere
+ * @property {number} projectScore - model accuracy
+ * @global
+ */
+ /**
+ * Namespace is used for an interaction with events
+ * @namespace predictor
+ * @memberof Session
+ */
+ /**
+ * Subscribe to updates of a ML model binded to the project
+ * @method status
+ * @memberof Session.predictor
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @returns {PredictorStatus}
+ * @instance
+ * @async
+ */
+ /**
+ * Get predictions from a ML model binded to the project
+ * @method predict
+ * @memberof Session.predictor
+ * @param {number} frame - number of frame to inference
+ * @throws {module:API.cvat.exceptions.PluginError}
+ * @throws {module:API.cvat.exceptions.ArgumentError}
+ * @throws {module:API.cvat.exceptions.ServerError}
+ * @throws {module:API.cvat.exceptions.DataError}
+ * @returns {object[] | null} annotations
+ * @instance
+ * @async
+ */
}
/**
@@ -1948,809 +1928,3 @@ export class Task extends Session {
buildDuplicatedAPI(Job.prototype);
buildDuplicatedAPI(Task.prototype);
-
-(async () => {
- const annotations = await import('./annotations');
- const {
- getAnnotations, putAnnotations, saveAnnotations,
- hasUnsavedChanges, searchAnnotations, searchEmptyFrame,
- mergeAnnotations, splitAnnotations, groupAnnotations,
- clearAnnotations, selectObject, annotationsStatistics,
- importCollection, exportCollection, importDataset,
- exportDataset, undoActions, redoActions,
- freezeHistory, clearActions, getActions,
- clearCache, getHistory,
- } = annotations;
-
-
- 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;
- };
-
- // 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);
- }
-
- 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;
- };
-
- 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;
- };
-
-})();