From 1d00e515b9206c379ac4cf38179e6dbbf4fcb53f Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Sun, 1 Jan 2023 19:41:01 +0300 Subject: [PATCH] Fix ESLint errors in session.ts (#5540) The main problem is the circular dependency between `session.ts` and `annotations.ts`. To fix it, split the second half of `session.ts` into a separate file, like what is currently done with `project.ts` and `project-implementation.ts`. ### Motivation and context ### How has this been tested? Manual testing. ### Checklist - [x] I submit my changes into the `develop` branch - ~~[ ] I have added a description of my changes into [CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md) file~~ - ~~[ ] I have updated the [documentation]( https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation) accordingly~~ - ~~[ ] I have added tests to cover my changes~~ - ~~[ ] I have linked related issues ([read github docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))~~ - ~~[ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning))~~ ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. Co-authored-by: Nikita Manovich --- cvat-core/src/api.ts | 5 +- cvat-core/src/session-implementation.ts | 826 +++++++++++ cvat-core/src/session.ts | 1760 ++++++----------------- 3 files changed, 1296 insertions(+), 1295 deletions(-) create mode 100644 cvat-core/src/session-implementation.ts 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: - * - * 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; - }; - -})();