|
|
|
@ -23,6 +23,7 @@ import {
|
|
|
|
} from 'reducers/interfaces';
|
|
|
|
} from 'reducers/interfaces';
|
|
|
|
|
|
|
|
|
|
|
|
import getCore from 'cvat-core';
|
|
|
|
import getCore from 'cvat-core';
|
|
|
|
|
|
|
|
import logger, { LogType } from 'cvat-logger';
|
|
|
|
import { RectDrawingMethod } from 'cvat-canvas';
|
|
|
|
import { RectDrawingMethod } from 'cvat-canvas';
|
|
|
|
import { getCVATStore } from 'cvat-store';
|
|
|
|
import { getCVATStore } from 'cvat-store';
|
|
|
|
|
|
|
|
|
|
|
|
@ -89,6 +90,23 @@ function computeZRange(states: any[]): number[] {
|
|
|
|
return [minZ, maxZ];
|
|
|
|
return [minZ, maxZ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function jobInfoGenerator(job: any): Promise<Record<string, number>> {
|
|
|
|
|
|
|
|
const { total } = await job.annotations.statistics();
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
'frame count': job.stopFrame - job.startFrame + 1,
|
|
|
|
|
|
|
|
'track count': total.rectangle.shape + total.rectangle.track
|
|
|
|
|
|
|
|
+ total.polygon.shape + total.polygon.track
|
|
|
|
|
|
|
|
+ total.polyline.shape + total.polyline.track
|
|
|
|
|
|
|
|
+ total.points.shape + total.points.track,
|
|
|
|
|
|
|
|
'object count': total.total,
|
|
|
|
|
|
|
|
'box count': total.rectangle.shape + total.rectangle.track,
|
|
|
|
|
|
|
|
'polygon count': total.polygon.shape + total.polygon.track,
|
|
|
|
|
|
|
|
'polyline count': total.polyline.shape + total.polyline.track,
|
|
|
|
|
|
|
|
'points count': total.points.shape + total.points.track,
|
|
|
|
|
|
|
|
'tag count': total.tags,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export enum AnnotationActionTypes {
|
|
|
|
export enum AnnotationActionTypes {
|
|
|
|
GET_JOB = 'GET_JOB',
|
|
|
|
GET_JOB = 'GET_JOB',
|
|
|
|
GET_JOB_SUCCESS = 'GET_JOB_SUCCESS',
|
|
|
|
GET_JOB_SUCCESS = 'GET_JOB_SUCCESS',
|
|
|
|
@ -166,6 +184,28 @@ export enum AnnotationActionTypes {
|
|
|
|
ADD_Z_LAYER = 'ADD_Z_LAYER',
|
|
|
|
ADD_Z_LAYER = 'ADD_Z_LAYER',
|
|
|
|
SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED',
|
|
|
|
SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED',
|
|
|
|
CHANGE_WORKSPACE = 'CHANGE_WORKSPACE',
|
|
|
|
CHANGE_WORKSPACE = 'CHANGE_WORKSPACE',
|
|
|
|
|
|
|
|
SAVE_LOGS_SUCCESS = 'SAVE_LOGS_SUCCESS',
|
|
|
|
|
|
|
|
SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED',
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function saveLogsAsync():
|
|
|
|
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>) => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await logger.save();
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
|
|
|
|
type: AnnotationActionTypes.SAVE_LOGS_SUCCESS,
|
|
|
|
|
|
|
|
payload: {},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
|
|
|
|
type: AnnotationActionTypes.SAVE_LOGS_FAILED,
|
|
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
|
|
error,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function changeWorkspace(workspace: Workspace): AnyAction {
|
|
|
|
export function changeWorkspace(workspace: Workspace): AnyAction {
|
|
|
|
@ -193,8 +233,7 @@ export function switchZLayer(cur: number): AnyAction {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function fetchAnnotationsAsync():
|
|
|
|
export function fetchAnnotationsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const {
|
|
|
|
const {
|
|
|
|
@ -251,14 +290,21 @@ export function undoActionAsync(sessionInstance: any, frame: number):
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
const state = getStore().getState();
|
|
|
|
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
|
|
|
|
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: use affected IDs as an optimization
|
|
|
|
// TODO: use affected IDs as an optimization
|
|
|
|
|
|
|
|
const [undoName] = state.annotation.annotations.history.undo.slice(-1);
|
|
|
|
|
|
|
|
const undoLog = await sessionInstance.logger.log(LogType.undoAction, {
|
|
|
|
|
|
|
|
name: undoName,
|
|
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
|
|
}, true);
|
|
|
|
await sessionInstance.actions.undo();
|
|
|
|
await sessionInstance.actions.undo();
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
const states = await sessionInstance.annotations
|
|
|
|
const states = await sessionInstance.annotations
|
|
|
|
.get(frame, showAllInterpolationTracks, filters);
|
|
|
|
.get(frame, showAllInterpolationTracks, filters);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
|
|
|
|
await undoLog.close();
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.UNDO_ACTION_SUCCESS,
|
|
|
|
type: AnnotationActionTypes.UNDO_ACTION_SUCCESS,
|
|
|
|
@ -284,14 +330,21 @@ export function redoActionAsync(sessionInstance: any, frame: number):
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
const state = getStore().getState();
|
|
|
|
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
|
|
|
|
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: use affected IDs as an optimization
|
|
|
|
// TODO: use affected IDs as an optimization
|
|
|
|
|
|
|
|
const [redoName] = state.annotation.annotations.history.redo.slice(-1);
|
|
|
|
|
|
|
|
const redoLog = await sessionInstance.logger.log(LogType.redoAction, {
|
|
|
|
|
|
|
|
name: redoName,
|
|
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
|
|
}, true);
|
|
|
|
await sessionInstance.actions.redo();
|
|
|
|
await sessionInstance.actions.redo();
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
const states = await sessionInstance.annotations
|
|
|
|
const states = await sessionInstance.annotations
|
|
|
|
.get(frame, showAllInterpolationTracks, filters);
|
|
|
|
.get(frame, showAllInterpolationTracks, filters);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
|
|
|
|
await redoLog.close();
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.REDO_ACTION_SUCCESS,
|
|
|
|
type: AnnotationActionTypes.REDO_ACTION_SUCCESS,
|
|
|
|
@ -382,6 +435,12 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
const frame = state.annotation.player.frame.number;
|
|
|
|
const frame = state.annotation.player.frame.number;
|
|
|
|
await job.annotations.upload(file, loader);
|
|
|
|
await job.annotations.upload(file, loader);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await job.logger.log(
|
|
|
|
|
|
|
|
LogType.uploadAnnotations, {
|
|
|
|
|
|
|
|
...(await jobInfoGenerator(job)),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// One more update to escape some problems
|
|
|
|
// One more update to escape some problems
|
|
|
|
// in canvas when shape with the same
|
|
|
|
// in canvas when shape with the same
|
|
|
|
// clientID has different type (polygon, rectangle) for example
|
|
|
|
// clientID has different type (polygon, rectangle) for example
|
|
|
|
@ -508,6 +567,9 @@ export function propagateObjectAsync(
|
|
|
|
frame: from,
|
|
|
|
frame: from,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await sessionInstance.logger.log(
|
|
|
|
|
|
|
|
LogType.propagateObject, { count: to - from + 1 },
|
|
|
|
|
|
|
|
);
|
|
|
|
const states = [];
|
|
|
|
const states = [];
|
|
|
|
for (let frame = from; frame <= to; frame++) {
|
|
|
|
for (let frame = from; frame <= to; frame++) {
|
|
|
|
copy.frame = frame;
|
|
|
|
copy.frame = frame;
|
|
|
|
@ -558,6 +620,7 @@ export function removeObjectAsync(sessionInstance: any, objectState: any, force:
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
await sessionInstance.logger.log(LogType.deleteObject, { count: 1 });
|
|
|
|
const removed = await objectState.delete(force);
|
|
|
|
const removed = await objectState.delete(force);
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
const history = await sessionInstance.actions.get();
|
|
|
|
|
|
|
|
|
|
|
|
@ -593,6 +656,9 @@ export function editShape(enabled: boolean): AnyAction {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function copyShape(objectState: any): AnyAction {
|
|
|
|
export function copyShape(objectState: any): AnyAction {
|
|
|
|
|
|
|
|
const job = getStore().getState().annotation.job.instance;
|
|
|
|
|
|
|
|
job.logger.log(LogType.copyObject, { count: 1 });
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
type: AnnotationActionTypes.COPY_SHAPE,
|
|
|
|
type: AnnotationActionTypes.COPY_SHAPE,
|
|
|
|
payload: {
|
|
|
|
payload: {
|
|
|
|
@ -696,6 +762,12 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
payload: {},
|
|
|
|
payload: {},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await job.logger.log(
|
|
|
|
|
|
|
|
LogType.changeFrame, {
|
|
|
|
|
|
|
|
from: frame,
|
|
|
|
|
|
|
|
to: toFrame,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
const data = await job.frames.get(toFrame);
|
|
|
|
const data = await job.frames.get(toFrame);
|
|
|
|
const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters);
|
|
|
|
const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
@ -716,6 +788,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const delay = Math.max(0, Math.round(1000 / frameSpeed)
|
|
|
|
const delay = Math.max(0, Math.round(1000 / frameSpeed)
|
|
|
|
- currentTime + (state.annotation.player.frame.changeTime as number));
|
|
|
|
- currentTime + (state.annotation.player.frame.changeTime as number));
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS,
|
|
|
|
type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS,
|
|
|
|
payload: {
|
|
|
|
payload: {
|
|
|
|
@ -743,14 +816,33 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
|
|
|
|
|
|
|
|
export function rotateCurrentFrame(rotation: Rotation): AnyAction {
|
|
|
|
export function rotateCurrentFrame(rotation: Rotation): AnyAction {
|
|
|
|
const state: CombinedState = getStore().getState();
|
|
|
|
const state: CombinedState = getStore().getState();
|
|
|
|
const { number: frameNumber } = state.annotation.player.frame;
|
|
|
|
const {
|
|
|
|
const { startFrame } = state.annotation.job.instance;
|
|
|
|
annotation: {
|
|
|
|
const { frameAngles } = state.annotation.player;
|
|
|
|
player: {
|
|
|
|
const { rotateAll } = state.settings.player;
|
|
|
|
frame: {
|
|
|
|
|
|
|
|
number: frameNumber,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
frameAngles,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
job: {
|
|
|
|
|
|
|
|
instance: job,
|
|
|
|
|
|
|
|
instance: {
|
|
|
|
|
|
|
|
startFrame,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
settings: {
|
|
|
|
|
|
|
|
player: {
|
|
|
|
|
|
|
|
rotateAll,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
} = state;
|
|
|
|
|
|
|
|
|
|
|
|
const frameAngle = (frameAngles[frameNumber - startFrame]
|
|
|
|
const frameAngle = (frameAngles[frameNumber - startFrame]
|
|
|
|
+ (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360;
|
|
|
|
+ (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
job.logger.log(LogType.rotateImage, { angle: frameAngle });
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
type: AnnotationActionTypes.ROTATE_FRAME,
|
|
|
|
type: AnnotationActionTypes.ROTATE_FRAME,
|
|
|
|
payload: {
|
|
|
|
payload: {
|
|
|
|
@ -800,11 +892,6 @@ export function getJobAsync(
|
|
|
|
initialFilters: string[],
|
|
|
|
initialFilters: string[],
|
|
|
|
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
|
|
|
dispatch({
|
|
|
|
|
|
|
|
type: AnnotationActionTypes.GET_JOB,
|
|
|
|
|
|
|
|
payload: {},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const state: CombinedState = getStore().getState();
|
|
|
|
const state: CombinedState = getStore().getState();
|
|
|
|
const filters = initialFilters;
|
|
|
|
const filters = initialFilters;
|
|
|
|
@ -817,6 +904,18 @@ export function getJobAsync(
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
|
|
|
|
type: AnnotationActionTypes.GET_JOB,
|
|
|
|
|
|
|
|
payload: {},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const loadJobEvent = await logger.log(
|
|
|
|
|
|
|
|
LogType.loadJob, {
|
|
|
|
|
|
|
|
task_id: tid,
|
|
|
|
|
|
|
|
job_id: jid,
|
|
|
|
|
|
|
|
}, true,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Check state if the task is already there
|
|
|
|
// Check state if the task is already there
|
|
|
|
let task = state.tasks.current
|
|
|
|
let task = state.tasks.current
|
|
|
|
.filter((_task: Task) => _task.instance.id === tid)
|
|
|
|
.filter((_task: Task) => _task.instance.id === tid)
|
|
|
|
@ -841,6 +940,8 @@ export function getJobAsync(
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
const [minZ, maxZ] = computeZRange(states);
|
|
|
|
const colors = [...cvat.enums.colors];
|
|
|
|
const colors = [...cvat.enums.colors];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadJobEvent.close(await jobInfoGenerator(job));
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.GET_JOB_SUCCESS,
|
|
|
|
type: AnnotationActionTypes.GET_JOB_SUCCESS,
|
|
|
|
payload: {
|
|
|
|
payload: {
|
|
|
|
@ -874,6 +975,10 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
const saveJobEvent = await sessionInstance.logger.log(
|
|
|
|
|
|
|
|
LogType.saveJob, {}, true,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await sessionInstance.annotations.save((status: string) => {
|
|
|
|
await sessionInstance.annotations.save((status: string) => {
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS,
|
|
|
|
type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS,
|
|
|
|
@ -883,6 +988,13 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await saveJobEvent.close();
|
|
|
|
|
|
|
|
await sessionInstance.logger.log(
|
|
|
|
|
|
|
|
LogType.sendTaskInfo,
|
|
|
|
|
|
|
|
await jobInfoGenerator(sessionInstance),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
dispatch(saveLogsAsync());
|
|
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
dispatch({
|
|
|
|
type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS,
|
|
|
|
type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS,
|
|
|
|
payload: {},
|
|
|
|
payload: {},
|
|
|
|
@ -898,6 +1010,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// used to reproduce the latest drawing (in case of tags just creating) by using N
|
|
|
|
export function rememberObject(
|
|
|
|
export function rememberObject(
|
|
|
|
objectType: ObjectType,
|
|
|
|
objectType: ObjectType,
|
|
|
|
labelID: number,
|
|
|
|
labelID: number,
|
|
|
|
|