diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e2c6e88d..ea7c4a63 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,6 +29,7 @@ $ python3 -m venv .env
$ . .env/bin/activate
$ pip install -U pip wheel
$ pip install -r cvat/requirements/development.txt
+$ pip install -r datumaro/requirements.txt
$ python manage.py migrate
$ python manage.py collectstatic
```
diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js
index 4faf9e21..fcd5a2a0 100644
--- a/cvat-core/src/server-proxy.js
+++ b/cvat-core/src/server-proxy.js
@@ -147,6 +147,7 @@
`${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
]).join('&').replace(/%20/g, '+');
+ Axios.defaults.headers.common.Authorization = '';
let authenticationResponse = null;
try {
authenticationResponse = await Axios.post(
@@ -246,7 +247,7 @@
try {
await Axios.delete(`${backendAPI}/tasks/${id}`);
} catch (errorData) {
- throw generateError(errorData, 'Could not delete the task from the server');
+ throw generateError(errorData, `Could not delete the task ${id} from the server`);
}
}
diff --git a/cvat-ui/src/actions/formats-actions.ts b/cvat-ui/src/actions/formats-actions.ts
index 806e73aa..a3e74685 100644
--- a/cvat-ui/src/actions/formats-actions.ts
+++ b/cvat-ui/src/actions/formats-actions.ts
@@ -6,38 +6,53 @@ import getCore from '../core';
const cvat = getCore();
export enum FormatsActionTypes {
- GETTING_FORMATS_SUCCESS = 'GETTING_FORMATS_SUCCESS',
- GETTING_FORMATS_FAILED = 'GETTING_FORMATS_FAILED',
+ GET_FORMATS = 'GET_FORMATS',
+ GET_FORMATS_SUCCESS = 'GET_FORMATS_SUCCESS',
+ GET_FORMATS_FAILED = 'GET_FORMATS_FAILED',
}
-export function gettingFormatsSuccess(formats: any): AnyAction {
+function getFormats(): AnyAction {
return {
- type: FormatsActionTypes.GETTING_FORMATS_SUCCESS,
+ type: FormatsActionTypes.GET_FORMATS,
+ payload: {},
+ };
+}
+
+function getFormatsSuccess(
+ annotationFormats: any[],
+ datasetFormats: any[],
+): AnyAction {
+ return {
+ type: FormatsActionTypes.GET_FORMATS_SUCCESS,
payload: {
- formats,
+ annotationFormats,
+ datasetFormats,
},
};
}
-export function gettingFormatsFailed(error: any): AnyAction {
+function getFormatsFailed(error: any): AnyAction {
return {
- type: FormatsActionTypes.GETTING_FORMATS_FAILED,
+ type: FormatsActionTypes.GET_FORMATS_FAILED,
payload: {
error,
},
};
}
-export function gettingFormatsAsync(): ThunkAction, {}, {}, AnyAction> {
+export function getFormatsAsync(): ThunkAction, {}, {}, AnyAction> {
return async (dispatch: ActionCreator): Promise => {
- let formats = null;
+ dispatch(getFormats());
+ let annotationFormats = null;
+ let datasetFormats = null;
try {
- formats = await cvat.server.formats();
+ annotationFormats = await cvat.server.formats();
+ datasetFormats = await cvat.server.datasetFormats();
} catch (error) {
- dispatch(gettingFormatsFailed(error));
+ dispatch(getFormatsFailed(error));
return;
}
- dispatch(gettingFormatsSuccess(formats));
+ dispatch(getFormatsSuccess(annotationFormats, datasetFormats));
};
}
diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts
index d11549d8..c2745195 100644
--- a/cvat-ui/src/actions/models-actions.ts
+++ b/cvat-ui/src/actions/models-actions.ts
@@ -3,7 +3,12 @@ import { ThunkAction } from 'redux-thunk';
import getCore from '../core';
import { getCVATStore } from '../store';
-import { Model, ModelFiles, CombinedState } from '../reducers/interfaces';
+import {
+ Model,
+ ModelFiles,
+ ActiveInference,
+ CombinedState,
+} from '../reducers/interfaces';
export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS',
@@ -324,6 +329,199 @@ ThunkAction, {}, {}, AnyAction> {
};
}
+
+function getInferenceStatusSuccess(
+ taskID: number,
+ activeInference: ActiveInference,
+): AnyAction {
+ const action = {
+ type: ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS,
+ payload: {
+ taskID,
+ activeInference,
+ },
+ };
+
+ return action;
+}
+
+function getInferenceStatusFailed(taskID: number, error: any): AnyAction {
+ const action = {
+ type: ModelsActionTypes.GET_INFERENCE_STATUS_FAILED,
+ payload: {
+ taskID,
+ error,
+ },
+ };
+
+ return action;
+}
+
+interface InferenceMeta {
+ active: boolean;
+ taskID: number;
+ requestID: string;
+}
+
+const timers: any = {};
+
+async function timeoutCallback(
+ url: string,
+ taskID: number,
+ dispatch: ActionCreator,
+): Promise {
+ try {
+ delete timers[taskID];
+
+ const response = await core.server.request(url, {
+ method: 'GET',
+ });
+
+ const activeInference: ActiveInference = {
+ status: response.status,
+ progress: +response.progress || 0,
+ error: response.error || response.stderr || '',
+ };
+
+
+ if (activeInference.status === 'unknown') {
+ dispatch(getInferenceStatusFailed(
+ taskID,
+ new Error(
+ `Inference status for the task ${taskID} is unknown.`,
+ ),
+ ));
+
+ return;
+ }
+
+ if (activeInference.status === 'failed') {
+ dispatch(getInferenceStatusFailed(
+ taskID,
+ new Error(
+ `Inference status for the task ${taskID} is failed. ${activeInference.error}`,
+ ),
+ ));
+
+ return;
+ }
+
+ if (activeInference.status !== 'finished') {
+ timers[taskID] = setTimeout(
+ timeoutCallback.bind(
+ null,
+ url,
+ taskID,
+ dispatch,
+ ), 3000,
+ );
+ }
+
+ dispatch(getInferenceStatusSuccess(taskID, activeInference));
+ } catch (error) {
+ dispatch(getInferenceStatusFailed(taskID, error));
+ }
+}
+
+function subscribe(
+ urlPath: string,
+ inferenceMeta: InferenceMeta,
+ dispatch: ActionCreator,
+): void {
+ if (!(inferenceMeta.taskID in timers)) {
+ const requestURL = `${baseURL}/${urlPath}/${inferenceMeta.requestID}`;
+ timers[inferenceMeta.taskID] = setTimeout(
+ timeoutCallback.bind(
+ null,
+ requestURL,
+ inferenceMeta.taskID,
+ dispatch,
+ ),
+ );
+ }
+}
+
+export function getInferenceStatusAsync(tasks: number[]):
+ThunkAction, {}, {}, AnyAction> {
+ return async (dispatch: ActionCreator): Promise => {
+ function parse(response: any): InferenceMeta[] {
+ return Object.keys(response).map((key: string): InferenceMeta => ({
+ taskID: +key,
+ requestID: response[key].rq_id || key,
+ active: typeof (response[key].active) === 'undefined' ? ['queued', 'started']
+ .includes(response[key].status.toLowerCase()) : response[key].active,
+ }));
+ }
+
+ const store = getCVATStore();
+ const state: CombinedState = store.getState();
+ const OpenVINO = state.plugins.plugins.AUTO_ANNOTATION;
+ const RCNN = state.plugins.plugins.TF_ANNOTATION;
+ const MaskRCNN = state.plugins.plugins.TF_SEGMENTATION;
+
+ try {
+ if (OpenVINO) {
+ const response = await core.server.request(
+ `${baseURL}/auto_annotation/meta/get`, {
+ method: 'POST',
+ data: JSON.stringify(tasks),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+
+ parse(response.run)
+ .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
+ .forEach((inferenceMeta: InferenceMeta): void => {
+ subscribe('auto_annotation/check', inferenceMeta, dispatch);
+ });
+ }
+
+ if (RCNN) {
+ const response = await core.server.request(
+ `${baseURL}/tensorflow/annotation/meta/get`, {
+ method: 'POST',
+ data: JSON.stringify(tasks),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+
+ parse(response)
+ .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
+ .forEach((inferenceMeta: InferenceMeta): void => {
+ subscribe('tensorflow/annotation/check/task', inferenceMeta, dispatch);
+ });
+ }
+
+ if (MaskRCNN) {
+ const response = await core.server.request(
+ `${baseURL}/tensorflow/segmentation/meta/get`, {
+ method: 'POST',
+ data: JSON.stringify(tasks),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+
+ parse(response)
+ .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
+ .forEach((inferenceMeta: InferenceMeta): void => {
+ subscribe('tensorflow/segmentation/check/task', inferenceMeta, dispatch);
+ });
+ }
+ } catch (error) {
+ tasks.forEach((task: number): void => {
+ dispatch(getInferenceStatusFailed(task, error));
+ });
+ }
+ };
+}
+
+
function inferModel(): AnyAction {
const action = {
type: ModelsActionTypes.INFER_MODEL,
@@ -387,6 +585,8 @@ export function inferModelAsync(
},
);
}
+
+ dispatch(getInferenceStatusAsync([taskInstance.id]));
} catch (error) {
dispatch(inferModelFailed(error));
return;
diff --git a/cvat-ui/src/actions/notification-actions.ts b/cvat-ui/src/actions/notification-actions.ts
new file mode 100644
index 00000000..b8431106
--- /dev/null
+++ b/cvat-ui/src/actions/notification-actions.ts
@@ -0,0 +1,24 @@
+import { AnyAction } from 'redux';
+
+export enum NotificationsActionType {
+ RESET_ERRORS = 'RESET_ERRORS',
+ RESET_MESSAGES = 'RESET_MESSAGES',
+}
+
+export function resetErrors(): AnyAction {
+ const action = {
+ type: NotificationsActionType.RESET_ERRORS,
+ payload: {},
+ };
+
+ return action;
+}
+
+export function resetMessages(): AnyAction {
+ const action = {
+ type: NotificationsActionType.RESET_MESSAGES,
+ payload: {},
+ };
+
+ return action;
+}
diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts
index 89c04d03..0e27013f 100644
--- a/cvat-ui/src/actions/plugins-actions.ts
+++ b/cvat-ui/src/actions/plugins-actions.ts
@@ -4,6 +4,7 @@ import { SupportedPlugins } from '../reducers/interfaces';
import PluginChecker from '../utils/plugin-checker';
export enum PluginsActionTypes {
+ CHECK_PLUGINS = 'CHECK_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS'
}
@@ -11,6 +12,15 @@ interface PluginObjects {
[plugin: string]: boolean;
}
+function checkPlugins(): AnyAction {
+ const action = {
+ type: PluginsActionTypes.CHECK_PLUGINS,
+ payload: {},
+ };
+
+ return action;
+}
+
function checkedAllPlugins(plugins: PluginObjects): AnyAction {
const action = {
type: PluginsActionTypes.CHECKED_ALL_PLUGINS,
@@ -25,6 +35,7 @@ function checkedAllPlugins(plugins: PluginObjects): AnyAction {
export function checkPluginsAsync():
ThunkAction, {}, {}, AnyAction> {
return async (dispatch: ActionCreator): Promise => {
+ dispatch(checkPlugins());
const plugins: PluginObjects = {};
const promises: Promise[] = [];
diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts
index b5437823..8bb5c381 100644
--- a/cvat-ui/src/actions/tasks-actions.ts
+++ b/cvat-ui/src/actions/tasks-actions.ts
@@ -1,6 +1,7 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { TasksQuery } from '../reducers/interfaces';
+import { getInferenceStatusAsync } from './models-actions';
import getCore from '../core';
@@ -16,6 +17,9 @@ export enum TasksActionTypes {
DUMP_ANNOTATIONS = 'DUMP_ANNOTATIONS',
DUMP_ANNOTATIONS_SUCCESS = 'DUMP_ANNOTATIONS_SUCCESS',
DUMP_ANNOTATIONS_FAILED = 'DUMP_ANNOTATIONS_FAILED',
+ EXPORT_DATASET = 'EXPORT_DATASET',
+ EXPORT_DATASET_SUCCESS = 'EXPORT_DATASET_SUCCESS',
+ EXPORT_DATASET_FAILED = 'EXPORT_DATASET_FAILED',
DELETE_TASK = 'DELETE_TASK',
DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS',
DELETE_TASK_FAILED = 'DELETE_TASK_FAILED',
@@ -26,6 +30,7 @@ export enum TasksActionTypes {
UPDATE_TASK = 'UPDATE_TASK',
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
+ RESET_ERROR = 'RESET_ERROR',
}
function getTasks(): AnyAction {
@@ -89,6 +94,13 @@ ThunkAction, {}, {}, AnyAction> {
const previews = [];
const promises = array
.map((task): string => (task as any).frames.preview());
+ dispatch(
+ getInferenceStatusAsync(
+ array.map(
+ (task: any): number => task.id,
+ ),
+ ),
+ );
for (const promise of promises) {
try {
@@ -150,7 +162,9 @@ ThunkAction, {}, {}, AnyAction> {
try {
dispatch(dumpAnnotation(task, dumper));
const url = await task.annotations.dump(task.name, dumper);
- window.location.assign(url);
+ // false positive
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
+ window.open(url);
} catch (error) {
dispatch(dumpAnnotationFailed(task, dumper, error));
return;
@@ -210,6 +224,61 @@ ThunkAction, {}, {}, AnyAction> {
};
}
+function exportDataset(task: any, exporter: any): AnyAction {
+ const action = {
+ type: TasksActionTypes.EXPORT_DATASET,
+ payload: {
+ task,
+ exporter,
+ },
+ };
+
+ return action;
+}
+
+function exportDatasetSuccess(task: any, exporter: any): AnyAction {
+ const action = {
+ type: TasksActionTypes.EXPORT_DATASET_SUCCESS,
+ payload: {
+ task,
+ exporter,
+ },
+ };
+
+ return action;
+}
+
+function exportDatasetFailed(task: any, exporter: any, error: any): AnyAction {
+ const action = {
+ type: TasksActionTypes.EXPORT_DATASET_FAILED,
+ payload: {
+ task,
+ exporter,
+ error,
+ },
+ };
+
+ return action;
+}
+
+export function exportDatasetAsync(task: any, exporter: any):
+ThunkAction, {}, {}, AnyAction> {
+ return async (dispatch: ActionCreator): Promise => {
+ dispatch(exportDataset(task, exporter));
+
+ try {
+ const url = await task.annotations.exportDataset(exporter.tag);
+ // false positive
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
+ window.open(url, '_blank');
+ } catch (error) {
+ dispatch(exportDatasetFailed(task, exporter, error));
+ }
+
+ dispatch(exportDatasetSuccess(task, exporter));
+ };
+}
+
function deleteTask(taskID: number): AnyAction {
const action = {
type: TasksActionTypes.DELETE_TASK,
diff --git a/cvat-ui/src/actions/users-actions.ts b/cvat-ui/src/actions/users-actions.ts
index c64cf111..3d75197a 100644
--- a/cvat-ui/src/actions/users-actions.ts
+++ b/cvat-ui/src/actions/users-actions.ts
@@ -14,7 +14,7 @@ export enum UsersActionTypes {
function getUsers(): AnyAction {
const action = {
type: UsersActionTypes.GET_USERS,
- payload: { },
+ payload: {},
};
return action;
@@ -41,8 +41,9 @@ function getUsersFailed(error: any): AnyAction {
export function getUsersAsync():
ThunkAction, {}, {}, AnyAction> {
return async (dispatch: ActionCreator): Promise => {
+ dispatch(getUsers());
+
try {
- dispatch(getUsers());
const users = await core.users.get();
dispatch(
getUsersSuccess(
diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx
index c782aaf2..f6057cb8 100644
--- a/cvat-ui/src/components/actions-menu/actions-menu.tsx
+++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx
@@ -5,24 +5,27 @@ import {
Modal,
} from 'antd';
-import Text from 'antd/lib/typography/Text';
import { ClickParam } from 'antd/lib/menu/index';
import LoaderItemComponent from './loader-item';
import DumperItemComponent from './dumper-item';
-
+import ExportItemComponent from './export-item';
interface ActionsMenuComponentProps {
taskInstance: any;
loaders: any[];
dumpers: any[];
+ exporters: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
+ exportActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
+ inferenceIsActive: boolean;
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
+ onExportDataset: (taskInstance: any, exporter: any) => void;
onDeleteTask: (taskInstance: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
@@ -66,24 +69,22 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
const tracker = props.taskInstance.bugTracker;
const renderModelRunner = props.installedAutoAnnotation ||
props.installedTFAnnotation || props.installedTFSegmentation;
+
return (
diff --git a/cvat-ui/src/components/actions-menu/dumper-item.tsx b/cvat-ui/src/components/actions-menu/dumper-item.tsx
index e9e9c36e..ca862a8e 100644
--- a/cvat-ui/src/components/actions-menu/dumper-item.tsx
+++ b/cvat-ui/src/components/actions-menu/dumper-item.tsx
@@ -11,7 +11,7 @@ import Text from 'antd/lib/typography/Text';
interface DumperItemComponentProps {
taskInstance: any;
dumper: any;
- dumpActivities: string[] | null;
+ dumpActivity: string | null;
onDumpAnnotation: (task: any, dumper: any) => void;
}
@@ -24,11 +24,7 @@ export default function DumperItemComponent(props: DumperItemComponentProps) {
const task = props.taskInstance;
const { mode } = task;
const { dumper } = props;
-
- const dumpingWithThisDumper = (props.dumpActivities || [])
- .filter((_dumper: string) => _dumper === dumper.name)[0];
-
- const pending = !!dumpingWithThisDumper;
+ const pending = !!props.dumpActivity;
return (
diff --git a/cvat-ui/src/components/actions-menu/export-item.tsx b/cvat-ui/src/components/actions-menu/export-item.tsx
new file mode 100644
index 00000000..157fd495
--- /dev/null
+++ b/cvat-ui/src/components/actions-menu/export-item.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+import {
+ Menu,
+ Button,
+ Icon,
+} from 'antd';
+
+import Text from 'antd/lib/typography/Text';
+
+interface DumperItemComponentProps {
+ taskInstance: any;
+ exporter: any;
+ exportActivity: string | null;
+ onExportDataset: (task: any, exporter: any) => void;
+}
+
+export default function DumperItemComponent(props: DumperItemComponentProps) {
+ const task = props.taskInstance;
+ const { exporter } = props;
+ const pending = !!props.exportActivity;
+
+ return (
+
+
+
+ );
+}
+
diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx
index 94c36e11..92719519 100644
--- a/cvat-ui/src/components/create-model-page/create-model-content.tsx
+++ b/cvat-ui/src/components/create-model-page/create-model-content.tsx
@@ -9,8 +9,11 @@ import {
Tooltip,
Modal,
message,
+ notification,
} from 'antd';
+import Text from 'antd/lib/typography/Text';
+
import CreateModelForm, {
CreateModelForm as WrappedCreateModelForm
} from './create-model-form';
@@ -22,7 +25,6 @@ import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
- modelCreatingError: string;
modelCreatingStatus: string;
}
@@ -64,17 +66,17 @@ export default class CreateModelContent extends React.PureComponent {
if (Object.keys(grouppedFiles)
.map((key: string) => grouppedFiles[key])
.filter((val) => !!val).length !== 4) {
- Modal.error({
- title: 'Could not upload a model',
- content: 'Please, specify correct files',
+ notification.error({
+ message: 'Could not upload a model',
+ description: 'Please, specify correct files',
});
} else {
this.props.createModel(data.name, grouppedFiles, data.global);
}
}).catch(() => {
- Modal.error({
- title: 'Could not upload a model',
- content: 'Please, check input fields',
+ notification.error({
+ message: 'Could not upload a model',
+ description: 'Please, check input fields',
});
})
}
@@ -86,13 +88,6 @@ export default class CreateModelContent extends React.PureComponent {
this.modelForm.resetFields();
this.fileManagerContainer.reset();
}
-
- if (!prevProps.modelCreatingError && this.props.modelCreatingError) {
- Modal.error({
- title: 'Could not create task',
- content: this.props.modelCreatingError,
- });
- }
}
public render() {
@@ -106,7 +101,9 @@ export default class CreateModelContent extends React.PureComponent {
- {window.open(guideLink, '_blank')}} type='question-circle'/>
+ {
+ window.open(guideLink, '_blank')
+ }} type='question-circle'/>
@@ -116,11 +113,15 @@ export default class CreateModelContent extends React.PureComponent {
}
/>
+
+ *
+ Select files:
+
this.fileManagerContainer = container
- }/>
+ } withRemote={true}/>
{status && }
diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx
index 44eb7b23..1a196593 100644
--- a/cvat-ui/src/components/create-model-page/create-model-form.tsx
+++ b/cvat-ui/src/components/create-model-page/create-model-form.tsx
@@ -43,14 +43,13 @@ export class CreateModelForm extends React.PureComponent {
return (
);
}
diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx
index 1f1e2e05..e58a8ff1 100644
--- a/cvat-ui/src/components/create-model-page/create-model-page.tsx
+++ b/cvat-ui/src/components/create-model-page/create-model-page.tsx
@@ -13,7 +13,6 @@ import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
- modelCreatingError: string;
modelCreatingStatus: string;
}
@@ -21,10 +20,9 @@ export default function CreateModelPageComponent(props: Props) {
return (
- {`Upload a new model`}
+ Upload a new model
diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx
index 93c100ca..56cc88da 100644
--- a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx
+++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx
@@ -58,28 +58,25 @@ class AdvancedConfigurationForm extends React.PureComponent {
private renderZOrder() {
return (
-
-
- {this.props.form.getFieldDecorator('zOrder', {
- initialValue: false,
- valuePropName: 'checked',
- })(
-
-
- Z-order
-
-
- )}
-
+
+ {this.props.form.getFieldDecorator('zOrder', {
+ initialValue: false,
+ valuePropName: 'checked',
+ })(
+
+
+ Z-order
+
+
+ )}
);
}
private renderImageQuality() {
return (
-
+
- {'Image quality'}
{this.props.form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [{
@@ -102,9 +99,8 @@ class AdvancedConfigurationForm extends React.PureComponent {
private renderOverlap() {
return (
-
+
- {'Overlap size'}
{this.props.form.getFieldDecorator('overlapSize')(
)}
@@ -115,9 +111,8 @@ class AdvancedConfigurationForm extends React.PureComponent {
private renderSegmentSize() {
return (
-
+
- {'Segment size'}
{this.props.form.getFieldDecorator('segmentSize')(
)}
@@ -128,8 +123,7 @@ class AdvancedConfigurationForm extends React.PureComponent {
private renderStartFrame() {
return (
-
- {'Start frame'}
+
{this.props.form.getFieldDecorator('startFrame')(
{
private renderStopFrame() {
return (
-
- {'Stop frame'}
+
{this.props.form.getFieldDecorator('stopFrame')(
{
private renderFrameStep() {
return (
-
- {'Frame step'}
+
{this.props.form.getFieldDecorator('frameStep')(
{
private renderGitLFSBox() {
return (
-
-
- {this.props.form.getFieldDecorator('lfs', {
- valuePropName: 'checked',
- initialValue: false,
- })(
-
-
- Use LFS (Large File Support)
-
-
- )}
-
+
+ {this.props.form.getFieldDecorator('lfs', {
+ valuePropName: 'checked',
+ initialValue: false,
+ })(
+
+
+ Use LFS (Large File Support):
+
+
+ )}
);
}
private renderGitRepositoryURL() {
return (
-
-
- {'Dataset repository URL'}
- {this.props.form.getFieldDecorator('repository', {
- rules: [{
- validator: (_, value, callback) => {
+
+ {this.props.form.getFieldDecorator('repository', {
+ rules: [{
+ validator: (_, value, callback) => {
+ if (!value) {
+ callback();
+ } else {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
@@ -213,14 +207,13 @@ class AdvancedConfigurationForm extends React.PureComponent {
callback();
}
- }]
- })(
-
- )}
-
+ }
+ }]
+ })(
+
+ )}
);
}
@@ -231,7 +224,6 @@ class AdvancedConfigurationForm extends React.PureComponent {
{this.renderGitRepositoryURL()}
-
@@ -245,19 +237,24 @@ class AdvancedConfigurationForm extends React.PureComponent {
private renderBugTracker() {
return (
-
-
- {'Issue tracker'}
- {this.props.form.getFieldDecorator('bugTracker', {
- rules: [{
- ...patterns.validateURL,
- }]
- })(
-
- )}
-
+
+ {this.props.form.getFieldDecorator('bugTracker', {
+ rules: [{
+ validator: (_, value, callback) => {
+ if (value && !patterns.validateURL.pattern.test(value)) {
+ callback('Issue tracker must be URL');
+ } else {
+ callback();
+ }
+ }
+ }]
+ })(
+
+ )}
)
}
diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
index 2d0d5d0a..e0b877a4 100644
--- a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
+++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx
@@ -39,8 +39,7 @@ class BasicConfigurationForm extends React.PureComponent {
const { getFieldDecorator } = this.props.form;
return (
+
{ getFieldDecorator('name', {
rules: [{
required: true,
diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx
index 9b4ac355..57801a1b 100644
--- a/cvat-ui/src/components/create-task-page/create-task-content.tsx
+++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx
@@ -4,10 +4,9 @@ import {
Row,
Col,
Alert,
- Modal,
Button,
Collapse,
- message,
+ notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@@ -28,7 +27,6 @@ export interface CreateTaskData {
interface Props {
onCreate: (data: CreateTaskData) => void;
status: string;
- error: string;
installedGit: boolean;
}
@@ -90,17 +88,17 @@ export default class CreateTaskContent extends React.PureComponent
private handleSubmitClick = () => {
if (!this.validateLabels()) {
- Modal.error({
- title: 'Could not create a task',
- content: 'A task must contain at least one label',
+ notification.error({
+ message: 'Could not create a task',
+ description: 'A task must contain at least one label',
});
return;
}
if (!this.validateFiles()) {
- Modal.error({
- title: 'Could not create a task',
- content: 'A task must contain at least one file',
+ notification.error({
+ message: 'Could not create a task',
+ description: 'A task must contain at least one file',
});
return;
}
@@ -117,9 +115,9 @@ export default class CreateTaskContent extends React.PureComponent
this.props.onCreate(this.state);
})
.catch((_: any) => {
- Modal.error({
- title: 'Could not create a task',
- content: 'Please, check configuration you specified',
+ notification.error({
+ message: 'Could not create a task',
+ description: 'Please, check configuration you specified',
});
});
}
@@ -137,7 +135,8 @@ export default class CreateTaskContent extends React.PureComponent
private renderLabelsBlock() {
return (
- Labels
+ *
+ Labels:
private renderFilesBlock() {
return (
+ *
+ Select files:
this.fileManagerContainer = container
@@ -169,7 +170,7 @@ export default class CreateTaskContent extends React.PureComponent
{'Advanced configuration'}
+ Advanced configuration
} key='1'>
}
public componentDidUpdate(prevProps: Props) {
- if (this.props.error && prevProps.error !== this.props.error) {
- Modal.error({
- title: 'Could not create task',
- content: this.props.error,
- });
- }
-
if (this.props.status === 'CREATED' && prevProps.status !== 'CREATED') {
- message.success('The task has been created');
+ notification.info({
+ message: 'The task has been created',
+ });
this.basicConfigurationComponent.resetFields();
if (this.advancedConfigurationComponent) {
@@ -213,12 +209,12 @@ export default class CreateTaskContent extends React.PureComponent
public render() {
const loading = !!this.props.status
&& this.props.status !== 'CREATED'
- && !this.props.error;
+ && this.props.status !== 'FAILED';
return (
- {'Basic configuration'}
+ Basic configuration
{ this.renderBasicBlock() }
diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx
index d6155843..2287ab5c 100644
--- a/cvat-ui/src/components/create-task-page/create-task-page.tsx
+++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx
@@ -11,7 +11,6 @@ import CreateTaskContent, { CreateTaskData } from './create-task-content';
interface Props {
onCreate: (data: CreateTaskData) => void;
- error: string;
status: string;
installedGit: boolean;
}
@@ -20,10 +19,9 @@ export default function CreateTaskPage(props: Props) {
return (
- {'Create a new task'}
+ Create a new task
diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx
index 6d7ad26f..ba9c40d7 100644
--- a/cvat-ui/src/components/cvat-app.tsx
+++ b/cvat-ui/src/components/cvat-app.tsx
@@ -1,12 +1,17 @@
import React from 'react';
-
-import { Switch, Route, Redirect } from 'react-router';
-import { BrowserRouter } from 'react-router-dom';
-
-import { Spin, Layout, Modal } from 'antd';
-
import 'antd/dist/antd.css';
import '../stylesheet.css';
+import { BrowserRouter } from 'react-router-dom';
+import {
+ Switch,
+ Route,
+ Redirect,
+} from 'react-router';
+import {
+ Spin,
+ Layout,
+ notification,
+} from 'antd';
import TasksPageContainer from '../containers/tasks-page/tasks-page';
import CreateTaskPageContainer from '../containers/create-task-page/create-task-page';
@@ -20,22 +25,26 @@ import HeaderContainer from '../containers/header/header';
import ModelRunnerModalContainer from '../containers/model-runner-dialog/model-runner-dialog';
import FeedbackComponent from './feedback';
+import { NotificationsState } from '../reducers/interfaces';
type CVATAppProps = {
loadFormats: () => void;
loadUsers: () => void;
verifyAuthorized: () => void;
initPlugins: () => void;
- pluginsInitialized: boolean;
+ resetErrors: () => void;
+ resetMessages: () => void;
userInitialized: boolean;
+ pluginsInitialized: boolean;
+ pluginsFetching: boolean;
formatsInitialized: boolean;
+ formatsFetching: boolean;
usersInitialized: boolean;
- gettingAuthError: string;
- gettingFormatsError: string;
- gettingUsersError: string;
+ usersFetching: boolean;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
+ notifications: NotificationsState;
user: any;
}
@@ -44,54 +53,141 @@ export default class CVATApplication extends React.PureComponent {
super(props);
}
- public componentDidMount() {
- this.props.verifyAuthorized();
- }
+ private showMessages() {
+ function showMessage(title: string) {
+ notification.info({
+ message: title,
+ duration: null,
+ });
+ }
- public componentDidUpdate() {
- if (!this.props.userInitialized ||
- this.props.userInitialized && this.props.user == null) {
- return;
+ const { tasks } = this.props.notifications.messages;
+ const { models } = this.props.notifications.messages;
+ let shown = !!tasks.loadingDone || !!models.inferenceDone;
+
+ if (tasks.loadingDone) {
+ showMessage(tasks.loadingDone);
}
+ if (models.inferenceDone) {
+ showMessage(models.inferenceDone);
+ }
+
+ if (shown) {
+ this.props.resetMessages();
+ }
+ }
- if (this.props.gettingAuthError) {
- Modal.error({
- title: 'Could not check authorization',
- content: `${this.props.gettingAuthError}`,
+ private showErrors() {
+ function showError(title: string, _error: any) {
+ const error = _error.toString();
+ notification.error({
+ message: title,
+ duration: null,
+ description: error.length > 200 ? '' : error,
});
- return;
+
+ console.error(error);
}
- if (!this.props.formatsInitialized) {
- this.props.loadFormats();
- return;
+ const { auth } = this.props.notifications.errors;
+ const { tasks } = this.props.notifications.errors;
+ const { formats } = this.props.notifications.errors;
+ const { users } = this.props.notifications.errors;
+ const { share } = this.props.notifications.errors;
+ const { models } = this.props.notifications.errors;
+
+ let shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register
+ || !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading
+ || !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching
+ || !!users.fetching || !!share.fetching || !!models.creating || !!models.starting
+ || !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching;
+
+ if (auth.authorized) {
+ showError('Could not check authorization on the server', auth.authorized);
+ }
+ if (auth.login) {
+ showError('Could not login on the server', auth.login);
+ }
+ if (auth.register) {
+ showError('Could not register on the server', auth.register);
+ }
+ if (auth.logout) {
+ showError('Could not logout on the server', auth.logout);
+ }
+ if (tasks.fetching) {
+ showError('Could not fetch tasks from the server', tasks.fetching);
+ }
+ if (tasks.updating) {
+ showError('Could not update task on the server', tasks.updating);
+ }
+ if (tasks.dumping) {
+ showError('Could not dump annotations from the server', tasks.dumping);
+ }
+ if (tasks.loading) {
+ showError('Could not upload annotations to the server', tasks.loading);
+ }
+ if (tasks.exporting) {
+ showError('Could not export task from the server', tasks.exporting);
+ }
+ if (tasks.deleting) {
+ showError('Could not delete task on the server', tasks.deleting);
+ }
+ if (tasks.creating) {
+ showError('Could not create task on the server', tasks.creating);
+ }
+ if (formats.fetching) {
+ showError('Could not get annotations and dataset formats from the server', formats.fetching);
+ }
+ if (users.fetching) {
+ showError('Could not get users from the server', users.fetching);
+ }
+ if (share.fetching) {
+ showError('Could not get share info from the server', share.fetching);
+ }
+ if (models.creating) {
+ showError('Could not create model on the server', models.creating);
+ }
+ if (models.starting) {
+ showError('Could not run model on the server', models.starting);
+ }
+ if (models.fetching) {
+ showError('Could not get models from the server', models.fetching);
+ }
+ if (models.deleting) {
+ showError('Could not delete model from the server', models.deleting);
+ }
+ if (models.inferenceStatusFetching) {
+ showError('Could not fetch inference status from the server', models.inferenceStatusFetching);
}
- if (this.props.gettingFormatsError) {
- Modal.error({
- title: 'Could not receive annotations formats',
- content: `${this.props.gettingFormatsError}`,
- });
- return;
+ if (shown) {
+ this.props.resetErrors();
}
+ }
- if (!this.props.usersInitialized) {
- this.props.loadUsers();
+ public componentDidMount() {
+ this.props.verifyAuthorized();
+ }
+
+ public componentDidUpdate() {
+ this.showErrors();
+ this.showMessages();
+
+ if (!this.props.userInitialized || this.props.user == null) {
+ // not authorized user
return;
}
- if (this.props.gettingUsersError) {
- Modal.error({
- title: 'Could not receive users',
- content: `${this.props.gettingUsersError}`,
- });
+ if (!this.props.formatsInitialized && !this.props.formatsFetching) {
+ this.props.loadFormats();
+ }
- return;
+ if (!this.props.usersInitialized && !this.props.usersFetching) {
+ this.props.loadUsers();
}
- if (!this.props.pluginsInitialized) {
+ if (!this.props.pluginsInitialized && !this.props.pluginsFetching) {
this.props.initPlugins();
- return;
}
}
diff --git a/cvat-ui/src/components/feedback.tsx b/cvat-ui/src/components/feedback.tsx
index bd00ea81..e4bbf7e8 100644
--- a/cvat-ui/src/components/feedback.tsx
+++ b/cvat-ui/src/components/feedback.tsx
@@ -98,7 +98,7 @@ export default class Feedback extends React.PureComponent<{}, State> {
Help to make CVAT better
+ Help to make CVAT better
}
content={this.renderContent()}
visible={this.state.active}
diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx
index 6f27f2ca..e34aeaba 100644
--- a/cvat-ui/src/components/file-manager/file-manager.tsx
+++ b/cvat-ui/src/components/file-manager/file-manager.tsx
@@ -81,13 +81,13 @@ export default class FileManager extends React.PureComponent {
Support for a bulk images or a single video
- { this.state.files.local.length ?
+ { !!this.state.files.local.length &&
<>
- {this.state.files.local.length} file(s) selected
+ {`${this.state.files.local.length} file(s) selected`}
- > : null
+ >
}
);
@@ -184,13 +184,12 @@ export default class FileManager extends React.PureComponent {
public render() {
return (
<>
- {'Select files'}
this.setState({
active: activeKey as any,
})}>
{ this.renderLocalSelector() }
{ this.renderShareSelector() }
- { this.props.withRemote ? this.renderRemoteSelector() : null }
+ { this.props.withRemote && this.renderRemoteSelector() }
>
);
diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx
index 5f04b5ab..0efeb86b 100644
--- a/cvat-ui/src/components/header/header.tsx
+++ b/cvat-ui/src/components/header/header.tsx
@@ -8,7 +8,6 @@ import {
Icon,
Button,
Menu,
- Modal,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@@ -24,89 +23,70 @@ interface HeaderContainerProps {
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
username: string;
- logoutError: string;
}
type Props = HeaderContainerProps & RouteComponentProps;
-class HeaderContainer extends React.PureComponent {
- private cvatLogo: React.FunctionComponent;
- private userLogo: React.FunctionComponent;
+const cvatLogo = () =>
;
+const userLogo = () =>
;
- public constructor(props: Props) {
- super(props);
- this.cvatLogo = () =>
;
- this.userLogo = () =>
;
- }
+function HeaderContainer(props: Props) {
+ const renderModels = props.installedAutoAnnotation
+ || props.installedTFAnnotation
+ || props.installedTFSegmentation;
+ return (
+
+
+
- public componentDidUpdate(prevProps: Props) {
- if (!prevProps.logoutError && this.props.logoutError) {
- Modal.error({
- title: 'Could not logout',
- content: `${this.props.logoutError}`,
- });
- }
- }
-
- public render() {
- const { props } = this;
- const renderModels = props.installedAutoAnnotation
- || props.installedTFAnnotation
- || props.installedTFSegmentation;
- return (
-
-
-
-
-
- { renderModels ?
- : null
- }
- { props.installedAnalytics ?
- : null
- }
-
-
+
+ { renderModels ?
+ : null
+ }
+ { props.installedAnalytics ?
-
-
+
+
+
+
-
-
- );
- }
+
+ }>
+ Logout
+
+
+
+
+ );
}
export default withRouter(HeaderContainer);
diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx
index 6c7f24a1..f49b2e22 100644
--- a/cvat-ui/src/components/labels-editor/labels-editor.tsx
+++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import {
Tabs,
Icon,
- Modal,
+ notification,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@@ -128,9 +128,9 @@ export default class LabelsEditor
private handleDelete = (label: Label) => {
// the label is saved on the server, cannot delete it
if (typeof(label.id) !== 'undefined' && label.id >= 0) {
- Modal.error({
- title: 'Could not delete the label',
- content: 'It has been already saved on the server',
+ notification.error({
+ message: 'Could not delete the label',
+ description: 'It has been already saved on the server',
});
}
diff --git a/cvat-ui/src/components/login-page/login-page.tsx b/cvat-ui/src/components/login-page/login-page.tsx
index 96d76da3..fb2befa0 100644
--- a/cvat-ui/src/components/login-page/login-page.tsx
+++ b/cvat-ui/src/components/login-page/login-page.tsx
@@ -8,13 +8,11 @@ import Text from 'antd/lib/typography/Text';
import {
Col,
Row,
- Modal,
} from 'antd';
import LoginForm, { LoginData } from './login-form';
interface LoginPageComponentProps {
- loginError: string;
onLogin: (username: string, password: string) => void;
}
@@ -27,13 +25,6 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
xl: { span: 4 },
}
- if (props.loginError) {
- Modal.error({
- title: 'Could not login',
- content: props.loginError,
- });
- }
-
return (
diff --git a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx
index aeb04393..5b347713 100644
--- a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx
+++ b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx
@@ -14,23 +14,23 @@ import {
import { Model } from '../../reducers/interfaces';
+interface StringObject {
+ [index: string]: string;
+}
+
interface Props {
+ modelsFetching: boolean;
modelsInitialized: boolean;
models: Model[];
- activeProcesses: {
- [index: string]: string;
- };
+ activeProcesses: StringObject;
visible: boolean;
taskInstance: any;
- startingError: string;
getModels(): void;
closeDialog(): void;
runInference(
taskInstance: any,
model: Model,
- mapping: {
- [index: string]: string
- },
+ mapping: StringObject,
cleanOut: boolean,
): void;
}
@@ -38,12 +38,8 @@ interface Props {
interface State {
selectedModel: string | null;
cleanOut: boolean;
- mapping: {
- [index: string]: string;
- };
- colors: {
- [index: string]: string;
- };
+ mapping: StringObject;
+ colors: StringObject;
matching: {
model: string,
task: string,
@@ -277,7 +273,11 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === this.state.selectedModel)[0];
+ if (!model.primary) {
+ let taskLabels: string[] = this.props.taskInstance.labels
+ .map((label: any) => label.name);
+ const defaultMapping: StringObject = model.labels
+ .reduce((acc: StringObject, label) => {
+ if (taskLabels.includes(label)) {
+ acc[label] = label;
+ taskLabels = taskLabels.filter((_label) => _label !== label)
+ }
- public componentDidMount() {
- if (!this.props.modelsInitialized) {
- this.props.getModels();
+ return acc;
+ }, {});
+
+ this.setState({
+ mapping: defaultMapping,
+ });
+ }
}
}
diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx
index f6d3df50..8db88abe 100644
--- a/cvat-ui/src/components/models-page/models-page.tsx
+++ b/cvat-ui/src/components/models-page/models-page.tsx
@@ -14,8 +14,8 @@ interface Props {
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
- modelsAreBeingFetched: boolean;
- modelsFetchingError: any;
+ modelsInitialized: boolean;
+ modelsFetching: boolean;
registeredUsers: any[];
models: Model[];
getModels(): void;
@@ -23,7 +23,7 @@ interface Props {
}
export default function ModelsPageComponent(props: Props) {
- if (props.modelsAreBeingFetched) {
+ if (!props.modelsInitialized && !props.modelsFetching) {
props.getModels();
return (
@@ -35,15 +35,18 @@ export default function ModelsPageComponent(props: Props) {
return (
- { integratedModels.length ?
- : null }
- { uploadedModels.length &&
+ { !!integratedModels.length &&
+
+ }
+ { !!uploadedModels.length &&
- } { props.installedAutoAnnotation &&
+ }
+ { props.installedAutoAnnotation &&
+ !uploadedModels.length &&
!props.installedTFAnnotation &&
!props.installedTFSegmentation &&
diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx
index 01d48897..bad3f1a6 100644
--- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx
+++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx
@@ -7,7 +7,6 @@ import {
Select,
Menu,
Dropdown,
- Button,
Icon,
} from 'antd';
@@ -62,7 +61,7 @@ export default function UploadedModelItem(props: Props) {
Actions
+