diff --git a/CHANGELOG.md b/CHANGELOG.md
index abc9800b0614..18e5ca299eac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+
+## \[2.14.4\] - 2024-06-20
+
+### Added
+
+- Polyline editing may be finished using corresponding shortcut
+ ()
+
+### Changed
+
+- Single shape annotation mode allows to modify/delete objects
+ ()
+
+### Fixed
+
+- Invalid server cache cleanup for backups and events (after #7864)
+ ()
+
+- Filters by created date, updated date do not work on different pages (e.g. list of tasks or jobs)
+ ()
+
## \[2.14.3\] - 2024-06-13
diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts
index 84e4bca31e4b..1bcc7ddb961a 100644
--- a/cvat-canvas/src/typescript/canvas.ts
+++ b/cvat-canvas/src/typescript/canvas.ts
@@ -11,6 +11,7 @@ import {
CanvasModel, CanvasModelImpl, RectDrawingMethod,
CuboidDrawingMethod, Configuration, Geometry, Mode,
HighlightSeverity as _HighlightSeverity, CanvasHint as _CanvasHint,
+ PolyEditData,
} from './canvasModel';
import { Master } from './master';
import { CanvasController, CanvasControllerImpl } from './canvasController';
@@ -35,7 +36,7 @@ interface Canvas {
interact(interactionData: InteractionData): void;
draw(drawData: DrawData): void;
- edit(editData: MasksEditData): void;
+ edit(editData: MasksEditData | PolyEditData): void;
group(groupData: GroupData): void;
join(joinData: JoinData): void;
slice(sliceData: SliceData): void;
@@ -137,7 +138,7 @@ class CanvasImpl implements Canvas {
this.model.draw(drawData);
}
- public edit(editData: MasksEditData): void {
+ public edit(editData: MasksEditData | PolyEditData): void {
this.model.edit(editData);
}
diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts
index 9acf58e95fc2..82ec611be3e4 100644
--- a/cvat-canvas/src/typescript/canvasController.ts
+++ b/cvat-canvas/src/typescript/canvasController.ts
@@ -20,6 +20,7 @@ import {
Configuration,
MasksEditData,
HighlightedElements,
+ PolyEditData,
} from './canvasModel';
export interface CanvasController {
@@ -30,7 +31,7 @@ export interface CanvasController {
readonly activeElement: ActiveElement;
readonly highlightedElements: HighlightedElements;
readonly drawData: DrawData;
- readonly editData: MasksEditData;
+ readonly editData: MasksEditData | PolyEditData;
readonly interactionData: InteractionData;
readonly mergeData: MergeData;
readonly splitData: SplitData;
@@ -44,7 +45,7 @@ export interface CanvasController {
zoom(x: number, y: number, direction: number): void;
draw(drawData: DrawData): void;
- edit(editData: MasksEditData): void;
+ edit(editData: MasksEditData | PolyEditData): void;
enableDrag(x: number, y: number): void;
drag(x: number, y: number): void;
disableDrag(): void;
@@ -96,7 +97,7 @@ export class CanvasControllerImpl implements CanvasController {
this.model.draw(drawData);
}
- public edit(editData: MasksEditData): void {
+ public edit(editData: MasksEditData | PolyEditData): void {
this.model.edit(editData);
}
@@ -136,7 +137,7 @@ export class CanvasControllerImpl implements CanvasController {
return this.model.drawData;
}
- public get editData(): MasksEditData {
+ public get editData(): MasksEditData | PolyEditData {
return this.model.editData;
}
diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts
index 426a76acfd62..181111396267 100644
--- a/cvat-canvas/src/typescript/canvasModel.ts
+++ b/cvat-canvas/src/typescript/canvasModel.ts
@@ -147,8 +147,8 @@ export interface InteractionResult {
export interface PolyEditData {
enabled: boolean;
- state: any;
- pointID: number;
+ state?: any;
+ pointID?: number;
}
export interface MasksEditData {
@@ -249,7 +249,7 @@ export interface CanvasModel {
readonly activeElement: ActiveElement;
readonly highlightedElements: HighlightedElements;
readonly drawData: DrawData;
- readonly editData: MasksEditData;
+ readonly editData: MasksEditData | PolyEditData;
readonly interactionData: InteractionData;
readonly mergeData: MergeData;
readonly splitData: SplitData;
@@ -275,7 +275,7 @@ export interface CanvasModel {
grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void;
- edit(editData: MasksEditData): void;
+ edit(editData: MasksEditData | PolyEditData): void;
group(groupData: GroupData): void;
join(joinData: JoinData): void;
slice(sliceData: SliceData): void;
@@ -369,7 +369,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
fittedScale: number;
zLayer: number | null;
drawData: DrawData;
- editData: MasksEditData;
+ editData: MasksEditData | PolyEditData;
interactionData: InteractionData;
mergeData: MergeData;
groupData: GroupData;
@@ -780,7 +780,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.notify(UpdateReasons.DRAW);
}
- public edit(editData: MasksEditData): void {
+ public edit(editData: MasksEditData | PolyEditData): void {
if (![Mode.IDLE, Mode.EDIT].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
@@ -1083,7 +1083,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
return { ...this.data.drawData };
}
- public get editData(): MasksEditData {
+ public get editData(): MasksEditData | PolyEditData {
return { ...this.data.editData };
}
diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts
index 4b348903aae3..398a6262c5d3 100644
--- a/cvat-canvas/src/typescript/canvasView.ts
+++ b/cvat-canvas/src/typescript/canvasView.ts
@@ -1642,6 +1642,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.masksHandler.edit(data);
} else if (this.masksHandler.enabled) {
this.masksHandler.edit(data);
+ } else if (this.editHandler.enabled && this.editHandler.shapeType === 'polyline') {
+ this.editHandler.edit(data);
}
} else if (reason === UpdateReasons.INTERACT) {
const data: InteractionData = this.controller.interactionData;
@@ -1854,18 +1856,18 @@ export class CanvasViewImpl implements CanvasView, Listener {
const { points } = state;
const [left, top, right, bottom] = points.slice(-4);
const imageBitmap = expandChannels(255, 255, 255, points);
- imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1,
- (dataURL: string) => new Promise((resolve) => {
- if (bitmapUpdateReqId === this.bitmapUpdateReqId) {
- const img = document.createElement('img');
- img.addEventListener('load', () => {
- ctx.drawImage(img, left, top);
- URL.revokeObjectURL(dataURL);
- resolve();
- });
- img.src = dataURL;
- }
- }));
+ imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, (dataURL: string) => new
+ Promise((resolve) => {
+ if (bitmapUpdateReqId === this.bitmapUpdateReqId) {
+ const img = document.createElement('img');
+ img.addEventListener('load', () => {
+ ctx.drawImage(img, left, top);
+ URL.revokeObjectURL(dataURL);
+ resolve();
+ });
+ img.src = dataURL;
+ }
+ }));
}
if (state.shapeType === 'cuboid') {
diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts
index 89e61881a57c..567eea29c7de 100644
--- a/cvat-canvas/src/typescript/editHandler.ts
+++ b/cvat-canvas/src/typescript/editHandler.ts
@@ -16,6 +16,8 @@ export interface EditHandler {
transform(geometry: Geometry): void;
configurate(configuration: Configuration): void;
cancel(): void;
+ enabled: boolean;
+ shapeType: string;
}
export class EditHandlerImpl implements EditHandler {
@@ -31,9 +33,9 @@ export class EditHandlerImpl implements EditHandler {
private autobordersEnabled: boolean;
private intelligentCutEnabled: boolean;
private outlinedBorders: string;
+ private isEditing: boolean;
private setupTrailingPoint(circle: SVG.Circle): void {
- const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' ');
circle.on('mouseenter', (): void => {
circle.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale,
@@ -46,22 +48,9 @@ export class EditHandlerImpl implements EditHandler {
});
});
- const minimumPoints = 2;
circle.on('mousedown', (e: MouseEvent): void => {
if (e.button !== 0) return;
- const { offset } = this.geometry;
- const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`;
- const points = pointsToNumberArray(stringifiedPoints)
- .slice(0, -2)
- .map((coord: number): number => coord - offset);
-
- if (points.length >= minimumPoints * 2) {
- const { state } = this.editData;
- this.edit({
- enabled: false,
- });
- this.onEditDone(state, points);
- }
+ this.edit({ enabled: false });
});
}
@@ -345,6 +334,7 @@ export class EditHandlerImpl implements EditHandler {
this.canvas.off('mousedown.edit');
this.canvas.off('mousemove.edit');
this.autoborderHandler.autoborder(false);
+ this.isEditing = false;
if (this.editedShape) {
this.setupPoints(false);
@@ -372,6 +362,7 @@ export class EditHandlerImpl implements EditHandler {
.clone().attr('stroke', this.outlinedBorders);
this.setupPoints(true);
this.startEdit();
+ this.isEditing = true;
// draw points for this with selected and start editing till another point is clicked
// click one of two parts to remove (in case of polygon only)
@@ -380,6 +371,18 @@ export class EditHandlerImpl implements EditHandler {
}
private closeEditing(): void {
+ if (this.isEditing && this.editData.state.shapeType === 'polyline') {
+ const { offset } = this.geometry;
+ const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' ');
+ const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`;
+ const points = pointsToNumberArray(stringifiedPoints)
+ .slice(0, -2)
+ .map((coord: number): number => coord - offset);
+ if (points.length >= 2 * 2) { // minimumPoints * 2
+ const { state } = this.editData;
+ this.onEditDone(state, points);
+ }
+ }
this.release();
}
@@ -400,11 +403,12 @@ export class EditHandlerImpl implements EditHandler {
this.editLine = null;
this.geometry = null;
this.clones = [];
+ this.isEditing = false;
}
public edit(editData: any): void {
if (editData.enabled) {
- if (editData.state.shapeType !== 'rectangle') {
+ if (['polygon', 'polyline', 'points'].includes(editData.state.shapeType)) {
this.editData = editData;
this.initEditing();
} else {
@@ -421,6 +425,14 @@ export class EditHandlerImpl implements EditHandler {
this.onEditDone(null, null);
}
+ get enabled(): boolean {
+ return this.isEditing;
+ }
+
+ get shapeType(): string {
+ return this.editData.state.shapeType;
+ }
+
public configurate(configuration: Configuration): void {
this.autobordersEnabled = configuration.autoborders;
this.outlinedBorders = configuration.outlinedBorders || 'black';
diff --git a/cvat-cli/requirements/base.txt b/cvat-cli/requirements/base.txt
index 05201fb33f02..0d48f98de939 100644
--- a/cvat-cli/requirements/base.txt
+++ b/cvat-cli/requirements/base.txt
@@ -1,3 +1,3 @@
-cvat-sdk~=2.14.3
+cvat-sdk~=2.14.4
Pillow>=10.3.0
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
diff --git a/cvat-cli/src/cvat_cli/version.py b/cvat-cli/src/cvat_cli/version.py
index fc687842ab48..7497d5e3c100 100644
--- a/cvat-cli/src/cvat_cli/version.py
+++ b/cvat-cli/src/cvat_cli/version.py
@@ -1 +1 @@
-VERSION = "2.14.3"
+VERSION = "2.14.4"
diff --git a/cvat-core/src/annotations-collection.ts b/cvat-core/src/annotations-collection.ts
index 257e18fbad24..d1a8c8d24bfb 100644
--- a/cvat-core/src/annotations-collection.ts
+++ b/cvat-core/src/annotations-collection.ts
@@ -24,12 +24,6 @@ import {
import AnnotationHistory from './annotations-history';
import { Job } from './session';
-interface ImportedCollection {
- tags: Tag[],
- shapes: Shape[],
- tracks: Track[],
-}
-
const validateAttributesList = (
attributes: { spec_id: number, value: string }[],
): { spec_id: number, value: string }[] => {
@@ -116,7 +110,11 @@ export default class Collection {
};
}
- public import(data: Omit): ImportedCollection {
+ public import(data: Omit): {
+ tags: Tag[];
+ shapes: Shape[];
+ tracks: Track[];
+ } {
const result = {
tags: [],
shapes: [],
@@ -181,7 +179,7 @@ export default class Collection {
return data;
}
- public get(frame: number, allTracks: boolean, filters: string[]): ObjectState[] {
+ public get(frame: number, allTracks: boolean, filters: object[]): ObjectState[] {
const { tracks } = this;
const shapes = this.shapes[frame] || [];
const tags = this.tags[frame] || [];
@@ -774,47 +772,56 @@ export default class Collection {
);
}
- public clear(startframe: number, endframe: number, delTrackKeyframesOnly: boolean): void {
- if (startframe !== undefined && endframe !== undefined) {
+ public clear(options?: {
+ startFrame?: number;
+ stopFrame?: number;
+ delTrackKeyframesOnly?: boolean;
+ }): void {
+ const { startFrame, stopFrame, delTrackKeyframesOnly } = options ?? {};
+
+ if (typeof startFrame === 'undefined' && typeof stopFrame === 'undefined') {
+ this.shapes = {};
+ this.tags = {};
+ this.tracks = [];
+ this.objects = {};
+
+ this.flush = true;
+ } else {
+ const from = startFrame ?? 0;
+ const to = stopFrame ?? this.stopFrame;
+
// If only a range of annotations need to be cleared
- for (let frame = startframe; frame <= endframe; frame++) {
+ for (let frame = from; frame <= to; frame++) {
this.shapes[frame] = [];
this.tags[frame] = [];
}
- const { tracks } = this;
- tracks.forEach((track) => {
- if (track.frame <= endframe) {
+
+ this.tracks.slice(0).forEach((track) => {
+ if (track.frame <= to) {
if (delTrackKeyframesOnly) {
for (const keyframe of Object.keys(track.shapes)) {
- if (+keyframe >= startframe && +keyframe <= endframe) {
+ if (+keyframe >= from && +keyframe <= to) {
delete track.shapes[keyframe];
- ((track as unknown as SkeletonTrack).elements || []).forEach((element) => {
- if (keyframe in element.shapes) {
- delete element.shapes[keyframe];
- element.updated = Date.now();
- }
- });
+ if (track instanceof SkeletonTrack) {
+ track.elements.forEach((element) => {
+ if (keyframe in element.shapes) {
+ delete element.shapes[keyframe];
+ element.updated = Date.now();
+ }
+ });
+ }
track.updated = Date.now();
}
}
- } else if (track.frame >= startframe) {
- const index = tracks.indexOf(track);
- if (index > -1) { tracks.splice(index, 1); }
+
+ if (Object.keys(track.shapes).length === 0) {
+ this.tracks.splice(this.tracks.indexOf(track), 1);
+ }
+ } else if (track.frame >= from) {
+ this.tracks.splice(this.tracks.indexOf(track), 1);
}
}
});
- } else if (startframe === undefined && endframe === undefined) {
- // If all annotations need to be cleared
- this.shapes = {};
- this.tags = {};
- this.tracks = [];
- this.objects = {};
-
- this.flush = true;
- } else {
- // If inputs provided were wrong
- throw Error('Could not remove the annotations, please provide both inputs or' +
- ' leave the inputs below empty to remove all the annotations from this job');
}
}
diff --git a/cvat-core/src/annotations.ts b/cvat-core/src/annotations.ts
index 8e3b74b4a182..3c63f800426a 100644
--- a/cvat-core/src/annotations.ts
+++ b/cvat-core/src/annotations.ts
@@ -42,7 +42,7 @@ function getSession(session): WeakMapItem {
}
throw new InstanceNotInitializedError(
- 'Session has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
+ 'Session has not been initialized yet. Call annotations.get() or annotations.clear({ reload: true }) before',
);
}
@@ -113,17 +113,24 @@ export async function getAnnotations(session, frame, allTracks, filters): Promis
}
}
-export async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly): Promise {
- checkObjectType('reload', reload, 'boolean', null);
+export async function clearAnnotations(
+ session: Task | Job,
+ options: Parameters[0],
+): Promise {
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
- if (reload) {
- cache.delete(session);
- return getAnnotationsFromServer(session);
+ if (Object.hasOwn(options ?? {}, 'reload')) {
+ const { reload } = options;
+ checkObjectType('reload', reload, 'boolean', null);
+
+ if (reload) {
+ cache.delete(session);
+ return getAnnotationsFromServer(session);
+ }
}
- return getCollection(session).clear(startframe, endframe, delTrackKeyframesOnly);
+ return getCollection(session).clear(options);
}
export async function exportDataset(
diff --git a/cvat-core/src/core-types.ts b/cvat-core/src/core-types.ts
index 0edb2dce84cf..e72e32041f00 100644
--- a/cvat-core/src/core-types.ts
+++ b/cvat-core/src/core-types.ts
@@ -38,7 +38,7 @@ export interface SerializedModel {
labels_v2?: MLModelLabel[];
version?: number;
description?: string;
- kind?: ModelKind;
+ kind?: ModelKind | 'classifier';
type?: string;
return_type?: ModelReturnType;
owner?: any;
diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts
index 226ed8e3df87..5543d4712af4 100644
--- a/cvat-core/src/enums.ts
+++ b/cvat-core/src/enums.ts
@@ -159,7 +159,6 @@ export enum ModelKind {
DETECTOR = 'detector',
INTERACTOR = 'interactor',
TRACKER = 'tracker',
- CLASSIFIER = 'classifier',
REID = 'reid',
}
diff --git a/cvat-core/src/ml-model.ts b/cvat-core/src/ml-model.ts
index a504986c038d..e632328d6250 100644
--- a/cvat-core/src/ml-model.ts
+++ b/cvat-core/src/ml-model.ts
@@ -40,9 +40,19 @@ export default class MLModel {
}
public get kind(): ModelKind {
+ // compatibility alias; TODO: remove this
+ if (this.serialized.kind === 'classifier') return ModelKind.DETECTOR;
return this.serialized.kind;
}
+ public get displayKind(): string {
+ if (this.kind === ModelKind.DETECTOR) {
+ if (this.returnType === ModelReturnType.TAG) return 'classifier';
+ if (this.returnType === ModelReturnType.MASK) return 'segmenter';
+ }
+ return this.kind;
+ }
+
public get params(): ModelParams {
const result: ModelParams = {
canvas: {
diff --git a/cvat-core/src/project-implementation.ts b/cvat-core/src/project-implementation.ts
index 8a274d4ee308..028f535cab61 100644
--- a/cvat-core/src/project-implementation.ts
+++ b/cvat-core/src/project-implementation.ts
@@ -1,140 +1,172 @@
// Copyright (C) 2021-2022 Intel Corporation
-// Copyright (C) 2022-2023 CVAT.ai Corporation
+// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
-import { Storage } from './storage';
import serverProxy from './server-proxy';
import { decodePreview } from './frames';
-import Project from './project';
+import ProjectClass from './project';
import { exportDataset, importDataset } from './annotations';
import { SerializedLabel } from './server-response-types';
import { Label } from './labels';
import AnnotationGuide from './guide';
-export default function implementProject(projectClass) {
- projectClass.prototype.save.implementation = async function () {
- if (typeof this.id !== 'undefined') {
- const projectData = this._updateTrigger.getUpdated(this, {
- bugTracker: 'bug_tracker',
- assignee: 'assignee_id',
- });
+export default function implementProject(Project: typeof ProjectClass): typeof ProjectClass {
+ Object.defineProperty(Project.prototype.save, 'implementation', {
+ value: async function saveImplementation(
+ this: ProjectClass,
+ ): ReturnType {
+ if (typeof this.id !== 'undefined') {
+ const projectData = this._updateTrigger.getUpdated(this, {
+ bugTracker: 'bug_tracker',
+ assignee: 'assignee_id',
+ });
+
+ if (projectData.assignee_id) {
+ projectData.assignee_id = projectData.assignee_id.id;
+ }
- if (projectData.assignee_id) {
- projectData.assignee_id = projectData.assignee_id.id;
- }
+ await Promise.all((projectData.labels || []).map((label: Label): Promise => {
+ if (label.deleted) {
+ return serverProxy.labels.delete(label.id);
+ }
- await Promise.all((projectData.labels || []).map((label: Label): Promise => {
- if (label.deleted) {
- return serverProxy.labels.delete(label.id);
+ if (label.patched) {
+ return serverProxy.labels.update(label.id, label.toJSON());
+ }
+
+ return Promise.resolve();
+ }));
+
+ // leave only new labels to create them via project PATCH request
+ projectData.labels = (projectData.labels || [])
+ .filter((label: SerializedLabel) => !Number.isInteger(label.id)).map((el) => el.toJSON());
+ if (!projectData.labels.length) {
+ delete projectData.labels;
}
- if (label.patched) {
- return serverProxy.labels.update(label.id, label.toJSON());
+ this._updateTrigger.reset();
+ let serializedProject = null;
+ if (Object.keys(projectData).length) {
+ serializedProject = await serverProxy.projects.save(this.id, projectData);
+ } else {
+ [serializedProject] = (await serverProxy.projects.get({ id: this.id }));
}
- return Promise.resolve();
- }));
+ const labels = await serverProxy.labels.get({ project_id: serializedProject.id });
+ return new Project({ ...serializedProject, labels: labels.results });
+ }
+
+ // initial creating
+ const projectSpec: any = {
+ name: this.name,
+ labels: this.labels.map((el) => el.toJSON()),
+ };
+
+ if (this.bugTracker) {
+ projectSpec.bug_tracker = this.bugTracker;
+ }
+
+ if (this.targetStorage) {
+ projectSpec.target_storage = this.targetStorage.toJSON();
+ }
- // leave only new labels to create them via project PATCH request
- projectData.labels = (projectData.labels || [])
- .filter((label: SerializedLabel) => !Number.isInteger(label.id)).map((el) => el.toJSON());
- if (!projectData.labels.length) {
- delete projectData.labels;
+ if (this.sourceStorage) {
+ projectSpec.source_storage = this.sourceStorage.toJSON();
}
- this._updateTrigger.reset();
- let serializedProject = null;
- if (Object.keys(projectData).length) {
- serializedProject = await serverProxy.projects.save(this.id, projectData);
- } else {
- [serializedProject] = (await serverProxy.projects.get({ id: this.id }));
+ const project = await serverProxy.projects.create(projectSpec);
+ const labels = await serverProxy.labels.get({ project_id: project.id });
+ return new Project({ ...project, labels: labels.results });
+ },
+ });
+
+ Object.defineProperty(Project.prototype.delete, 'implementation', {
+ value: function deleteImplementation(
+ this: ProjectClass,
+ ): ReturnType {
+ return serverProxy.projects.delete(this.id);
+ },
+ });
+
+ Object.defineProperty(Project.prototype.preview, 'implementation', {
+ value: function previewImplementation(
+ this: ProjectClass,
+ ): ReturnType {
+ if (this.id === null) {
+ return Promise.resolve('');
}
+ return serverProxy.projects.getPreview(this.id).then((preview) => {
+ if (!preview) {
+ return Promise.resolve('');
+ }
+ return decodePreview(preview);
+ });
+ },
+ });
+
+ Object.defineProperty(Project.prototype.annotations.exportDataset, 'implementation', {
+ value: function exportDatasetImplementation(
+ this: ProjectClass,
+ format: Parameters[0],
+ saveImages: Parameters[1],
+ useDefaultSettings: Parameters[2],
+ targetStorage: Parameters[3],
+ customName: Parameters[4],
+ ): ReturnType {
+ return exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
+ },
+ });
+
+ Object.defineProperty(Project.prototype.annotations.importDataset, 'implementation', {
+ value: function importDatasetImplementation(
+ this: ProjectClass,
+ format: Parameters[0],
+ useDefaultSettings: Parameters[1],
+ sourceStorage: Parameters[2],
+ file: Parameters[3],
+ options: Parameters[4],
+ ): ReturnType {
+ return importDataset(this, format, useDefaultSettings, sourceStorage, file, options);
+ },
+ });
+
+ Object.defineProperty(Project.prototype.backup, 'implementation', {
+ value: function backupImplementation(
+ this: ProjectClass,
+ targetStorage: Parameters[0],
+ useDefaultSettings: Parameters[1],
+ fileName: Parameters[2],
+ ): ReturnType {
+ return serverProxy.projects.backup(this.id, targetStorage, useDefaultSettings, fileName);
+ },
+ });
+
+ Object.defineProperty(Project.restore, 'implementation', {
+ value: async function restoreImplementation(
+ this: ProjectClass,
+ storage: Parameters[0],
+ file: Parameters[1],
+ ): ReturnType {
+ const serializedProject = await serverProxy.projects.restore(storage, file);
const labels = await serverProxy.labels.get({ project_id: serializedProject.id });
return new Project({ ...serializedProject, labels: labels.results });
- }
-
- // initial creating
- const projectSpec: any = {
- name: this.name,
- labels: this.labels.map((el) => el.toJSON()),
- };
-
- if (this.bugTracker) {
- projectSpec.bug_tracker = this.bugTracker;
- }
-
- if (this.targetStorage) {
- projectSpec.target_storage = this.targetStorage.toJSON();
- }
-
- if (this.sourceStorage) {
- projectSpec.source_storage = this.sourceStorage.toJSON();
- }
-
- const project = await serverProxy.projects.create(projectSpec);
- const labels = await serverProxy.labels.get({ project_id: project.id });
- return new Project({ ...project, labels: labels.results });
- };
-
- projectClass.prototype.delete.implementation = async function () {
- const result = await serverProxy.projects.delete(this.id);
- return result;
- };
-
- projectClass.prototype.preview.implementation = async function (this: Project): Promise {
- if (this.id === null) return '';
- const preview = await serverProxy.projects.getPreview(this.id);
- if (!preview) return '';
- return decodePreview(preview);
- };
-
- projectClass.prototype.annotations.exportDataset.implementation = async function (
- format: string,
- saveImages: boolean,
- useDefaultSettings: boolean,
- targetStorage: Storage,
- customName?: string,
- ) {
- const result = exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
- return result;
- };
- projectClass.prototype.annotations.importDataset.implementation = async function (
- format: string,
- useDefaultSettings: boolean,
- sourceStorage: Storage,
- file: File | string,
- options?: {
- convMaskToPoly?: boolean,
- updateStatusCallback?: (s: string, n: number) => void,
},
- ) {
- return importDataset(this, format, useDefaultSettings, sourceStorage, file, options);
- };
-
- projectClass.prototype.backup.implementation = async function (
- targetStorage: Storage,
- useDefaultSettings: boolean,
- fileName?: string,
- ) {
- const result = await serverProxy.projects.backup(this.id, targetStorage, useDefaultSettings, fileName);
- return result;
- };
-
- projectClass.restore.implementation = async function (storage: Storage, file: File | string) {
- const result = await serverProxy.projects.restore(storage, file);
- return result;
- };
-
- projectClass.prototype.guide.implementation = async function guide() {
- if (this.guideId === null) {
- return null;
- }
-
- const result = await serverProxy.guides.get(this.guideId);
- return new AnnotationGuide(result);
- };
-
- return projectClass;
+ });
+
+ Object.defineProperty(Project.prototype.guide, 'implementation', {
+ value: async function guideImplementation(
+ this: ProjectClass,
+ ): ReturnType {
+ if (this.guideId === null) {
+ return null;
+ }
+
+ const result = await serverProxy.guides.get(this.guideId);
+ return new AnnotationGuide(result);
+ },
+ });
+
+ return Project;
}
diff --git a/cvat-core/src/project.ts b/cvat-core/src/project.ts
index fd147472837d..d96f7361b610 100644
--- a/cvat-core/src/project.ts
+++ b/cvat-core/src/project.ts
@@ -1,5 +1,5 @@
// Copyright (C) 2019-2022 Intel Corporation
-// Copyright (C) 2022-2023 CVAT.ai Corporation
+// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
@@ -31,8 +31,23 @@ export default class Project {
public readonly targetStorage: Storage;
public labels: Label[];
public annotations: {
- exportDataset: CallableFunction;
- importDataset: CallableFunction;
+ exportDataset: (
+ format: string,
+ saveImages: boolean,
+ useDefaultSettings: boolean,
+ targetStorage: Storage,
+ name?: string,
+ ) => Promise;
+ importDataset: (
+ format: string,
+ useDefaultSettings: boolean,
+ sourceStorage: Storage,
+ file: File | string,
+ options?: {
+ convMaskToPoly?: boolean,
+ updateStatusCallback?: (s: string, n: number) => void,
+ },
+ ) => Promise;
};
constructor(initialData: Readonly) {
@@ -198,30 +213,29 @@ export default class Project {
);
// When we call a function, for example: project.annotations.get()
- // In the method get we lose the project context
- // So, we need to bind it
+ // In the method get we lose the project context, so, we need to bind it
this.annotations = {
exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this),
importDataset: Object.getPrototypeOf(this).annotations.importDataset.bind(this),
};
}
- async preview() {
+ async preview(): Promise {
const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.preview);
return result;
}
- async save() {
+ async save(): Promise {
const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.save);
return result;
}
- async delete() {
+ async delete(): Promise {
const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.delete);
return result;
}
- async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) {
+ async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string): Promise {
const result = await PluginRegistry.apiWrapper.call(
this,
Project.prototype.backup,
@@ -232,7 +246,7 @@ export default class Project {
return result;
}
- static async restore(storage: Storage, file: File | string) {
+ static async restore(storage: Storage, file: File | string): Promise {
const result = await PluginRegistry.apiWrapper.call(this, Project.restore, storage, file);
return result;
}
@@ -249,11 +263,11 @@ Object.defineProperties(
annotations: Object.freeze({
value: {
async exportDataset(
- format: string,
- saveImages: boolean,
- useDefaultSettings: boolean,
- targetStorage: Storage,
- customName?: string,
+ format: Parameters[0],
+ saveImages: Parameters[1],
+ useDefaultSettings: Parameters[2],
+ targetStorage: Parameters[3],
+ customName: Parameters[4],
) {
const result = await PluginRegistry.apiWrapper.call(
this,
@@ -267,14 +281,11 @@ Object.defineProperties(
return result;
},
async importDataset(
- format: string,
- useDefaultSettings: boolean,
- sourceStorage: Storage,
- file: File | string,
- options?: {
- convMaskToPoly?: boolean,
- updateStatusCallback?: (s: string, n: number) => void,
- },
+ format: Parameters[0],
+ useDefaultSettings: Parameters[1],
+ sourceStorage: Parameters[2],
+ file: Parameters[3],
+ options: Parameters[4],
) {
const result = await PluginRegistry.apiWrapper.call(
this,
diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts
index 7a38b48dd401..c59354b4d242 100644
--- a/cvat-core/src/server-proxy.ts
+++ b/cvat-core/src/server-proxy.ts
@@ -1004,7 +1004,7 @@ async function backupTask(id: number, targetStorage: Storage, useDefaultSettings
});
}
-async function restoreTask(storage: Storage, file: File | string) {
+async function restoreTask(storage: Storage, file: File | string): Promise {
const { backendAPI } = config;
// keep current default params to 'freeze" them during this request
const params: Params = {
@@ -1110,7 +1110,7 @@ async function backupProject(
});
}
-async function restoreProject(storage: Storage, file: File | string) {
+async function restoreProject(storage: Storage, file: File | string): Promise {
const { backendAPI } = config;
// keep current default params to 'freeze" them during this request
const params: Params = {
diff --git a/cvat-core/src/session-implementation.ts b/cvat-core/src/session-implementation.ts
index 124424f78fb8..b26a2675804f 100644
--- a/cvat-core/src/session-implementation.ts
+++ b/cvat-core/src/session-implementation.ts
@@ -5,8 +5,7 @@
import { omit } from 'lodash';
import { ArgumentError } from './exceptions';
-import { HistoryActions, JobType, RQStatus } from './enums';
-import { Storage } from './storage';
+import { HistoryActions, JobType } from './enums';
import { Task as TaskClass, Job as JobClass } from './session';
import logger from './logger';
import serverProxy from './server-proxy';
@@ -54,781 +53,1165 @@ async function restoreFrameWrapper(jobID, frame): Promise {
}, redo, [], frame);
}
-export function implementJob(Job) {
- Job.prototype.save.implementation = async function (additionalData: any) {
- if (this.id) {
- const jobData = this._updateTrigger.getUpdated(this);
- if (jobData.assignee) {
- jobData.assignee = jobData.assignee.id;
- }
-
- let updatedJob = null;
- try {
- const data = await serverProxy.jobs.save(this.id, jobData);
- updatedJob = new Job(data);
- } catch (error) {
- updatedJob = new Job(this._initialData);
- throw error;
- } finally {
- this.stage = updatedJob.stage;
- this.state = updatedJob.state;
- this.assignee = updatedJob.assignee;
- this._updateTrigger.reset();
- }
-
- return this;
- }
-
- const jobSpec = {
- ...(this.assignee ? { assignee: this.assignee.id } : {}),
- ...(this.stage ? { stage: this.stage } : {}),
- ...(this.state ? { stage: this.state } : {}),
- type: this.type,
- task_id: this.taskId,
- };
- const job = await serverProxy.jobs.create({ ...jobSpec, ...additionalData });
- return new Job(job);
- };
-
- Job.prototype.delete.implementation = async function () {
- if (this.type !== JobType.GROUND_TRUTH) {
- throw new Error('Only ground truth job can be deleted');
- }
- const result = await serverProxy.jobs.delete(this.id);
- return result;
- };
-
- Job.prototype.issues.implementation = async function () {
- const result = await serverProxy.issues.get({ job_id: 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,
- (chunkNumber, quality) => this.frames.chunk(chunkNumber, quality),
- );
- 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.cachedChunks.implementation = async function () {
- const cachedChunks = await getCachedChunks(this.id);
- return cachedChunks;
- };
-
- Job.prototype.frames.preview.implementation = async function (this: JobClass): Promise {
- if (this.id === null || this.taskId === null) return '';
- const preview = await serverProxy.jobs.getPreview(this.id);
- if (!preview) return '';
- return decodePreview(preview);
- };
-
- Job.prototype.frames.contextImage.implementation = async function (frameId) {
- const result = await getContextImage(this.id, frameId);
- return result;
- };
-
- Job.prototype.frames.chunk.implementation = async function (chunkNumber, quality) {
- const result = await serverProxy.frames.getData(this.id, chunkNumber, quality);
- 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');
- }
-
- return findFrame(this.id, frameFrom, frameTo, filters);
- };
-
- 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`);
- }
+export function implementJob(Job: typeof JobClass): typeof JobClass {
+ Object.defineProperty(Job.prototype.save, 'implementation', {
+ value: async function saveImplementation(
+ this: JobClass,
+ additionalData: any,
+ ): ReturnType {
+ if (this.id) {
+ const jobData = this._updateTrigger.getUpdated(this);
+ if (jobData.assignee) {
+ jobData.assignee = jobData.assignee.id;
+ }
- const annotationsData = await getAnnotations(this, frame, allTracks, filters);
- const deletedFrames = await getDeletedFrames('job', this.id);
- if (frame in deletedFrames) {
- return [];
- }
+ let updatedJob = null;
+ try {
+ const data = await serverProxy.jobs.save(this.id, jobData);
+ updatedJob = new Job(data);
+ } catch (error) {
+ updatedJob = new Job(this._initialData);
+ throw error;
+ } finally {
+ this.stage = updatedJob.stage;
+ this.state = updatedJob.state;
+ this.assignee = updatedJob.assignee;
+ this._updateTrigger.reset();
+ }
- return annotationsData;
- };
+ return this;
+ }
- Job.prototype.annotations.search.implementation = function (frameFrom, frameTo, searchParameters) {
- if ('annotationsFilters' in searchParameters && !Array.isArray(searchParameters.annotationsFilters)) {
- throw new ArgumentError('Annotations filters must be an array');
- }
+ const jobSpec = {
+ ...(this.assignee ? { assignee: this.assignee.id } : {}),
+ ...(this.stage ? { stage: this.stage } : {}),
+ ...(this.state ? { stage: this.state } : {}),
+ type: this.type,
+ task_id: this.taskId,
+ };
+ const job = await serverProxy.jobs.create({ ...jobSpec, ...additionalData });
+ return new Job(job);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.delete, 'implementation', {
+ value: async function deleteImplementation(
+ this: JobClass,
+ ): ReturnType {
+ if (this.type !== JobType.GROUND_TRUTH) {
+ throw new Error('Only ground truth job can be deleted');
+ }
- if ('generalFilters' in searchParameters && typeof searchParameters.generalFilters.isEmptyFrame !== 'boolean') {
- throw new ArgumentError('General filter isEmptyFrame must be a boolean');
- }
+ return serverProxy.jobs.delete(this.id);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.issues, 'implementation', {
+ value: function issuesImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return serverProxy.issues.get({ job_id: this.id })
+ .then((issues) => issues.map((issue) => new Issue(issue)));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.openIssue, 'implementation', {
+ value: async function openIssueImplementation(
+ this: JobClass,
+ issue: Parameters[0],
+ message: Parameters[1],
+ ): ReturnType {
+ checkObjectType('issue', issue, null, Issue);
+ checkObjectType('message', message, 'string');
+ const result = await serverProxy.issues.create({
+ ...issue.serialize(),
+ message,
+ });
+ return new Issue(result);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.close, 'implementation', {
+ value: function closeImplementation(
+ this: JobClass,
+ ) {
+ clearFrames(this.id);
+ clearCache(this);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.guide, 'implementation', {
+ value: async function guideImplementation(
+ this: JobClass,
+ ): ReturnType {
+ if (this.guideId === null) {
+ return null;
+ }
- if ('annotationsFilters' in searchParameters && 'generalFilters' in searchParameters) {
- throw new ArgumentError('Both annotations filters and general fiters could not be used together');
- }
+ const result = await serverProxy.guides.get(this.guideId);
+ return new AnnotationGuide(result);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.get, 'implementation', {
+ value: function getFrameImplementation(
+ this: JobClass,
+ frame: Parameters[0],
+ isPlaying: Parameters[1],
+ step: Parameters[2],
+ ): ReturnType {
+ if (!Number.isInteger(frame) || frame < 0) {
+ throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
+ }
- if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
- throw new ArgumentError('The start and end frames both must be an integer');
- }
+ if (frame < this.startFrame || frame > this.stopFrame) {
+ throw new ArgumentError(`The frame with number ${frame} is out of the job`);
+ }
- if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
- throw new ArgumentError('The start frame is out of the job');
- }
+ return getFrame(
+ this.id,
+ this.dataChunkSize,
+ this.dataChunkType,
+ this.mode,
+ frame,
+ this.startFrame,
+ this.stopFrame,
+ isPlaying,
+ step,
+ this.dimension,
+ (chunkNumber, quality) => this.frames.chunk(chunkNumber, quality),
+ );
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.delete, 'implementation', {
+ value: function deleteFrameImplementation(
+ this: JobClass,
+ frame: Parameters[0],
+ ): ReturnType {
+ if (!Number.isInteger(frame)) {
+ throw new Error(`Frame must be an integer. Got: "${frame}"`);
+ }
- if (frameTo < this.startFrame || frameTo > this.stopFrame) {
- throw new ArgumentError('The stop frame is out of the job');
- }
+ if (frame < this.startFrame || frame > this.stopFrame) {
+ throw new Error('The frame is out of the job');
+ }
- return getCollection(this).search(frameFrom, frameTo, searchParameters);
- };
+ return deleteFrameWrapper.call(this, this.id, frame);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.restore, 'implementation', {
+ value: function restoreFrameImplementation(
+ this: JobClass,
+ frame: Parameters[0],
+ ): ReturnType {
+ if (!Number.isInteger(frame)) {
+ throw new Error(`Frame must be an integer. Got: "${frame}"`);
+ }
- Job.prototype.annotations.save.implementation = async function (onUpdate) {
- return getSaver(this).save(onUpdate);
- };
+ if (frame < this.startFrame || frame > this.stopFrame) {
+ throw new Error('The frame is out of the job');
+ }
- Job.prototype.annotations.merge.implementation = async function (objectStates) {
- return getCollection(this).merge(objectStates);
- };
+ return restoreFrameWrapper.call(this, this.id, frame);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.save, 'implementation', {
+ value: function saveFramesImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return patchMeta(this.id);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.cachedChunks, 'implementation', {
+ value: function cachedChunksImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return Promise.resolve(getCachedChunks(this.id));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.preview, 'implementation', {
+ value: function previewImplementation(
+ this: JobClass,
+ ): ReturnType {
+ if (this.id === null || this.taskId === null) {
+ return Promise.resolve('');
+ }
- Job.prototype.annotations.split.implementation = async function (objectState, frame) {
- return getCollection(this).split(objectState, frame);
- };
+ return serverProxy.jobs.getPreview(this.id).then((preview) => {
+ if (!preview) {
+ return Promise.resolve('');
+ }
+ return decodePreview(preview);
+ });
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.contextImage, 'implementation', {
+ value: function contextImageImplementation(
+ this: JobClass,
+ frameId: Parameters[0],
+ ): ReturnType {
+ return getContextImage(this.id, frameId);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.chunk, 'implementation', {
+ value: function chunkImplementation(
+ this: JobClass,
+ chunkNumber: Parameters[0],
+ quality: Parameters[1],
+ ): ReturnType {
+ return serverProxy.frames.getData(this.id, chunkNumber, quality);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.frames.search, 'implementation', {
+ value: function searchFrameImplementation(
+ this: JobClass,
+ filters: Parameters[0],
+ frameFrom: Parameters[1],
+ frameTo: Parameters[2],
+ ): ReturnType {
+ if (typeof filters !== 'object') {
+ throw new ArgumentError('Filters should be an object');
+ }
- Job.prototype.annotations.group.implementation = async function (objectStates, reset) {
- return getCollection(this).group(objectStates, reset);
- };
+ if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
+ throw new ArgumentError('The start and end frames both must be an integer');
+ }
- Job.prototype.annotations.join.implementation = async function (objectStates, points) {
- return getCollection(this).join(objectStates, points);
- };
+ if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
+ throw new ArgumentError('The start frame is out of the job');
+ }
- Job.prototype.annotations.slice.implementation = async function (objectState, results) {
- return getCollection(this).slice(objectState, results);
- };
+ if (frameTo < this.startFrame || frameTo > this.stopFrame) {
+ throw new ArgumentError('The stop frame is out of the job');
+ }
- Job.prototype.annotations.hasUnsavedChanges.implementation = function () {
- return getSaver(this).hasUnsavedChanges();
- };
+ return findFrame(this.id, frameFrom, frameTo, filters);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.get, 'implementation', {
+ value: async function getAnnotationsImplementation(
+ this: JobClass,
+ frame: Parameters[0],
+ allTracks: Parameters[1],
+ filters: Parameters[2],
+ ): ReturnType {
+ if (!Array.isArray(filters)) {
+ throw new ArgumentError('Filters must be an array');
+ }
- Job.prototype.annotations.clear.implementation = async function (
- reload, startframe, endframe, delTrackKeyframesOnly,
- ) {
- const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
- return result;
- };
+ if (!Number.isInteger(frame)) {
+ throw new ArgumentError('The frame argument must be an integer');
+ }
- Job.prototype.annotations.select.implementation = function (objectStates, x, y) {
- return getCollection(this).select(objectStates, x, y);
- };
+ if (frame < this.startFrame || frame > this.stopFrame) {
+ throw new ArgumentError(`Frame ${frame} does not exist in the job`);
+ }
- Job.prototype.annotations.statistics.implementation = function () {
- return getCollection(this).statistics();
- };
+ const annotationsData = await getAnnotations(this, frame, allTracks, filters);
+ const deletedFrames = await getDeletedFrames('job', this.id);
+ if (frame in deletedFrames) {
+ return [];
+ }
- Job.prototype.annotations.put.implementation = function (objectStates) {
- return getCollection(this).put(objectStates);
- };
+ return annotationsData;
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.search, 'implementation', {
+ value: function searchAnnotationsImplementation(
+ this: JobClass,
+ frameFrom: Parameters[0],
+ frameTo: Parameters[1],
+ searchParameters: Parameters[2],
+ ): ReturnType {
+ if ('annotationsFilters' in searchParameters && !Array.isArray(searchParameters.annotationsFilters)) {
+ throw new ArgumentError('Annotations filters must be an array');
+ }
- 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;
- };
+ if ('generalFilters' in searchParameters && typeof searchParameters.generalFilters.isEmptyFrame !== 'boolean') {
+ throw new ArgumentError('General filter isEmptyFrame must be a boolean');
+ }
- Job.prototype.annotations.import.implementation = function (data) {
- return getCollection(this).import(data);
- };
+ if ('annotationsFilters' in searchParameters && 'generalFilters' in searchParameters) {
+ throw new ArgumentError('Both annotations filters and general fiters could not be used together');
+ }
- Job.prototype.annotations.export.implementation = function () {
- return getCollection(this).export();
- };
+ if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
+ throw new ArgumentError('The start and end frames both must be an integer');
+ }
- 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;
- };
+ if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
+ throw new ArgumentError('The start frame is out of the job');
+ }
- Job.prototype.actions.undo.implementation = async function (count) {
- return getHistory(this).undo(count);
- };
+ if (frameTo < this.startFrame || frameTo > this.stopFrame) {
+ throw new ArgumentError('The stop frame is out of the job');
+ }
- Job.prototype.actions.redo.implementation = async function (count) {
- return getHistory(this).redo(count);
- };
+ return Promise.resolve(
+ getCollection(this).search(frameFrom, frameTo, searchParameters),
+ );
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.save, 'implementation', {
+ value: async function saveAnnotationsImplementation(
+ this: JobClass,
+ onUpdate: Parameters[0],
+ ): ReturnType {
+ return getSaver(this).save(onUpdate);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.merge, 'implementation', {
+ value: function mergeAnnotationsImplementation(
+ this: JobClass,
+ objectStates: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).merge(objectStates));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.split, 'implementation', {
+ value: function splitAnnotationsImplementation(
+ this: JobClass,
+ objectState: Parameters[0],
+ frame: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).split(objectState, frame));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.group, 'implementation', {
+ value: function groupAnnotationsImplementation(
+ this: JobClass,
+ objectStates: Parameters[0],
+ reset: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).group(objectStates, reset));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.join, 'implementation', {
+ value: function joinAnnotationsImplementation(
+ this: JobClass,
+ objectStates: Parameters[0],
+ points: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).join(objectStates, points));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.slice, 'implementation', {
+ value: function sliceAnnotationsImplementation(
+ this: JobClass,
+ objectState: Parameters[0],
+ results: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).slice(objectState, results));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.hasUnsavedChanges, 'implementation', {
+ value: function hasUnsavedChangesImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return getSaver(this).hasUnsavedChanges();
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.clear, 'implementation', {
+ value: function clearAnnotationsImplementation(
+ this: JobClass,
+ options: Parameters[0],
+ ): ReturnType {
+ return clearAnnotations(this, options);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.select, 'implementation', {
+ value: function selectAnnotationsImplementation(
+ this: JobClass,
+ objectStates: Parameters[0],
+ x: Parameters[1],
+ y: Parameters[2],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).select(objectStates, x, y));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.statistics, 'implementation', {
+ value: function statisticsImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).statistics());
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.put, 'implementation', {
+ value: function putAnnotationsImplementation(
+ this: JobClass,
+ objectStates: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).put(objectStates));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.import, 'implementation', {
+ value: function importAnnotationsImplementation(
+ this: JobClass,
+ data: Parameters[0],
+ ): ReturnType {
+ getCollection(this).import(data);
+ return Promise.resolve();
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.export, 'implementation', {
+ value: function exportAnnotationsImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).export());
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.upload, 'implementation', {
+ value: async function uploadAnnotationsImplementation(
+ this: JobClass,
+ format: Parameters[0],
+ useDefaultLocation: Parameters[1],
+ sourceStorage: Parameters[2],
+ file: Parameters[3],
+ options: Parameters[4],
+ ): ReturnType {
+ return importDataset(this, format, useDefaultLocation, sourceStorage, file, options);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.annotations.exportDataset, 'implementation', {
+ value: async function exportDatasetImplementation(
+ this: JobClass,
+ format: Parameters[0],
+ saveImages: Parameters[1],
+ useDefaultSettings: Parameters[2],
+ targetStorage: Parameters[3],
+ customName?: Parameters[4],
+ ): ReturnType {
+ return exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.actions.undo, 'implementation', {
+ value: async function undoActionImplementation(
+ this: JobClass,
+ count: Parameters[0],
+ ): ReturnType {
+ return getHistory(this).undo(count);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.actions.redo, 'implementation', {
+ value: async function redoActionImplementation(
+ this: JobClass,
+ count: Parameters[0],
+ ): ReturnType {
+ return getHistory(this).redo(count);
+ },
+ });
+
+ Object.defineProperty(Job.prototype.actions.freeze, 'implementation', {
+ value: function freezeActionsImplementation(
+ this: JobClass,
+ frozen: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).freeze(frozen));
+ },
+ });
+
+ Object.defineProperty(Job.prototype.actions.clear, 'implementation', {
+ value: function clearActionsImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).clear());
+ },
+ });
+
+ Object.defineProperty(Job.prototype.actions.get, 'implementation', {
+ value: function getActionsImplementation(
+ this: JobClass,
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).get());
+ },
+ });
+
+ Object.defineProperty(Job.prototype.logger.log, 'implementation', {
+ value: async function logImplementation(
+ this: JobClass,
+ scope: Parameters[0],
+ payload: Parameters[1],
+ wait: Parameters[2],
+ ): ReturnType {
+ return logger.log(
+ scope,
+ {
+ ...payload,
+ project_id: this.projectId,
+ task_id: this.taskId,
+ job_id: this.id,
+ },
+ wait,
+ );
+ },
+ });
- Job.prototype.actions.freeze.implementation = function (frozen) {
- return getHistory(this).freeze(frozen);
- };
+ return Job;
+}
- Job.prototype.actions.clear.implementation = function () {
- return getHistory(this).clear();
- };
+export function implementTask(Task: typeof TaskClass): typeof TaskClass {
+ Object.defineProperty(Task.prototype.close, 'implementation', {
+ value: function closeImplementation(
+ this: TaskClass,
+ ) {
+ for (const job of this.jobs) {
+ clearFrames(job.id);
+ clearCache(job);
+ }
- Job.prototype.actions.get.implementation = function () {
- return getHistory(this).get();
- };
+ clearCache(this);
+ },
+ });
- Job.prototype.logger.log.implementation = async function (scope, payload, wait) {
- const result = await logger.log(
- scope,
- {
- ...payload,
- project_id: this.projectId,
- task_id: this.taskId,
- job_id: this.id,
- },
- wait,
- );
- return result;
- };
+ Object.defineProperty(Task.prototype.guide, 'implementation', {
+ value: async function guideImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ if (this.guideId === null) {
+ return null;
+ }
- Job.prototype.close.implementation = function closeTask() {
- clearFrames(this.id);
- clearCache(this);
- return this;
- };
+ const result = await serverProxy.guides.get(this.guideId);
+ return new AnnotationGuide(result);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.save, 'implementation', {
+ value: async function saveImplementation(
+ this: TaskClass,
+ onUpdate: Parameters[0],
+ ): ReturnType {
+ 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;
+ }
- Job.prototype.guide.implementation = async function guide() {
- if (this.guideId === null) {
- return null;
- }
+ for await (const label of taskData.labels || []) {
+ if (label.deleted) {
+ await serverProxy.labels.delete(label.id);
+ } else if (label.patched) {
+ await serverProxy.labels.update(label.id, label.toJSON());
+ }
+ }
- const result = await serverProxy.guides.get(this.guideId);
- return new AnnotationGuide(result);
- };
+ // leave only new labels to create them via task PATCH request
+ taskData.labels = (taskData.labels || [])
+ .filter((label: SerializedLabel) => !Number.isInteger(label.id)).map((el) => el.toJSON());
+ if (!taskData.labels.length) {
+ delete taskData.labels;
+ }
- return Job;
-}
+ this._updateTrigger.reset();
-export function implementTask(Task) {
- Task.prototype.close.implementation = function closeTask() {
- for (const job of this.jobs) {
- clearFrames(job.id);
- clearCache(job);
- }
+ let serializedTask: SerializedTask = null;
+ if (Object.keys(taskData).length) {
+ serializedTask = await serverProxy.tasks.save(this.id, taskData);
+ } else {
+ [serializedTask] = (await serverProxy.tasks.get({ id: this.id }));
+ }
- clearCache(this);
- return this;
- };
+ const labels = await serverProxy.labels.get({ task_id: this.id });
+ const jobs = await serverProxy.jobs.get({ task_id: this.id }, true);
+ return new Task({
+ ...omit(serializedTask, ['jobs', 'labels']),
+ progress: serializedTask.jobs,
+ jobs,
+ labels: labels.results,
+ });
+ }
- Task.prototype.save.implementation = async function (onUpdate) {
- 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',
- });
+ const taskSpec: any = {
+ name: this.name,
+ labels: this.labels.map((el) => el.toJSON()),
+ };
- if (taskData.assignee_id) {
- taskData.assignee_id = taskData.assignee_id.id;
+ 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;
}
- for await (const label of taskData.labels || []) {
- if (label.deleted) {
- await serverProxy.labels.delete(label.id);
- } else if (label.patched) {
- await serverProxy.labels.update(label.id, label.toJSON());
- }
+ if (this.targetStorage) {
+ taskSpec.target_storage = this.targetStorage.toJSON();
}
- // leave only new labels to create them via task PATCH request
- taskData.labels = (taskData.labels || [])
- .filter((label: SerializedLabel) => !Number.isInteger(label.id)).map((el) => el.toJSON());
- if (!taskData.labels.length) {
- delete taskData.labels;
+ if (this.sourceStorage) {
+ taskSpec.source_storage = this.sourceStorage.toJSON();
}
- this._updateTrigger.reset();
+ 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,
+ ...(typeof this.startFrame !== 'undefined' ? { start_frame: this.startFrame } : {}),
+ ...(typeof this.stopFrame !== 'undefined' ? { stop_frame: this.stopFrame } : {}),
+ ...(typeof this.frameFilter !== 'undefined' ? { frame_filter: this.frameFilter } : {}),
+ ...(typeof this.dataChunkSize !== 'undefined' ? { chunk_size: this.dataChunkSize } : {}),
+ ...(typeof this.copyData !== 'undefined' ? { copy_data: this.copyData } : {}),
+ ...(typeof this.cloudStorageId !== 'undefined' ? { cloud_storage_id: this.cloudStorageId } : {}),
+ };
+
+ const task = await serverProxy.tasks.create(taskSpec, taskDataSpec, onUpdate);
+ const labels = await serverProxy.labels.get({ task_id: task.id });
+ const jobs = await serverProxy.jobs.get({
+ filter: JSON.stringify({ and: [{ '==': [{ var: 'task_id' }, task.id] }] }),
+ }, true);
- let serializedTask: SerializedTask = null;
- if (Object.keys(taskData).length) {
- serializedTask = await serverProxy.tasks.save(this.id, taskData);
- } else {
- [serializedTask] = (await serverProxy.tasks.get({ id: this.id }));
+ return new Task({
+ ...omit(task, ['jobs', 'labels']),
+ jobs,
+ progress: task.jobs,
+ labels: labels.results,
+ });
+ },
+ });
+
+ Object.defineProperty(Task.prototype.listenToCreate, 'implementation', {
+ value: async function listenToCreateImplementation(
+ this: TaskClass,
+ onUpdate: Parameters[0],
+ ): ReturnType {
+ if (Number.isInteger(this.id) && this.size === 0) {
+ const serializedTask = await serverProxy.tasks.listenToCreate(this.id, onUpdate);
+ return new Task(omit(serializedTask, ['labels', 'jobs']));
}
- const labels = await serverProxy.labels.get({ task_id: this.id });
- const jobs = await serverProxy.jobs.get({ task_id: this.id }, true);
+ return this;
+ },
+ });
+
+ Object.defineProperty(Task.prototype.delete, 'implementation', {
+ value: function deleteImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return serverProxy.tasks.delete(this.id);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.issues, 'implementation', {
+ value: function issuesImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return serverProxy.issues.get({ task_id: this.id })
+ .then((issues) => issues.map((issue) => new Issue(issue)));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.backup, 'implementation', {
+ value: function backupImplementation(
+ this: TaskClass,
+ targetStorage: Parameters[0],
+ useDefaultSettings: Parameters[1],
+ fileName: Parameters[2],
+ ): ReturnType {
+ return serverProxy.tasks.backup(this.id, targetStorage, useDefaultSettings, fileName);
+ },
+ });
+
+ Object.defineProperty(Task.restore, 'implementation', {
+ value: async function restoreImplementation(
+ this: TaskClass,
+ storage: Parameters[0],
+ file: Parameters[1],
+ ): ReturnType {
+ const serializedTask = await serverProxy.tasks.restore(storage, file);
+ // When request task by ID we also need to add labels and jobs to work with them
+ const labels = await serverProxy.labels.get({ task_id: serializedTask.id });
+ const jobs = await serverProxy.jobs.get({ task_id: serializedTask.id }, true);
return new Task({
...omit(serializedTask, ['jobs', 'labels']),
progress: serializedTask.jobs,
jobs,
labels: labels.results,
});
- }
-
- 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,
- ...(typeof this.startFrame !== 'undefined' ? { start_frame: this.startFrame } : {}),
- ...(typeof this.stopFrame !== 'undefined' ? { stop_frame: this.stopFrame } : {}),
- ...(typeof this.frameFilter !== 'undefined' ? { frame_filter: this.frameFilter } : {}),
- ...(typeof this.dataChunkSize !== 'undefined' ? { chunk_size: this.dataChunkSize } : {}),
- ...(typeof this.copyData !== 'undefined' ? { copy_data: this.copyData } : {}),
- ...(typeof this.cloudStorageId !== 'undefined' ? { cloud_storage_id: this.cloudStorageId } : {}),
- };
-
- const task = await serverProxy.tasks.create(taskSpec, taskDataSpec, onUpdate);
- const labels = await serverProxy.labels.get({ task_id: task.id });
- const jobs = await serverProxy.jobs.get({
- filter: JSON.stringify({ and: [{ '==': [{ var: 'task_id' }, task.id] }] }),
- }, true);
-
- return new Task({
- ...omit(task, ['jobs', 'labels']),
- jobs,
- progress: task.jobs,
- labels: labels.results,
- });
- };
-
- Task.prototype.listenToCreate.implementation = async function (
- onUpdate: (state: RQStatus, progress: number, message: string) => void = () => {},
- ): Promise {
- if (Number.isInteger(this.id) && this.size === 0) {
- const serializedTask = await serverProxy.tasks.listenToCreate(this.id, onUpdate);
- return new Task(omit(serializedTask, ['labels', 'jobs']));
- }
-
- return this;
- };
-
- Task.prototype.delete.implementation = async function () {
- const result = await serverProxy.tasks.delete(this.id);
- return result;
- };
-
- Task.prototype.issues.implementation = async function () {
- const result = await serverProxy.issues.get({ task_id: this.id });
- return result.map((issue) => new Issue(issue));
- };
-
- 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,
- this.dimension,
- (chunkNumber, quality) => job.frames.chunk(chunkNumber, quality),
- );
- return result;
- };
-
- Task.prototype.frames.cachedChunks.implementation = async function () {
- let chunks = [];
- for (const job of this.jobs) {
- const cachedChunks = await getCachedChunks(job.id);
- chunks = chunks.concat(cachedChunks);
- }
- return Array.from(new Set(chunks));
- };
-
- Task.prototype.frames.preview.implementation = async function (this: TaskClass): Promise {
- if (this.id === null) return '';
- const preview = await serverProxy.tasks.getPreview(this.id);
- if (!preview) return '';
- return decodePreview(preview);
- };
-
- 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');
- }
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.get, 'implementation', {
+ value: async function getFrameImplementation(
+ this: TaskClass,
+ frame: Parameters[0],
+ isPlaying: Parameters[1],
+ step: Parameters[2],
+ ): ReturnType {
+ if (!Number.isInteger(frame) || frame < 0) {
+ throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
+ }
- 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 (frame >= this.size) {
+ throw new ArgumentError(`The frame with number ${frame} is out of the task`);
+ }
- for (const job of jobs) {
- const result = await findFrame(
- job.id, Math.max(frameFrom, job.startFrame), Math.min(frameTo, job.stopFrame), filters,
+ 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,
+ this.dimension,
+ (chunkNumber, quality) => job.frames.chunk(chunkNumber, quality),
);
+ return result;
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.cachedChunks, 'implementation', {
+ value: async function cachedChunksImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ throw new Error('Not implemented for Task');
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.preview, 'implementation', {
+ value: function previewImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ if (this.id === null) {
+ return Promise.resolve('');
+ }
- if (result !== null) return result;
- }
-
- return null;
- };
-
- Task.prototype.frames.contextImage.implementation = async function () {
- throw new Error('Not implemented');
- };
-
- Task.prototype.frames.chunk.implementation = async function () {
- throw new Error('Not implemented');
- };
-
- // 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 (frameFrom, frameTo, searchParameters) {
- if ('annotationsFilters' in searchParameters && !Array.isArray(searchParameters.annotationsFilters)) {
- throw new ArgumentError('Annotations filters must be an array');
- }
-
- if ('generalFilters' in searchParameters && typeof searchParameters.generalFilters.isEmptyFrame !== 'boolean') {
- throw new ArgumentError('General filter isEmptyFrame must be a boolean');
- }
-
- if ('annotationsFilters' in searchParameters && 'generalFilters' in searchParameters) {
- throw new ArgumentError('Both annotations filters and general fiters could not be used together');
- }
-
- 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');
- }
-
- return getCollection(this).search(frameFrom, frameTo, searchParameters);
- };
-
- Task.prototype.annotations.save.implementation = async function (onUpdate) {
- return getSaver(this).save(onUpdate);
- };
-
- Task.prototype.annotations.merge.implementation = async function (objectStates) {
- return getCollection(this).merge(objectStates);
- };
+ return serverProxy.tasks.getPreview(this.id).then((preview) => {
+ if (!preview) {
+ return Promise.resolve('');
+ }
+ return decodePreview(preview);
+ });
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.delete, 'implementation', {
+ value: async function deleteFrameImplementation(
+ this: TaskClass,
+ frame: Parameters[0],
+ ): ReturnType {
+ if (!Number.isInteger(frame)) {
+ throw new Error(`Frame must be an integer. Got: "${frame}"`);
+ }
- Task.prototype.annotations.split.implementation = async function (objectState, frame) {
- return getCollection(this).split(objectState, frame);
- };
+ if (frame < 0 || frame >= this.size) {
+ throw new Error('The frame is out of the task');
+ }
- Task.prototype.annotations.group.implementation = async function (objectStates, reset) {
- return getCollection(this).group(objectStates, reset);
- };
+ const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0];
+ if (job) {
+ await deleteFrameWrapper.call(this, job.id, frame);
+ }
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.restore, 'implementation', {
+ value: async function restoreFrameImplementation(
+ this: TaskClass,
+ frame: Parameters[0],
+ ): ReturnType {
+ if (!Number.isInteger(frame)) {
+ throw new Error(`Frame must be an integer. Got: "${frame}"`);
+ }
- Task.prototype.annotations.join.implementation = async function (objectStates, points) {
- return getCollection(this).join(objectStates, points);
- };
+ if (frame < 0 || frame >= this.size) {
+ throw new Error('The frame is out of the task');
+ }
- Task.prototype.annotations.slice.implementation = async function (objectState, results) {
- return getCollection(this).slice(objectState, results);
- };
+ const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0];
+ if (job) {
+ await restoreFrameWrapper.call(this, job.id, frame);
+ }
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.save, 'implementation', {
+ value: async function saveFramesImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return Promise.all(this.jobs.map((job) => patchMeta(job.id)))
+ .then(() => Promise.resolve());
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.search, 'implementation', {
+ value: async function searchFrameImplementation(
+ this: TaskClass,
+ filters: Parameters[0],
+ frameFrom: Parameters[1],
+ frameTo: Parameters[2],
+ ): ReturnType {
+ if (typeof filters !== 'object') {
+ throw new ArgumentError('Filters should be an object');
+ }
- Task.prototype.annotations.hasUnsavedChanges.implementation = function () {
- return getSaver(this).hasUnsavedChanges();
- };
+ if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
+ throw new ArgumentError('The start and end frames both must be an integer');
+ }
- Task.prototype.annotations.clear.implementation = async function (
- reload, startframe, endframe, delTrackKeyframesOnly,
- ) {
- const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
- return result;
- };
+ if (frameFrom < 0 || frameFrom > this.size) {
+ throw new ArgumentError('The start frame is out of the task');
+ }
- Task.prototype.annotations.select.implementation = function (objectStates, x, y) {
- return getCollection(this).select(objectStates, x, y);
- };
+ if (frameTo < 0 || frameTo > this.size) {
+ throw new ArgumentError('The stop frame is out of the task');
+ }
- Task.prototype.annotations.statistics.implementation = function () {
- return getCollection(this).statistics();
- };
+ const jobs = this.jobs.filter((_job) => (
+ (frameFrom >= _job.startFrame && frameFrom <= _job.stopFrame) ||
+ (frameTo >= _job.startFrame && frameTo <= _job.stopFrame) ||
+ (frameFrom < _job.startFrame && frameTo > _job.stopFrame)
+ ));
- Task.prototype.annotations.put.implementation = function (objectStates) {
- return getCollection(this).put(objectStates);
- };
+ for (const job of jobs) {
+ const result = await findFrame(
+ job.id, Math.max(frameFrom, job.startFrame), Math.min(frameTo, job.stopFrame), filters,
+ );
- 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;
- };
+ if (result !== null) return result;
+ }
- Task.prototype.annotations.import.implementation = function (data) {
- return getCollection(this).import(data);
- };
+ return null;
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.contextImage, 'implementation', {
+ value: function contextImageImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ throw new Error('Not implemented for Task');
+ },
+ });
+
+ Object.defineProperty(Task.prototype.frames.chunk, 'implementation', {
+ value: function chunkImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ throw new Error('Not implemented for Task');
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.get, 'implementation', {
+ value: async function getAnnotationsImplementation(
+ this: TaskClass,
+ frame: Parameters[0],
+ allTracks: Parameters[1],
+ filters: Parameters[2],
+ ): ReturnType {
+ if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) {
+ throw new ArgumentError('The filters argument must be an array of strings');
+ }
- Task.prototype.annotations.export.implementation = function () {
- return getCollection(this).export();
- };
+ if (!Number.isInteger(frame) || frame < 0) {
+ throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
+ }
- 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;
- };
+ if (frame >= this.size) {
+ throw new ArgumentError(`Frame ${frame} does not exist in the task`);
+ }
- Task.prototype.actions.undo.implementation = async function (count) {
- return getHistory(this).undo(count);
- };
+ const result = await getAnnotations(this, frame, allTracks, filters);
+ const deletedFrames = await getDeletedFrames('task', this.id);
+ if (frame in deletedFrames) {
+ return [];
+ }
- Task.prototype.actions.redo.implementation = async function (count) {
- return getHistory(this).redo(count);
- };
+ return result;
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.search, 'implementation', {
+ value: function searchAnnotationsImplementation(
+ this: TaskClass,
+ frameFrom: Parameters[0],
+ frameTo: Parameters[1],
+ searchParameters: Parameters[2],
+ ): ReturnType {
+ if ('annotationsFilters' in searchParameters && !Array.isArray(searchParameters.annotationsFilters)) {
+ throw new ArgumentError('Annotations filters must be an array');
+ }
- Task.prototype.actions.freeze.implementation = function (frozen) {
- return getHistory(this).freeze(frozen);
- };
+ if ('generalFilters' in searchParameters && typeof searchParameters.generalFilters.isEmptyFrame !== 'boolean') {
+ throw new ArgumentError('General filter isEmptyFrame must be a boolean');
+ }
- Task.prototype.actions.clear.implementation = function () {
- return getHistory(this).clear();
- };
+ if ('annotationsFilters' in searchParameters && 'generalFilters' in searchParameters) {
+ throw new ArgumentError('Both annotations filters and general fiters could not be used together');
+ }
- Task.prototype.actions.get.implementation = function () {
- return getHistory(this).get();
- };
+ if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
+ throw new ArgumentError('The start and end frames both must be an integer');
+ }
- Task.prototype.logger.log.implementation = async function (scope, payload, wait) {
- const result = await logger.log(
- scope,
- {
- ...payload,
- project_id: this.projectId,
- task_id: this.id,
- },
- wait,
- );
- return result;
- };
+ if (frameFrom < 0 || frameFrom >= this.size) {
+ throw new ArgumentError('The start frame is out of the task');
+ }
- Task.prototype.guide.implementation = async function guide() {
- if (this.guideId === null) {
- return null;
- }
+ if (frameTo < 0 || frameTo >= this.size) {
+ throw new ArgumentError('The stop frame is out of the task');
+ }
- const result = await serverProxy.guides.get(this.guideId);
- return new AnnotationGuide(result);
- };
+ return Promise.resolve(getCollection(this).search(frameFrom, frameTo, searchParameters));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.save, 'implementation', {
+ value: function saveAnnotationsImplementation(
+ this: TaskClass,
+ onUpdate: Parameters[0],
+ ): ReturnType {
+ return getSaver(this).save(onUpdate);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.merge, 'implementation', {
+ value: function mergeAnnotationsImplementation(
+ this: TaskClass,
+ objectStates: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).merge(objectStates));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.split, 'implementation', {
+ value: function splitAnnotationsImplementation(
+ this: TaskClass,
+ objectState: Parameters[0],
+ frame: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).split(objectState, frame));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.group, 'implementation', {
+ value: function groupAnnotationsImplementation(
+ this: TaskClass,
+ objectStates: Parameters[0],
+ reset: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).group(objectStates, reset));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.join, 'implementation', {
+ value: function joinAnnotationsImplementation(
+ this: TaskClass,
+ objectStates: Parameters[0],
+ points: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).join(objectStates, points));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.slice, 'implementation', {
+ value: function sliceAnnotationsImplementation(
+ this: TaskClass,
+ objectState: Parameters[0],
+ results: Parameters[1],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).slice(objectState, results));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.hasUnsavedChanges, 'implementation', {
+ value: function hasUnsavedChangesImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return getSaver(this).hasUnsavedChanges();
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.clear, 'implementation', {
+ value: function clearAnnotationsImplementation(
+ this: TaskClass,
+ options: Parameters[0],
+ ): ReturnType {
+ return clearAnnotations(this, options);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.select, 'implementation', {
+ value: function selectAnnotationsImplementation(
+ this: TaskClass,
+ objectStates: Parameters[0],
+ x: Parameters[1],
+ y: Parameters[2],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).select(objectStates, x, y));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.statistics, 'implementation', {
+ value: function statisticsImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).statistics());
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.put, 'implementation', {
+ value: function putAnnotationsImplementation(
+ this: TaskClass,
+ objectStates: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).put(objectStates));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.upload, 'implementation', {
+ value: function uploadAnnotationsImplementation(
+ this: TaskClass,
+ format: Parameters[0],
+ useDefaultLocation: Parameters[1],
+ sourceStorage: Parameters[2],
+ file: Parameters[3],
+ options: Parameters[4],
+ ): ReturnType {
+ return importDataset(this, format, useDefaultLocation, sourceStorage, file, options);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.import, 'implementation', {
+ value: function importAnnotationsImplementation(
+ this: TaskClass,
+ data: Parameters[0],
+ ): ReturnType {
+ getCollection(this).import(data);
+ return Promise.resolve();
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.export, 'implementation', {
+ value: function exportAnnotationsImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return Promise.resolve(getCollection(this).export());
+ },
+ });
+
+ Object.defineProperty(Task.prototype.annotations.exportDataset, 'implementation', {
+ value: function exportDatasetImplementation(
+ this: TaskClass,
+ format: Parameters[0],
+ saveImages: Parameters[1],
+ useDefaultSettings: Parameters[2],
+ targetStorage: Parameters[3],
+ customName: Parameters[4],
+ ): ReturnType {
+ return exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.actions.undo, 'implementation', {
+ value: function undoActionImplementation(
+ this: TaskClass,
+ count: Parameters[0],
+ ): ReturnType {
+ return getHistory(this).undo(count);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.actions.redo, 'implementation', {
+ value: function redoActionImplementation(
+ this: TaskClass,
+ count: Parameters[0],
+ ): ReturnType {
+ return getHistory(this).redo(count);
+ },
+ });
+
+ Object.defineProperty(Task.prototype.actions.freeze, 'implementation', {
+ value: function freezeActionsImplementation(
+ this: TaskClass,
+ frozen: Parameters[0],
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).freeze(frozen));
+ },
+ });
+
+ Object.defineProperty(Task.prototype.actions.clear, 'implementation', {
+ value: function clearActionsImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).clear());
+ },
+ });
+
+ Object.defineProperty(Task.prototype.actions.get, 'implementation', {
+ value: function getActionsImplementation(
+ this: TaskClass,
+ ): ReturnType {
+ return Promise.resolve(getHistory(this).get());
+ },
+ });
+
+ Object.defineProperty(Task.prototype.logger.log, 'implementation', {
+ value: function logImplementation(
+ this: TaskClass,
+ scope: Parameters[0],
+ payload: Parameters[1],
+ wait: Parameters[2],
+ ): ReturnType {
+ return logger.log(
+ scope,
+ {
+ ...payload,
+ project_id: this.projectId,
+ task_id: this.id,
+ },
+ wait,
+ );
+ },
+ });
return Task;
}
diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts
index 5565417ff9eb..701d05413cf9 100644
--- a/cvat-core/src/session.ts
+++ b/cvat-core/src/session.ts
@@ -4,8 +4,10 @@
// SPDX-License-Identifier: MIT
import _ from 'lodash';
+
+import { ChunkQuality } from 'cvat-data';
import {
- ChunkType, DimensionType, JobStage,
+ ChunkType, DimensionType, HistoryActions, JobStage,
JobState, JobType, RQStatus, StorageLocation, TaskMode, TaskStatus,
} from './enums';
import { Storage } from './storage';
@@ -15,11 +17,16 @@ import { ArgumentError, ScriptingError } from './exceptions';
import { Label } from './labels';
import User from './user';
import { FieldUpdateTrigger } from './common';
-import { SerializedJob, SerializedLabel, SerializedTask } from './server-response-types';
+import {
+ SerializedCollection, SerializedJob,
+ SerializedLabel, SerializedTask,
+} from './server-response-types';
import AnnotationGuide from './guide';
import { FrameData } from './frames';
import Statistics from './statistics';
import logger from './logger';
+import Issue from './issue';
+import ObjectState from './object-state';
function buildDuplicatedAPI(prototype) {
Object.defineProperties(prototype, {
@@ -49,11 +56,9 @@ function buildDuplicatedAPI(prototype) {
return result;
},
- async clear(
- reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true,
- ) {
+ async clear(options) {
const result = await PluginRegistry.apiWrapper.call(
- this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly,
+ this, prototype.annotations.clear, options,
);
return result;
},
@@ -230,13 +235,13 @@ function buildDuplicatedAPI(prototype) {
const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.preview);
return result;
},
- async search(filters, frameFrom, frameTo) {
+ async search(filters, startFrame, stopFrame) {
const result = await PluginRegistry.apiWrapper.call(
this,
prototype.frames.search,
filters,
- frameFrom,
- frameTo,
+ startFrame,
+ stopFrame,
);
return result;
},
@@ -305,43 +310,85 @@ function buildDuplicatedAPI(prototype) {
export class Session {
public annotations: {
- get: CallableFunction;
- put: CallableFunction;
- save: CallableFunction;
- merge: CallableFunction;
- split: CallableFunction;
- group: CallableFunction;
- join: CallableFunction;
- slice: CallableFunction;
- clear: CallableFunction;
- search: CallableFunction;
- upload: CallableFunction;
- select: CallableFunction;
- import: CallableFunction;
- export: CallableFunction;
+ get: (frame: number, allTracks: boolean, filters: object[]) => Promise;
+ put: (objectStates: ObjectState[]) => Promise;
+ merge: (objectStates: ObjectState[]) => Promise;
+ split: (objectState: ObjectState, frame: number) => Promise;
+ group: (objectStates: ObjectState[], reset: boolean) => Promise;
+ join: (objectStates: ObjectState[], points: number[]) => Promise;
+ slice: (state: ObjectState, results: number[][]) => Promise;
+ clear: (options?: {
+ reload?: boolean;
+ startFrame?: number;
+ stopFrame?: number;
+ delTrackKeyframesOnly?: boolean;
+ }) => Promise;
+ save: (
+ onUpdate ?: (message: string) => void,
+ ) => Promise;
+ search: (
+ frameFrom: number,
+ frameTo: number,
+ searchParameters: {
+ allowDeletedFrames: boolean;
+ annotationsFilters?: object[];
+ generalFilters?: {
+ isEmptyFrame?: boolean;
+ };
+ },
+ ) => Promise;
+ upload: (
+ format: string,
+ useDefaultSettings: boolean,
+ sourceStorage: Storage,
+ file: File | string,
+ options?: {
+ convMaskToPoly?: boolean,
+ updateStatusCallback?: (s: string, n: number) => void,
+ },
+ ) => Promise;
+ select: (objectStates: ObjectState[], x: number, y: number) => Promise<{
+ state: ObjectState,
+ distance: number | null,
+ }>;
+ import: (data: Omit) => Promise;
+ export: () => Promise>;
statistics: () => Promise;
- hasUnsavedChanges: CallableFunction;
- exportDataset: CallableFunction;
+ hasUnsavedChanges: () => boolean;
+ exportDataset: (
+ format: string,
+ saveImages: boolean,
+ useDefaultSettings: boolean,
+ targetStorage: Storage,
+ name?: string,
+ ) => Promise