diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts index 2e18e653..6f0d4b14 100644 --- a/cvat-core/src/api-implementation.ts +++ b/cvat-core/src/api-implementation.ts @@ -3,329 +3,325 @@ // // SPDX-License-Identifier: MIT -const config = require('./config').default; - -(() => { - const PluginRegistry = require('./plugins').default; - const serverProxy = require('./server-proxy').default; - const lambdaManager = require('./lambda-manager').default; - const { - isBoolean, - isInteger, - isString, - checkFilter, - checkExclusiveFields, - checkObjectType, - } = require('./common'); - - const User = require('./user').default; - const { AnnotationFormats } = require('./annotation-formats'); - const { ArgumentError } = require('./exceptions'); - const { Task, Job } = require('./session'); - const Project = require('./project').default; - const CloudStorage = require('./cloud-storage').default; - const Organization = require('./organization').default; - const Webhook = require('./webhook').default; - - function implementAPI(cvat) { - cvat.plugins.list.implementation = PluginRegistry.list; - cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); - - cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager); - cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager); - cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager); - cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); - cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); - cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); - - cvat.server.about.implementation = async () => { - const result = await serverProxy.server.about(); - return result; - }; - - cvat.server.share.implementation = async (directory) => { - const result = await serverProxy.server.share(directory); - return result; - }; - - cvat.server.formats.implementation = async () => { - const result = await serverProxy.server.formats(); - return new AnnotationFormats(result); - }; - - cvat.server.userAgreements.implementation = async () => { - const result = await serverProxy.server.userAgreements(); - return result; - }; - - cvat.server.register.implementation = async ( +import config from './config'; + +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import lambdaManager from './lambda-manager'; +import { + isBoolean, + isInteger, + isString, + checkFilter, + checkExclusiveFields, + checkObjectType, +} from './common'; + +import User from './user'; +import { AnnotationFormats } from './annotation-formats'; +import { ArgumentError } from './exceptions'; +import { Task, Job } from './session'; +import Project from './project'; +import CloudStorage from './cloud-storage'; +import Organization from './organization'; +import Webhook from './webhook'; + +export default function implementAPI(cvat) { + cvat.plugins.list.implementation = PluginRegistry.list; + cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); + + cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager); + cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager); + cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager); + cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); + cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); + cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); + + cvat.server.about.implementation = async () => { + const result = await serverProxy.server.about(); + return result; + }; + + cvat.server.share.implementation = async (directory) => { + const result = await serverProxy.server.share(directory); + return result; + }; + + cvat.server.formats.implementation = async () => { + const result = await serverProxy.server.formats(); + return new AnnotationFormats(result); + }; + + cvat.server.userAgreements.implementation = async () => { + const result = await serverProxy.server.userAgreements(); + return result; + }; + + cvat.server.register.implementation = async ( + username, + firstName, + lastName, + email, + password, + userConfirmations, + ) => { + const user = await serverProxy.server.register( username, firstName, lastName, email, password, userConfirmations, - ) => { - const user = await serverProxy.server.register( - username, - firstName, - lastName, - email, - password, - userConfirmations, - ); - - return new User(user); - }; - - cvat.server.login.implementation = async (username, password) => { - await serverProxy.server.login(username, password); - }; - - cvat.server.logout.implementation = async () => { - await serverProxy.server.logout(); - }; - - cvat.server.advancedAuthentication.implementation = async () => { - const result = await serverProxy.server.advancedAuthentication(); - return result; - }; - - cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => { - await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); - }; - - cvat.server.requestPasswordReset.implementation = async (email) => { - await serverProxy.server.requestPasswordReset(email); - }; - - cvat.server.resetPassword.implementation = async (newPassword1, newPassword2, uid, token) => { - await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token); - }; - - cvat.server.authorized.implementation = async () => { - const result = await serverProxy.server.authorized(); - return result; - }; - - cvat.server.request.implementation = async (url, data) => { - const result = await serverProxy.server.request(url, data); - return result; - }; - - cvat.server.installedApps.implementation = async () => { - const result = await serverProxy.server.installedApps(); - return result; - }; - - cvat.users.get.implementation = async (filter) => { - checkFilter(filter, { - id: isInteger, - is_active: isBoolean, - self: isBoolean, - search: isString, - limit: isInteger, - }); - - let users = null; - if ('self' in filter && filter.self) { - users = await serverProxy.users.self(); - users = [users]; - } else { - const searchParams = {}; - for (const key in filter) { - if (filter[key] && key !== 'self') { - searchParams[key] = filter[key]; - } + ); + + return new User(user); + }; + + cvat.server.login.implementation = async (username, password) => { + await serverProxy.server.login(username, password); + }; + + cvat.server.logout.implementation = async () => { + await serverProxy.server.logout(); + }; + + cvat.server.advancedAuthentication.implementation = async () => { + const result = await serverProxy.server.advancedAuthentication(); + return result; + }; + + cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => { + await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); + }; + + cvat.server.requestPasswordReset.implementation = async (email) => { + await serverProxy.server.requestPasswordReset(email); + }; + + cvat.server.resetPassword.implementation = async (newPassword1, newPassword2, uid, token) => { + await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token); + }; + + cvat.server.authorized.implementation = async () => { + const result = await serverProxy.server.authorized(); + return result; + }; + + cvat.server.request.implementation = async (url, data) => { + const result = await serverProxy.server.request(url, data); + return result; + }; + + cvat.server.installedApps.implementation = async () => { + const result = await serverProxy.server.installedApps(); + return result; + }; + + cvat.users.get.implementation = async (filter) => { + checkFilter(filter, { + id: isInteger, + is_active: isBoolean, + self: isBoolean, + search: isString, + limit: isInteger, + }); + + let users = null; + if ('self' in filter && filter.self) { + users = await serverProxy.users.self(); + users = [users]; + } else { + const searchParams = {}; + for (const key in filter) { + if (filter[key] && key !== 'self') { + searchParams[key] = filter[key]; } - users = await serverProxy.users.get(searchParams); } - - users = users.map((user) => new User(user)); - return users; - }; - - cvat.jobs.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - filter: isString, - sort: isString, - search: isString, - taskID: isInteger, - jobID: isInteger, - }); - - if ('taskID' in filter && 'jobID' in filter) { - throw new ArgumentError('Filter fields "taskID" and "jobID" are not permitted to be used at the same time'); + users = await serverProxy.users.get(searchParams); + } + + users = users.map((user) => new User(user)); + return users; + }; + + cvat.jobs.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + filter: isString, + sort: isString, + search: isString, + taskID: isInteger, + jobID: isInteger, + }); + + if ('taskID' in filter && 'jobID' in filter) { + throw new ArgumentError('Filter fields "taskID" and "jobID" are not permitted to be used at the same time'); + } + + if ('taskID' in filter) { + const [task] = await serverProxy.tasks.get({ id: filter.taskID }); + if (task) { + return new Task(task).jobs; } - if ('taskID' in filter) { - const [task] = await serverProxy.tasks.get({ id: filter.taskID }); - if (task) { - return new Task(task).jobs; - } + return []; + } - return []; + if ('jobID' in filter) { + const job = await serverProxy.jobs.get({ id: filter.jobID }); + if (job) { + return [new Job(job)]; } + } - if ('jobID' in filter) { - const job = await serverProxy.jobs.get({ id: filter.jobID }); - if (job) { - return [new Job(job)]; - } - } - - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'sort', 'search', 'filter'].includes(key)) { - searchParams[key] = filter[key]; - } + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'sort', 'search', 'filter'].includes(key)) { + searchParams[key] = filter[key]; } - - const jobsData = await serverProxy.jobs.get(searchParams); - const jobs = jobsData.results.map((jobData) => new Job(jobData)); - jobs.count = jobsData.count; - return jobs; - }; - - cvat.tasks.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - projectId: isInteger, - id: isInteger, - sort: isString, - search: isString, - filter: isString, - ordering: isString, - }); - - checkExclusiveFields(filter, ['id', 'projectId'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'id', 'sort', 'search', 'filter', 'ordering'].includes(key)) { - searchParams[key] = filter[key]; - } + } + + const jobsData = await serverProxy.jobs.get(searchParams); + const jobs = jobsData.results.map((jobData) => new Job(jobData)); + jobs.count = jobsData.count; + return jobs; + }; + + cvat.tasks.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + projectId: isInteger, + id: isInteger, + sort: isString, + search: isString, + filter: isString, + ordering: isString, + }); + + checkExclusiveFields(filter, ['id', 'projectId'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'id', 'sort', 'search', 'filter', 'ordering'].includes(key)) { + searchParams[key] = filter[key]; } + } - let tasksData = null; - if (filter.projectId) { - if (searchParams.filter) { - const parsed = JSON.parse(searchParams.filter); - searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); - } else { - searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); - } + let tasksData = null; + if (filter.projectId) { + if (searchParams.filter) { + const parsed = JSON.parse(searchParams.filter); + searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); + } else { + searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); } - - tasksData = await serverProxy.tasks.get(searchParams); - const tasks = tasksData.map((task) => new Task(task)); - tasks.count = tasksData.count; - return tasks; - }; - - cvat.projects.get.implementation = async (filter) => { - checkFilter(filter, { - id: isInteger, - page: isInteger, - search: isString, - sort: isString, - filter: isString, - }); - - checkExclusiveFields(filter, ['id'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['id', 'page', 'search', 'sort', 'page', 'filter'].includes(key)) { - searchParams[key] = filter[key]; - } + } + + tasksData = await serverProxy.tasks.get(searchParams); + const tasks = tasksData.map((task) => new Task(task)); + tasks.count = tasksData.count; + return tasks; + }; + + cvat.projects.get.implementation = async (filter) => { + checkFilter(filter, { + id: isInteger, + page: isInteger, + search: isString, + sort: isString, + filter: isString, + }); + + checkExclusiveFields(filter, ['id'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['id', 'page', 'search', 'sort', 'page', 'filter'].includes(key)) { + searchParams[key] = filter[key]; } - - const projectsData = await serverProxy.projects.get(searchParams); - const projects = projectsData.map((project) => { - project.task_ids = project.tasks; - return project; - }).map((project) => new Project(project)); - - projects.count = projectsData.count; - - return projects; - }; - - cvat.projects.searchNames - .implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit); - - cvat.cloudStorages.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - filter: isString, - sort: isString, - id: isInteger, - search: isString, - }); - - checkExclusiveFields(filter, ['id', 'search'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'filter', 'sort', 'id', 'search'].includes(key)) { - searchParams[key] = filter[key]; - } + } + + const projectsData = await serverProxy.projects.get(searchParams); + const projects = projectsData.map((project) => { + project.task_ids = project.tasks; + return project; + }).map((project) => new Project(project)); + + projects.count = projectsData.count; + + return projects; + }; + + cvat.projects.searchNames + .implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit); + + cvat.cloudStorages.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + filter: isString, + sort: isString, + id: isInteger, + search: isString, + }); + + checkExclusiveFields(filter, ['id', 'search'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'filter', 'sort', 'id', 'search'].includes(key)) { + searchParams[key] = filter[key]; } - const cloudStoragesData = await serverProxy.cloudStorages.get(searchParams); - const cloudStorages = cloudStoragesData.map((cloudStorage) => new CloudStorage(cloudStorage)); - cloudStorages.count = cloudStoragesData.count; - return cloudStorages; - }; - - cvat.organizations.get.implementation = async () => { - const organizationsData = await serverProxy.organizations.get(); - const organizations = organizationsData.map((organizationData) => new Organization(organizationData)); - return organizations; - }; - - cvat.organizations.activate.implementation = (organization) => { - checkObjectType('organization', organization, null, Organization); - config.organizationID = organization.slug; - }; - - cvat.organizations.deactivate.implementation = async () => { - config.organizationID = null; - }; - - cvat.webhooks.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - id: isInteger, - projectId: isInteger, - filter: isString, - search: isString, - sort: isString, - }); - - checkExclusiveFields(filter, ['id', 'projectId'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'id', 'filter', 'search', 'sort'].includes(key)) { - searchParams[key] = filter[key]; - } + } + const cloudStoragesData = await serverProxy.cloudStorages.get(searchParams); + const cloudStorages = cloudStoragesData.map((cloudStorage) => new CloudStorage(cloudStorage)); + cloudStorages.count = cloudStoragesData.count; + return cloudStorages; + }; + + cvat.organizations.get.implementation = async () => { + const organizationsData = await serverProxy.organizations.get(); + const organizations = organizationsData.map((organizationData) => new Organization(organizationData)); + return organizations; + }; + + cvat.organizations.activate.implementation = (organization) => { + checkObjectType('organization', organization, null, Organization); + config.organizationID = organization.slug; + }; + + cvat.organizations.deactivate.implementation = async () => { + config.organizationID = null; + }; + + cvat.webhooks.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + id: isInteger, + projectId: isInteger, + filter: isString, + search: isString, + sort: isString, + }); + + checkExclusiveFields(filter, ['id', 'projectId'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'id', 'filter', 'search', 'sort'].includes(key)) { + searchParams[key] = filter[key]; } + } - if (filter.projectId) { - if (searchParams.filter) { - const parsed = JSON.parse(searchParams.filter); - searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); - } else { - searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); - } + if (filter.projectId) { + if (searchParams.filter) { + const parsed = JSON.parse(searchParams.filter); + searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); + } else { + searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); } + } - const webhooksData = await serverProxy.webhooks.get(searchParams); - const webhooks = webhooksData.map((webhookData) => new Webhook(webhookData)); - webhooks.count = webhooksData.count; - return webhooks; - }; - - return cvat; - } + const webhooksData = await serverProxy.webhooks.get(searchParams); + const webhooks = webhooksData.map((webhookData) => new Webhook(webhookData)); + webhooks.count = webhooksData.count; + return webhooks; + }; - module.exports = implementAPI; -})(); + return cvat; +} diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index a12d0371..95756786 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -8,34 +8,36 @@ * @module API */ -function build() { - const PluginRegistry = require('./plugins').default; - const loggerStorage = require('./logger-storage').default; - const { Log } = require('./log'); - const ObjectState = require('./object-state').default; - const Statistics = require('./statistics').default; - const Comment = require('./comment').default; - const Issue = require('./issue').default; - const { Job, Task } = require('./session'); - const Project = require('./project').default; - const implementProject = require('./project-implementation').default; - const { Attribute, Label } = require('./labels'); - const MLModel = require('./ml-model').default; - const { FrameData } = require('./frames'); - const CloudStorage = require('./cloud-storage').default; - const Organization = require('./organization').default; - const Webhook = require('./webhook').default; +import PluginRegistry from './plugins'; +import loggerStorage from './logger-storage'; +import { Log } from './log'; +import ObjectState from './object-state'; +import Statistics from './statistics'; +import Comment from './comment'; +import Issue from './issue'; +import { Job, Task } from './session'; +import Project from './project'; +import implementProject from './project-implementation'; +import { Attribute, Label } from './labels'; +import MLModel from './ml-model'; +import { FrameData } from './frames'; +import CloudStorage from './cloud-storage'; +import Organization from './organization'; +import Webhook from './webhook'; + +import * as enums from './enums'; - const enums = require('./enums'); +import { + Exception, ArgumentError, DataError, ScriptingError, PluginError, ServerError, +} from './exceptions'; - const { - Exception, ArgumentError, DataError, ScriptingError, PluginError, ServerError, - } = require('./exceptions'); +import User from './user'; +import pjson from '../package.json'; +import config from './config'; - const User = require('./user').default; - const pjson = require('../package.json'); - const config = require('./config').default; +import implementAPI from './api-implementation'; +function build() { /** * API entrypoint * @namespace cvat @@ -925,9 +927,8 @@ function build() { cvat.cloudStorages = Object.freeze(cvat.cloudStorages); cvat.organizations = Object.freeze(cvat.organizations); - const implementAPI = require('./api-implementation'); const implemented = Object.freeze(implementAPI(cvat)); return implemented; } -module.exports = build(); +export default build(); diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 0a4f6954..6e0d3961 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -3,847 +3,831 @@ // // SPDX-License-Identifier: MIT -(() => { - const cvatData = require('cvat-data'); - const PluginRegistry = require('./plugins').default; - const serverProxy = require('./server-proxy').default; - const { isBrowser, isNode } = require('browser-or-node'); - const { Exception, ArgumentError, DataError } = require('./exceptions'); - - // This is the frames storage - const frameDataCache = {}; +import * as cvatData from 'cvat-data'; +import { isBrowser, isNode } from 'browser-or-node'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import { Exception, ArgumentError, DataError } from './exceptions'; + +// This is the frames storage +const frameDataCache = {}; + +/** + * Class provides meta information about specific frame and frame itself + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class FrameData { + constructor({ + width, + height, + name, + jobID, + frameNumber, + startFrame, + stopFrame, + decodeForward, + deleted, + has_related_context: hasRelatedContext, + }) { + Object.defineProperties( + this, + Object.freeze({ + /** + * @name filename + * @type {string} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + filename: { + value: name, + writable: false, + }, + /** + * @name width + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + width: { + value: width, + writable: false, + }, + /** + * @name height + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + height: { + value: height, + writable: false, + }, + /** + * @name jid + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + jid: { + value: jobID, + writable: false, + }, + /** + * @name number + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + number: { + value: frameNumber, + writable: false, + }, + /** + * True if some context images are associated with this frame + * @name hasRelatedContext + * @type {boolean} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + hasRelatedContext: { + value: hasRelatedContext, + writable: false, + }, + /** + * Start frame of the frame in the job + * @name startFrame + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + startFrame: { + value: startFrame, + writable: false, + }, + /** + * Stop frame of the frame in the job + * @name stopFrame + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + stopFrame: { + value: stopFrame, + writable: false, + }, + decodeForward: { + value: decodeForward, + writable: false, + }, + /** + * True if frame was deleted from the task data + * @name deleted + * @type {boolean} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + deleted: { + value: deleted, + writable: false, + }, + }), + ); + } /** - * Class provides meta information about specific frame and frame itself - * @memberof module:API.cvat.classes - * @hideconstructor + * Method returns URL encoded image which can be placed in the img tag + * @method data + * @returns {string} + * @memberof module:API.cvat.classes.FrameData + * @instance + * @async + * @param {function} [onServerRequest = () => {}] + * callback which will be called if data absences local + * @throws {module:API.cvat.exception.ServerError} + * @throws {module:API.cvat.exception.PluginError} */ - class FrameData { - constructor({ - width, - height, - name, - jobID, - frameNumber, - startFrame, - stopFrame, - decodeForward, - deleted, - has_related_context: hasRelatedContext, - }) { - Object.defineProperties( - this, - Object.freeze({ - /** - * @name filename - * @type {string} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - filename: { - value: name, - writable: false, - }, - /** - * @name width - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - width: { - value: width, - writable: false, - }, - /** - * @name height - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - height: { - value: height, - writable: false, - }, - /** - * @name jid - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - jid: { - value: jobID, - writable: false, - }, - /** - * @name number - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - number: { - value: frameNumber, - writable: false, - }, - /** - * True if some context images are associated with this frame - * @name hasRelatedContext - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - hasRelatedContext: { - value: hasRelatedContext, - writable: false, - }, - /** - * Start frame of the frame in the job - * @name startFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - startFrame: { - value: startFrame, - writable: false, - }, - /** - * Stop frame of the frame in the job - * @name stopFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - stopFrame: { - value: stopFrame, - writable: false, - }, - decodeForward: { - value: decodeForward, - writable: false, - }, - /** - * True if frame was deleted from the task data - * @name deleted - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - deleted: { - value: deleted, - writable: false, - }, - }), - ); - } - - /** - * Method returns URL encoded image which can be placed in the img tag - * @method data - * @returns {string} - * @memberof module:API.cvat.classes.FrameData - * @instance - * @async - * @param {function} [onServerRequest = () => {}] - * callback which will be called if data absences local - * @throws {module:API.cvat.exception.ServerError} - * @throws {module:API.cvat.exception.PluginError} - */ - async data(onServerRequest = () => {}) { - const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); - return result; - } - - get imageData() { - return this._data.imageData; - } + async data(onServerRequest = () => {}) { + const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); + return result; + } - set imageData(imageData) { - this._data.imageData = imageData; - } + get imageData() { + return this._data.imageData; } - FrameData.prototype.data.implementation = async function (onServerRequest) { - return new Promise((resolve, reject) => { - const resolveWrapper = (data) => { - this._data = { - imageData: data, - renderWidth: this.width, - renderHeight: this.height, - }; - return resolve(this._data); + set imageData(imageData) { + this._data.imageData = imageData; + } +} + +FrameData.prototype.data.implementation = async function (onServerRequest) { + return new Promise((resolve, reject) => { + const resolveWrapper = (data) => { + this._data = { + imageData: data, + renderWidth: this.width, + renderHeight: this.height, }; + return resolve(this._data); + }; - if (this._data) { - resolve(this._data); - return; - } + if (this._data) { + resolve(this._data); + return; + } - const { provider } = frameDataCache[this.jid]; - const { chunkSize } = frameDataCache[this.jid]; - const start = parseInt(this.number / chunkSize, 10) * chunkSize; - const stop = Math.min(this.stopFrame, (parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1); - const chunkNumber = Math.floor(this.number / chunkSize); - - const onDecodeAll = async (frameNumber) => { - if ( - frameDataCache[this.jid].activeChunkRequest && - chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber - ) { - const callbackArray = frameDataCache[this.jid].activeChunkRequest.callbacks; - for (let i = callbackArray.length - 1; i >= 0; --i) { - if (callbackArray[i].frameNumber === frameNumber) { - const callback = callbackArray[i]; - callbackArray.splice(i, 1); - callback.resolve(await provider.frame(callback.frameNumber)); - } - } - if (callbackArray.length === 0) { - frameDataCache[this.jid].activeChunkRequest = null; + const { provider } = frameDataCache[this.jid]; + const { chunkSize } = frameDataCache[this.jid]; + const start = parseInt(this.number / chunkSize, 10) * chunkSize; + const stop = Math.min(this.stopFrame, (parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1); + const chunkNumber = Math.floor(this.number / chunkSize); + + const onDecodeAll = async (frameNumber) => { + if ( + frameDataCache[this.jid].activeChunkRequest && + chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber + ) { + const callbackArray = frameDataCache[this.jid].activeChunkRequest.callbacks; + for (let i = callbackArray.length - 1; i >= 0; --i) { + if (callbackArray[i].frameNumber === frameNumber) { + const callback = callbackArray[i]; + callbackArray.splice(i, 1); + callback.resolve(await provider.frame(callback.frameNumber)); } } - }; - - const rejectRequestAll = () => { - if ( - frameDataCache[this.jid].activeChunkRequest && - chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber - ) { - for (const r of frameDataCache[this.jid].activeChunkRequest.callbacks) { - r.reject(r.frameNumber); - } + if (callbackArray.length === 0) { frameDataCache[this.jid].activeChunkRequest = null; } - }; + } + }; - const makeActiveRequest = () => { - const taskDataCache = frameDataCache[this.jid]; - const activeChunk = taskDataCache.activeChunkRequest; - activeChunk.request = serverProxy.frames - .getData(null, this.jid, activeChunk.chunkNumber) - .then((chunk) => { - frameDataCache[this.jid].activeChunkRequest.completed = true; - if (!taskDataCache.nextChunkRequest) { - provider.requestDecodeBlock( - chunk, - taskDataCache.activeChunkRequest.start, - taskDataCache.activeChunkRequest.stop, - taskDataCache.activeChunkRequest.onDecodeAll, - taskDataCache.activeChunkRequest.rejectRequestAll, - ); - } - }) - .catch((exception) => { - if (exception instanceof Exception) { - reject(exception); - } else { - reject(new Exception(exception.message)); - } - }) - .finally(() => { - if (taskDataCache.nextChunkRequest) { - if (taskDataCache.activeChunkRequest) { - for (const r of taskDataCache.activeChunkRequest.callbacks) { - r.reject(r.frameNumber); - } + const rejectRequestAll = () => { + if ( + frameDataCache[this.jid].activeChunkRequest && + chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber + ) { + for (const r of frameDataCache[this.jid].activeChunkRequest.callbacks) { + r.reject(r.frameNumber); + } + frameDataCache[this.jid].activeChunkRequest = null; + } + }; + + const makeActiveRequest = () => { + const taskDataCache = frameDataCache[this.jid]; + const activeChunk = taskDataCache.activeChunkRequest; + activeChunk.request = serverProxy.frames + .getData(null, this.jid, activeChunk.chunkNumber) + .then((chunk) => { + frameDataCache[this.jid].activeChunkRequest.completed = true; + if (!taskDataCache.nextChunkRequest) { + provider.requestDecodeBlock( + chunk, + taskDataCache.activeChunkRequest.start, + taskDataCache.activeChunkRequest.stop, + taskDataCache.activeChunkRequest.onDecodeAll, + taskDataCache.activeChunkRequest.rejectRequestAll, + ); + } + }) + .catch((exception) => { + if (exception instanceof Exception) { + reject(exception); + } else { + reject(new Exception(exception.message)); + } + }) + .finally(() => { + if (taskDataCache.nextChunkRequest) { + if (taskDataCache.activeChunkRequest) { + for (const r of taskDataCache.activeChunkRequest.callbacks) { + r.reject(r.frameNumber); } - taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest; - taskDataCache.nextChunkRequest = null; - makeActiveRequest(); } - }); - }; + taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest; + taskDataCache.nextChunkRequest = null; + makeActiveRequest(); + } + }); + }; - if (isNode) { - resolve('Dummy data'); - } else if (isBrowser) { - provider - .frame(this.number) - .then((frame) => { - if (frame === null) { - onServerRequest(); - const activeRequest = frameDataCache[this.jid].activeChunkRequest; - if (!provider.isChunkCached(start, stop)) { - if ( - !activeRequest || - (activeRequest && - activeRequest.completed && - activeRequest.chunkNumber !== chunkNumber) - ) { - if (activeRequest && activeRequest.rejectRequestAll) { - activeRequest.rejectRequestAll(); - } - frameDataCache[this.jid].activeChunkRequest = { - request: null, - chunkNumber, - start, - stop, - onDecodeAll, - rejectRequestAll, - completed: false, - callbacks: [ - { - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }, - ], - }; - makeActiveRequest(); - } else if (activeRequest.chunkNumber === chunkNumber) { - if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) { - activeRequest.onDecodeAll = onDecodeAll; - activeRequest.rejectRequestAll = rejectRequestAll; - } - activeRequest.callbacks.push({ - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }); - } else { - if (frameDataCache[this.jid].nextChunkRequest) { - const { callbacks } = frameDataCache[this.jid].nextChunkRequest; - for (const r of callbacks) { - r.reject(r.frameNumber); - } - } - frameDataCache[this.jid].nextChunkRequest = { - request: null, - chunkNumber, - start, - stop, - onDecodeAll, - rejectRequestAll, - completed: false, - callbacks: [ - { - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }, - ], - }; + if (isNode) { + resolve('Dummy data'); + } else if (isBrowser) { + provider + .frame(this.number) + .then((frame) => { + if (frame === null) { + onServerRequest(); + const activeRequest = frameDataCache[this.jid].activeChunkRequest; + if (!provider.isChunkCached(start, stop)) { + if ( + !activeRequest || + (activeRequest && + activeRequest.completed && + activeRequest.chunkNumber !== chunkNumber) + ) { + if (activeRequest && activeRequest.rejectRequestAll) { + activeRequest.rejectRequestAll(); + } + frameDataCache[this.jid].activeChunkRequest = { + request: null, + chunkNumber, + start, + stop, + onDecodeAll, + rejectRequestAll, + completed: false, + callbacks: [ + { + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }, + ], + }; + makeActiveRequest(); + } else if (activeRequest.chunkNumber === chunkNumber) { + if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) { + activeRequest.onDecodeAll = onDecodeAll; + activeRequest.rejectRequestAll = rejectRequestAll; } - } else { activeRequest.callbacks.push({ resolve: resolveWrapper, reject, frameNumber: this.number, }); - provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll); + } else { + if (frameDataCache[this.jid].nextChunkRequest) { + const { callbacks } = frameDataCache[this.jid].nextChunkRequest; + for (const r of callbacks) { + r.reject(r.frameNumber); + } + } + frameDataCache[this.jid].nextChunkRequest = { + request: null, + chunkNumber, + start, + stop, + onDecodeAll, + rejectRequestAll, + completed: false, + callbacks: [ + { + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }, + ], + }; } } else { - if ( - this.number % chunkSize > chunkSize / 4 && - provider.decodedBlocksCacheSize > 1 && - this.decodeForward && - !provider.isNextChunkExists(this.number) - ) { - const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; - if (nextChunkNumber * chunkSize < this.stopFrame) { - provider.setReadyToLoading(nextChunkNumber); - const nextStart = nextChunkNumber * chunkSize; - const nextStop = Math.min(this.stopFrame, (nextChunkNumber + 1) * chunkSize - 1); - if (!provider.isChunkCached(nextStart, nextStop)) { - if (!frameDataCache[this.jid].activeChunkRequest) { - frameDataCache[this.jid].activeChunkRequest = { - request: null, - chunkNumber: nextChunkNumber, - start: nextStart, - stop: nextStop, - onDecodeAll: null, - rejectRequestAll: null, - completed: false, - callbacks: [], - }; - makeActiveRequest(); - } - } else { - provider.requestDecodeBlock(null, nextStart, nextStop, null, null); + activeRequest.callbacks.push({ + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }); + provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll); + } + } else { + if ( + this.number % chunkSize > chunkSize / 4 && + provider.decodedBlocksCacheSize > 1 && + this.decodeForward && + !provider.isNextChunkExists(this.number) + ) { + const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; + if (nextChunkNumber * chunkSize < this.stopFrame) { + provider.setReadyToLoading(nextChunkNumber); + const nextStart = nextChunkNumber * chunkSize; + const nextStop = Math.min(this.stopFrame, (nextChunkNumber + 1) * chunkSize - 1); + if (!provider.isChunkCached(nextStart, nextStop)) { + if (!frameDataCache[this.jid].activeChunkRequest) { + frameDataCache[this.jid].activeChunkRequest = { + request: null, + chunkNumber: nextChunkNumber, + start: nextStart, + stop: nextStop, + onDecodeAll: null, + rejectRequestAll: null, + completed: false, + callbacks: [], + }; + makeActiveRequest(); } + } else { + provider.requestDecodeBlock(null, nextStart, nextStop, null, null); } } - resolveWrapper(frame); - } - }) - .catch((exception) => { - if (exception instanceof Exception) { - reject(exception); - } else { - reject(new Exception(exception.message)); } - }); - } - }); - }; - - function getFrameMeta(jobID, frame) { - const { meta, mode, startFrame } = frameDataCache[jobID]; - let size = null; - if (mode === 'interpolation') { - [size] = meta.frames; - } else if (mode === 'annotation') { - if (frame >= meta.size) { - throw new ArgumentError(`Meta information about frame ${frame} can't be received from the server`); - } else { - size = meta.frames[frame - startFrame]; - } + resolveWrapper(frame); + } + }) + .catch((exception) => { + if (exception instanceof Exception) { + reject(exception); + } else { + reject(new Exception(exception.message)); + } + }); + } + }); +}; + +function getFrameMeta(jobID, frame) { + const { meta, mode, startFrame } = frameDataCache[jobID]; + let size = null; + if (mode === 'interpolation') { + [size] = meta.frames; + } else if (mode === 'annotation') { + if (frame >= meta.size) { + throw new ArgumentError(`Meta information about frame ${frame} can't be received from the server`); } else { - throw new DataError(`Invalid mode is specified ${mode}`); + size = meta.frames[frame - startFrame]; } - return size; + } else { + throw new DataError(`Invalid mode is specified ${mode}`); + } + return size; +} + +class FrameBuffer { + constructor(size, chunkSize, stopFrame, jobID) { + this._size = size; + this._buffer = {}; + this._contextImage = {}; + this._requestedChunks = {}; + this._chunkSize = chunkSize; + this._stopFrame = stopFrame; + this._activeFillBufferRequest = false; + this._jobID = jobID; } - class FrameBuffer { - constructor(size, chunkSize, stopFrame, jobID) { - this._size = size; - this._buffer = {}; - this._contextImage = {}; - this._requestedChunks = {}; - this._chunkSize = chunkSize; - this._stopFrame = stopFrame; - this._activeFillBufferRequest = false; - this._jobID = jobID; - } + isContextImageAvailable(frame) { + return frame in this._contextImage; + } - isContextImageAvailable(frame) { - return frame in this._contextImage; - } + getContextImage(frame) { + return this._contextImage[frame] || null; + } - getContextImage(frame) { - return this._contextImage[frame] || null; - } + addContextImage(frame, data) { + this._contextImage[frame] = data; + } - addContextImage(frame, data) { - this._contextImage[frame] = data; + getFreeBufferSize() { + let requestedFrameCount = 0; + for (const chunk of Object.values(this._requestedChunks)) { + requestedFrameCount += chunk.requestedFrames.size; } - getFreeBufferSize() { - let requestedFrameCount = 0; - for (const chunk of Object.values(this._requestedChunks)) { - requestedFrameCount += chunk.requestedFrames.size; + return this._size - Object.keys(this._buffer).length - requestedFrameCount; + } + + requestOneChunkFrames(chunkIdx) { + return new Promise((resolve, reject) => { + this._requestedChunks[chunkIdx] = { + ...this._requestedChunks[chunkIdx], + resolve, + reject, + }; + for (const frame of this._requestedChunks[chunkIdx].requestedFrames.entries()) { + const requestedFrame = frame[1]; + const frameMeta = getFrameMeta(this._jobID, requestedFrame); + const frameData = new FrameData({ + ...frameMeta, + jobID: this._jobID, + frameNumber: requestedFrame, + startFrame: frameDataCache[this._jobID].startFrame, + stopFrame: frameDataCache[this._jobID].stopFrame, + decodeForward: false, + deleted: requestedFrame in frameDataCache[this._jobID].meta, + }); + + frameData + .data() + .then(() => { + if ( + !(chunkIdx in this._requestedChunks) || + !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame) + ) { + reject(chunkIdx); + } else { + this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); + this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; + if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { + const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map( + (f) => +f, + ); + this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); + } + } + }) + .catch(() => { + reject(chunkIdx); + }); } + }); + } - return this._size - Object.keys(this._buffer).length - requestedFrameCount; - } + fillBuffer(startFrame, frameStep = 1, count = null) { + const freeSize = this.getFreeBufferSize(); + const requestedFrameCount = count ? count * frameStep : freeSize * frameStep; + const stopFrame = Math.min(startFrame + requestedFrameCount, this._stopFrame + 1); - requestOneChunkFrames(chunkIdx) { - return new Promise((resolve, reject) => { + for (let i = startFrame; i < stopFrame; i += frameStep) { + const chunkIdx = Math.floor(i / this._chunkSize); + if (!(chunkIdx in this._requestedChunks)) { this._requestedChunks[chunkIdx] = { - ...this._requestedChunks[chunkIdx], - resolve, - reject, + requestedFrames: new Set(), + resolve: null, + reject: null, + buffer: {}, }; - for (const frame of this._requestedChunks[chunkIdx].requestedFrames.entries()) { - const requestedFrame = frame[1]; - const frameMeta = getFrameMeta(this._jobID, requestedFrame); - const frameData = new FrameData({ - ...frameMeta, - jobID: this._jobID, - frameNumber: requestedFrame, - startFrame: frameDataCache[this._jobID].startFrame, - stopFrame: frameDataCache[this._jobID].stopFrame, - decodeForward: false, - deleted: requestedFrame in frameDataCache[this._jobID].meta, - }); - - frameData - .data() - .then(() => { - if ( - !(chunkIdx in this._requestedChunks) || - !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame) - ) { - reject(chunkIdx); - } else { - this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); - this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; - if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { - const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map( - (f) => +f, - ); - this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); - } - } - }) - .catch(() => { - reject(chunkIdx); - }); - } - }); + } + this._requestedChunks[chunkIdx].requestedFrames.add(i); } - fillBuffer(startFrame, frameStep = 1, count = null) { - const freeSize = this.getFreeBufferSize(); - const requestedFrameCount = count ? count * frameStep : freeSize * frameStep; - const stopFrame = Math.min(startFrame + requestedFrameCount, this._stopFrame + 1); - - for (let i = startFrame; i < stopFrame; i += frameStep) { - const chunkIdx = Math.floor(i / this._chunkSize); - if (!(chunkIdx in this._requestedChunks)) { - this._requestedChunks[chunkIdx] = { - requestedFrames: new Set(), - resolve: null, - reject: null, - buffer: {}, - }; - } - this._requestedChunks[chunkIdx].requestedFrames.add(i); + let bufferedFrames = new Set(); + + // if we send one request to get frame 1 with filling the buffer + // then quicky send one more request to get frame 1 + // frame 1 will be already decoded and written to buffer + // the second request gets frame 1 from the buffer, removes it from there and returns + // after the first request finishes decoding it tries to get frame 1, but failed + // because frame 1 was already removed from the buffer by the second request + // to prevent this behavior we do not write decoded frames to buffer till the end of decoding all chunks + const buffersToBeCommited = []; + const commitBuffers = () => { + for (const buffer of buffersToBeCommited) { + this._buffer = { + ...this._buffer, + ...buffer, + }; } + }; - let bufferedFrames = new Set(); - - // if we send one request to get frame 1 with filling the buffer - // then quicky send one more request to get frame 1 - // frame 1 will be already decoded and written to buffer - // the second request gets frame 1 from the buffer, removes it from there and returns - // after the first request finishes decoding it tries to get frame 1, but failed - // because frame 1 was already removed from the buffer by the second request - // to prevent this behavior we do not write decoded frames to buffer till the end of decoding all chunks - const buffersToBeCommited = []; - const commitBuffers = () => { - for (const buffer of buffersToBeCommited) { - this._buffer = { - ...this._buffer, - ...buffer, - }; - } - }; + // Need to decode chunks in sequence + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + for (const chunkIdx of Object.keys(this._requestedChunks)) { + try { + const chunkFrames = await this.requestOneChunkFrames(chunkIdx); + if (chunkIdx in this._requestedChunks) { + bufferedFrames = new Set([...bufferedFrames, ...chunkFrames]); - // Need to decode chunks in sequence - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - for (const chunkIdx of Object.keys(this._requestedChunks)) { - try { - const chunkFrames = await this.requestOneChunkFrames(chunkIdx); - if (chunkIdx in this._requestedChunks) { - bufferedFrames = new Set([...bufferedFrames, ...chunkFrames]); - - buffersToBeCommited.push(this._requestedChunks[chunkIdx].buffer); - delete this._requestedChunks[chunkIdx]; - if (Object.keys(this._requestedChunks).length === 0) { - commitBuffers(); - resolve(bufferedFrames); - } - } else { + buffersToBeCommited.push(this._requestedChunks[chunkIdx].buffer); + delete this._requestedChunks[chunkIdx]; + if (Object.keys(this._requestedChunks).length === 0) { commitBuffers(); - reject(chunkIdx); - break; + resolve(bufferedFrames); } - } catch (error) { + } else { commitBuffers(); - reject(error); + reject(chunkIdx); break; } + } catch (error) { + commitBuffers(); + reject(error); + break; } - }); - } + } + }); + } - async makeFillRequest(start, step, count = null) { - if (!this._activeFillBufferRequest) { - this._activeFillBufferRequest = true; - try { - await this.fillBuffer(start, step, count); + async makeFillRequest(start, step, count = null) { + if (!this._activeFillBufferRequest) { + this._activeFillBufferRequest = true; + try { + await this.fillBuffer(start, step, count); + this._activeFillBufferRequest = false; + } catch (error) { + if (typeof error === 'number' && error in this._requestedChunks) { this._activeFillBufferRequest = false; - } catch (error) { - if (typeof error === 'number' && error in this._requestedChunks) { - this._activeFillBufferRequest = false; - } - throw error; } + throw error; } } + } - async require(frameNumber, jobID, fillBuffer, frameStep) { - for (const frame in this._buffer) { - if (+frame < frameNumber || +frame >= frameNumber + this._size * frameStep) { - delete this._buffer[frame]; - } + async require(frameNumber, jobID, fillBuffer, frameStep) { + for (const frame in this._buffer) { + if (+frame < frameNumber || +frame >= frameNumber + this._size * frameStep) { + delete this._buffer[frame]; } + } - this._required = frameNumber; - const frameMeta = getFrameMeta(jobID, frameNumber); - let frame = new FrameData({ - ...frameMeta, - jobID, - frameNumber, - startFrame: frameDataCache[jobID].startFrame, - stopFrame: frameDataCache[jobID].stopFrame, - decodeForward: !fillBuffer, - deleted: frameNumber in frameDataCache[jobID].meta.deleted_frames, - }); + this._required = frameNumber; + const frameMeta = getFrameMeta(jobID, frameNumber); + let frame = new FrameData({ + ...frameMeta, + jobID, + frameNumber, + startFrame: frameDataCache[jobID].startFrame, + stopFrame: frameDataCache[jobID].stopFrame, + decodeForward: !fillBuffer, + deleted: frameNumber in frameDataCache[jobID].meta.deleted_frames, + }); - if (frameNumber in this._buffer) { - frame = this._buffer[frameNumber]; - delete this._buffer[frameNumber]; - const cachedFrames = this.cachedFrames(); - if ( - fillBuffer && - !this._activeFillBufferRequest && - this._size > this._chunkSize && - cachedFrames.length < (this._size * 3) / 4 - ) { - const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber; - if (maxFrame < this._stopFrame) { - this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => { - if (e !== 'not needed') { - throw e; - } - }); - } + if (frameNumber in this._buffer) { + frame = this._buffer[frameNumber]; + delete this._buffer[frameNumber]; + const cachedFrames = this.cachedFrames(); + if ( + fillBuffer && + !this._activeFillBufferRequest && + this._size > this._chunkSize && + cachedFrames.length < (this._size * 3) / 4 + ) { + const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber; + if (maxFrame < this._stopFrame) { + this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => { + if (e !== 'not needed') { + throw e; + } + }); } - } else if (fillBuffer) { - this.clear(); - await this.makeFillRequest(frameNumber, frameStep, fillBuffer ? null : 1); - frame = this._buffer[frameNumber]; - } else { - this.clear(); } - - return frame; + } else if (fillBuffer) { + this.clear(); + await this.makeFillRequest(frameNumber, frameStep, fillBuffer ? null : 1); + frame = this._buffer[frameNumber]; + } else { + this.clear(); } - clear() { - for (const chunkIdx in this._requestedChunks) { - if ( - Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) && - this._requestedChunks[chunkIdx].reject - ) { - this._requestedChunks[chunkIdx].reject('not needed'); - } - } - this._activeFillBufferRequest = false; - this._requestedChunks = {}; - this._buffer = {}; - } + return frame; + } - cachedFrames() { - return Object.keys(this._buffer).map((f) => +f); + clear() { + for (const chunkIdx in this._requestedChunks) { + if ( + Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) && + this._requestedChunks[chunkIdx].reject + ) { + this._requestedChunks[chunkIdx].reject('not needed'); + } } + this._activeFillBufferRequest = false; + this._requestedChunks = {}; + this._buffer = {}; } - async function getImageContext(jobID, frame) { - return new Promise((resolve, reject) => { - serverProxy.frames - .getImageContext(jobID, frame) - .then((result) => { - if (isNode) { - // eslint-disable-next-line no-undef - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); + cachedFrames() { + return Object.keys(this._buffer).map((f) => +f); } +} + +async function getImageContext(jobID, frame) { + return new Promise((resolve, reject) => { + serverProxy.frames + .getImageContext(jobID, frame) + .then((result) => { + if (isNode) { + // eslint-disable-next-line no-undef + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); +} - async function getContextImage(jobID, frame) { - if (frameDataCache[jobID].frameBuffer.isContextImageAvailable(frame)) { - return frameDataCache[jobID].frameBuffer.getContextImage(frame); - } - const response = getImageContext(jobID, frame); - frameDataCache[jobID].frameBuffer.addContextImage(frame, response); +export async function getContextImage(jobID, frame) { + if (frameDataCache[jobID].frameBuffer.isContextImageAvailable(frame)) { return frameDataCache[jobID].frameBuffer.getContextImage(frame); } - - async function getPreview(taskID = null, jobID = null) { - return new Promise((resolve, reject) => { - // Just go to server and get preview (no any cache) - serverProxy.frames - .getPreview(taskID, jobID) - .then((result) => { - if (isNode) { - // eslint-disable-next-line no-undef - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); - } - - async function getFrame( - jobID, - chunkSize, - chunkType, - mode, - frame, - startFrame, - stopFrame, - isPlaying, - step, - dimension, - ) { - if (!(jobID in frameDataCache)) { - const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE; - const meta = await serverProxy.frames.getMeta('job', jobID); - meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); - const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length; - const stdDev = Math.sqrt( - meta.frames.map((x) => (x.width * x.height - mean) ** 2).reduce((a, b) => a + b) / - meta.frames.length, - ); - - // limit of decoded frames cache by 2GB - const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) || 1; - - frameDataCache[jobID] = { - meta, + const response = getImageContext(jobID, frame); + frameDataCache[jobID].frameBuffer.addContextImage(frame, response); + return frameDataCache[jobID].frameBuffer.getContextImage(frame); +} + +export async function getPreview(taskID = null, jobID = null) { + return new Promise((resolve, reject) => { + // Just go to server and get preview (no any cache) + serverProxy.frames + .getPreview(taskID, jobID) + .then((result) => { + if (isNode) { + // eslint-disable-next-line no-undef + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); +} + +export async function getFrame( + jobID, + chunkSize, + chunkType, + mode, + frame, + startFrame, + stopFrame, + isPlaying, + step, + dimension, +) { + if (!(jobID in frameDataCache)) { + const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE; + const meta = await serverProxy.frames.getMeta('job', jobID); + meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); + const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length; + const stdDev = Math.sqrt( + meta.frames.map((x) => (x.width * x.height - mean) ** 2).reduce((a, b) => a + b) / + meta.frames.length, + ); + + // limit of decoded frames cache by 2GB + const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) || 1; + + frameDataCache[jobID] = { + meta, + chunkSize, + mode, + startFrame, + stopFrame, + provider: new cvatData.FrameProvider( + blockType, chunkSize, - mode, - startFrame, - stopFrame, - provider: new cvatData.FrameProvider( - blockType, - chunkSize, - Math.max(decodedBlocksCacheSize, 9), - decodedBlocksCacheSize, - 1, - dimension, - ), - frameBuffer: new FrameBuffer( - Math.min(180, decodedBlocksCacheSize * chunkSize), - chunkSize, - stopFrame, - jobID, - ), + Math.max(decodedBlocksCacheSize, 9), decodedBlocksCacheSize, - activeChunkRequest: null, - nextChunkRequest: null, - }; - const frameMeta = getFrameMeta(jobID, frame); - // actual only for video chunks - frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height); - } - - return frameDataCache[jobID].frameBuffer.require(frame, jobID, isPlaying, step); + 1, + dimension, + ), + frameBuffer: new FrameBuffer( + Math.min(180, decodedBlocksCacheSize * chunkSize), + chunkSize, + stopFrame, + jobID, + ), + decodedBlocksCacheSize, + activeChunkRequest: null, + nextChunkRequest: null, + }; + const frameMeta = getFrameMeta(jobID, frame); + // actual only for video chunks + frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height); } - async function getDeletedFrames(instanceType, id) { - if (instanceType === 'job') { - const { meta } = frameDataCache[id]; - return meta.deleted_frames; - } + return frameDataCache[jobID].frameBuffer.require(frame, jobID, isPlaying, step); +} - if (instanceType === 'task') { - const meta = await serverProxy.frames.getMeta('job', id); - meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); - return meta; - } - - throw new Exception(`getDeletedFrames is not implemented for ${instanceType}`); +export async function getDeletedFrames(instanceType, id) { + if (instanceType === 'job') { + const { meta } = frameDataCache[id]; + return meta.deleted_frames; } - function deleteFrame(jobID, frame) { - const { meta } = frameDataCache[jobID]; - meta.deleted_frames[frame] = true; + if (instanceType === 'task') { + const meta = await serverProxy.frames.getMeta('job', id); + meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); + return meta; } - function restoreFrame(jobID, frame) { - const { meta } = frameDataCache[jobID]; - if (frame in meta.deleted_frames) { - delete meta.deleted_frames[frame]; - } - } + throw new Exception(`getDeletedFrames is not implemented for ${instanceType}`); +} - async function patchMeta(jobID) { - const { meta } = frameDataCache[jobID]; - const newMeta = await serverProxy.frames.saveMeta('job', jobID, { - deleted_frames: Object.keys(meta.deleted_frames), - }); - const prevDeletedFrames = meta.deleted_frames; - - // it is important do not overwrite the object, it is why we working on keys in two loops below - for (const frame of Object.keys(prevDeletedFrames)) { - delete prevDeletedFrames[frame]; - } - for (const frame of newMeta.deleted_frames) { - prevDeletedFrames[frame] = true; - } +export function deleteFrame(jobID, frame) { + const { meta } = frameDataCache[jobID]; + meta.deleted_frames[frame] = true; +} - frameDataCache[jobID].meta = newMeta; - frameDataCache[jobID].meta.deleted_frames = prevDeletedFrames; +export function restoreFrame(jobID, frame) { + const { meta } = frameDataCache[jobID]; + if (frame in meta.deleted_frames) { + delete meta.deleted_frames[frame]; + } +} + +export async function patchMeta(jobID) { + const { meta } = frameDataCache[jobID]; + const newMeta = await serverProxy.frames.saveMeta('job', jobID, { + deleted_frames: Object.keys(meta.deleted_frames), + }); + const prevDeletedFrames = meta.deleted_frames; + + // it is important do not overwrite the object, it is why we working on keys in two loops below + for (const frame of Object.keys(prevDeletedFrames)) { + delete prevDeletedFrames[frame]; + } + for (const frame of newMeta.deleted_frames) { + prevDeletedFrames[frame] = true; } - async function findNotDeletedFrame(jobID, frameFrom, frameTo, offset) { - let meta; - if (!frameDataCache[jobID]) { - meta = await serverProxy.frames.getMeta('job', jobID); - } else { - meta = frameDataCache[jobID].meta; - } - const sign = Math.sign(frameTo - frameFrom); - const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; - const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; - let framesCounter = 0; - let lastUndeletedFrame = null; - for (let frame = frameFrom; predicate(frame); frame = update(frame)) { - if (!(frame in meta.deleted_frames)) { - lastUndeletedFrame = frame; - framesCounter++; - if (framesCounter === offset) { - return lastUndeletedFrame; - } + frameDataCache[jobID].meta = newMeta; + frameDataCache[jobID].meta.deleted_frames = prevDeletedFrames; +} + +export async function findNotDeletedFrame(jobID, frameFrom, frameTo, offset) { + let meta; + if (!frameDataCache[jobID]) { + meta = await serverProxy.frames.getMeta('job', jobID); + } else { + meta = frameDataCache[jobID].meta; + } + const sign = Math.sign(frameTo - frameFrom); + const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; + const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; + let framesCounter = 0; + let lastUndeletedFrame = null; + for (let frame = frameFrom; predicate(frame); frame = update(frame)) { + if (!(frame in meta.deleted_frames)) { + lastUndeletedFrame = frame; + framesCounter++; + if (framesCounter === offset) { + return lastUndeletedFrame; } } - - return lastUndeletedFrame; } - function getRanges(jobID) { - if (!(jobID in frameDataCache)) { - return { - decoded: [], - buffered: [], - }; - } + return lastUndeletedFrame; +} +export function getRanges(jobID) { + if (!(jobID in frameDataCache)) { return { - decoded: frameDataCache[jobID].provider.cachedFrames, - buffered: frameDataCache[jobID].frameBuffer.cachedFrames(), + decoded: [], + buffered: [], }; } - function clear(jobID) { - if (jobID in frameDataCache) { - frameDataCache[jobID].frameBuffer.clear(); - delete frameDataCache[jobID]; - } - } - - module.exports = { - FrameData, - getFrame, - getDeletedFrames, - deleteFrame, - restoreFrame, - patchMeta, - getRanges, - getPreview, - clear, - findNotDeletedFrame, - getContextImage, + return { + decoded: frameDataCache[jobID].provider.cachedFrames, + buffered: frameDataCache[jobID].frameBuffer.cachedFrames(), }; -})(); +} + +export function clear(jobID) { + if (jobID in frameDataCache) { + frameDataCache[jobID].frameBuffer.clear(); + delete frameDataCache[jobID]; + } +} diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index e0d074f3..3fd6727b 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -13,7 +13,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const serverProxy = require('../../src/server-proxy').default; // Test cases diff --git a/cvat-core/tests/api/cloud-storages.js b/cvat-core/tests/api/cloud-storages.js index 70a7de37..a671b0a2 100644 --- a/cvat-core/tests/api/cloud-storages.js +++ b/cvat-core/tests/api/cloud-storages.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const CloudStorage= require('../../src/cloud-storage').default; const { cloudStoragesDummyData } = require('../mocks/dummy-data.mock'); diff --git a/cvat-core/tests/api/frames.js b/cvat-core/tests/api/frames.js index a707e4ea..8f9299ab 100644 --- a/cvat-core/tests/api/frames.js +++ b/cvat-core/tests/api/frames.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { FrameData } = require('../../src/frames'); diff --git a/cvat-core/tests/api/jobs.js b/cvat-core/tests/api/jobs.js index 3adcdb7f..7d457f17 100644 --- a/cvat-core/tests/api/jobs.js +++ b/cvat-core/tests/api/jobs.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { Job } = require('../../src/session'); diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index 0d15ca43..65ce7b1a 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; describe('Feature: set attributes for an object state', () => { test('set a valid value', () => { diff --git a/cvat-core/tests/api/plugins.js b/cvat-core/tests/api/plugins.js index 1f2949ab..8b791a89 100644 --- a/cvat-core/tests/api/plugins.js +++ b/cvat-core/tests/api/plugins.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; describe('Feature: dummy feature', () => { test('dummy test', async () => { diff --git a/cvat-core/tests/api/projects.js b/cvat-core/tests/api/projects.js index 9c942610..082cd034 100644 --- a/cvat-core/tests/api/projects.js +++ b/cvat-core/tests/api/projects.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const Project = require('../../src/project').default; diff --git a/cvat-core/tests/api/server.js b/cvat-core/tests/api/server.js index d775d89e..138388a8 100644 --- a/cvat-core/tests/api/server.js +++ b/cvat-core/tests/api/server.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { AnnotationFormats, Loader, Dumper } = require('../../src/annotation-formats'); // Test cases diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index 7321824b..96ccd95e 100644 --- a/cvat-core/tests/api/tasks.js +++ b/cvat-core/tests/api/tasks.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { Task } = require('../../src/session'); diff --git a/cvat-core/tests/api/user.js b/cvat-core/tests/api/user.js index 7d4e53ad..96e965ff 100644 --- a/cvat-core/tests/api/user.js +++ b/cvat-core/tests/api/user.js @@ -12,7 +12,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const User = require('../../src/user').default; diff --git a/cvat-core/tests/api/webhooks.js b/cvat-core/tests/api/webhooks.js index 0bee1010..9a2c2283 100644 --- a/cvat-core/tests/api/webhooks.js +++ b/cvat-core/tests/api/webhooks.js @@ -11,7 +11,7 @@ jest.mock('../../src/server-proxy', () => { }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const Webhook = require('../../src/webhook').default; const { webhooksDummyData, webhooksEventsDummyData } = require('../mocks/dummy-data.mock'); diff --git a/cvat-core/tsconfig.json b/cvat-core/tsconfig.json index 036d7e73..ba78d461 100644 --- a/cvat-core/tsconfig.json +++ b/cvat-core/tsconfig.json @@ -8,6 +8,7 @@ "isolatedModules": true, "noEmit": true, "baseUrl": "src", + "resolveJsonModule": true, }, "include": ["src/*.ts"] }