You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

505 lines
15 KiB
TypeScript

// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import Checkbox from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Text from 'antd/lib/typography/Text';
import patterns from 'utils/validation-patterns';
export interface AdvancedConfiguration {
bugTracker?: string;
zOrder: boolean;
imageQuality?: number;
overlapSize?: number;
segmentSize?: number;
startFrame?: number;
stopFrame?: number;
frameFilter?: string;
lfs: boolean;
repository?: string;
useZipChunks: boolean;
dataChunkSize?: number;
useCache: boolean;
}
type Props = FormComponentProps & {
onSubmit(values: AdvancedConfiguration): void;
installedGit: boolean;
};
function isPositiveInteger(_: any, value: any, callback: any): void {
if (!value) {
callback();
return;
}
const intValue = +value;
if (Number.isNaN(intValue)
|| !Number.isInteger(intValue) || intValue < 1) {
callback('Value must be a positive integer');
}
callback();
}
function isNonNegativeInteger(_: any, value: any, callback: any): void {
if (!value) {
callback();
return;
}
const intValue = +value;
if (Number.isNaN(intValue) || intValue < 0) {
callback('Value must be a non negative integer');
}
callback();
}
function isIntegerRange(min: number, max: number, _: any, value: any, callback: any): void {
if (!value) {
callback();
return;
}
const intValue = +value;
if (Number.isNaN(intValue)
|| !Number.isInteger(intValue)
|| intValue < min || intValue > max
) {
callback(`Value must be an integer [${min}, ${max}]`);
}
callback();
}
class AdvancedConfigurationForm extends React.PureComponent<Props> {
public submit(): Promise<void> {
return new Promise((resolve, reject) => {
const {
form,
onSubmit,
} = this.props;
form.validateFields((error, values): void => {
if (!error) {
const filteredValues = { ...values };
delete filteredValues.frameStep;
if (values.overlapSize && +values.segmentSize <= +values.overlapSize) {
reject(new Error('Segment size must be more than overlap size'));
}
if (typeof (values.startFrame) !== 'undefined' && typeof (values.stopFrame) !== 'undefined'
&& +values.stopFrame < +values.startFrame
) {
reject(new Error('Stop frame must be more or equal start frame'));
}
onSubmit({
...values,
frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined,
});
resolve();
} else {
reject();
}
});
});
}
public resetFields(): void {
const { form } = this.props;
form.resetFields();
}
private renderZOrder(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='Enables order for shapes. Useful for segmentation tasks'>
{form.getFieldDecorator('zOrder', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>
Z-order
</Text>
</Checkbox>,
)}
</Form.Item>
);
}
private renderImageQuality(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Image quality</span>}>
<Tooltip title='Defines image quality level' mouseLeaveDelay={0}>
{form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [{
required: true,
message: 'The field is required.',
}, {
validator: isIntegerRange.bind(null, 5, 100),
}],
})(
<Input
size='large'
type='number'
suffix={<Icon type='percentage' />}
/>,
)}
</Tooltip>
</Form.Item>
);
}
private renderOverlap(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Overlap size</span>}>
<Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}>
{form.getFieldDecorator('overlapSize', {
rules: [{
validator: isNonNegativeInteger,
}],
})(
<Input size='large' type='number' />,
)}
</Tooltip>
</Form.Item>
);
}
private renderSegmentSize(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Segment size</span>}>
<Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}>
{form.getFieldDecorator('segmentSize', {
rules: [{
validator: isPositiveInteger,
}],
})(
<Input size='large' type='number' />,
)}
</Tooltip>
</Form.Item>
);
}
private renderStartFrame(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Start frame</span>}>
{form.getFieldDecorator('startFrame', {
rules: [{
validator: isNonNegativeInteger,
}],
})(
<Input
size='large'
type='number'
min={0}
step={1}
/>,
)}
</Form.Item>
);
}
private renderStopFrame(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Stop frame</span>}>
{form.getFieldDecorator('stopFrame', {
rules: [{
validator: isNonNegativeInteger,
}],
})(
<Input
size='large'
type='number'
min={0}
step={1}
/>,
)}
</Form.Item>
);
}
private renderFrameStep(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Frame step</span>}>
{form.getFieldDecorator('frameStep', {
rules: [{
validator: isPositiveInteger,
}],
})(
<Input
size='large'
type='number'
min={1}
step={1}
/>,
)}
</Form.Item>
);
}
private renderGitLFSBox(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='If annotation files are large, you can use git LFS feature'>
{form.getFieldDecorator('lfs', {
valuePropName: 'checked',
initialValue: false,
})(
<Checkbox>
<Text className='cvat-text-color'>
Use LFS (Large File Support):
</Text>
</Checkbox>,
)}
</Form.Item>
);
}
private renderGitRepositoryURL(): JSX.Element {
const { form } = this.props;
return (
<Form.Item
hasFeedback
label={<span>Dataset repository URL</span>}
extra='Attach a repository to store annotations there'
>
{form.getFieldDecorator('repository', {
rules: [{
validator: (_, value, callback): void => {
if (!value) {
callback();
} else {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
}
if (path && !patterns.validatePath.pattern.test(path)) {
callback('Git path is not a valid');
}
callback();
}
},
}],
})(
<Input
size='large'
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
/>,
)}
</Form.Item>
);
}
private renderGit(): JSX.Element {
return (
<>
<Row>
<Col>
{this.renderGitRepositoryURL()}
</Col>
</Row>
<Row>
<Col>
{this.renderGitLFSBox()}
</Col>
</Row>
</>
);
}
private renderBugTracker(): JSX.Element {
const { form } = this.props;
return (
<Form.Item
hasFeedback
label={<span>Issue tracker</span>}
extra='Attach issue tracker where the task is described'
>
{form.getFieldDecorator('bugTracker', {
rules: [{
validator: (_, value, callback): void => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
},
}],
})(
<Input size='large' />,
)}
</Form.Item>
);
}
private renderUzeZipChunks(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='Force to use zip chunks as compressed data. Actual for videos only.'>
{form.getFieldDecorator('useZipChunks', {
initialValue: true,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>
Use zip chunks
</Text>
</Checkbox>,
)}
</Form.Item>
);
}
private renderCreateTaskMethod(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='Using cache to store data.'>
{form.getFieldDecorator('useCache', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>
Use cache
</Text>
</Checkbox>,
)}
</Form.Item>
);
}
private renderChunkSize(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Chunk size</span>}>
<Tooltip
title={(
<>
Defines a number of frames to be packed in
a chunk when send from client to server.
Server defines automatically if empty.
<br />
Recommended values:
<br />
1080p or less: 36
<br />
2k or less: 8 - 16
<br />
4k or less: 4 - 8
<br />
More: 1 - 4
</>
)}
mouseLeaveDelay={0}
>
{form.getFieldDecorator('dataChunkSize', {
rules: [{
validator: isPositiveInteger,
}],
})(
<Input size='large' type='number' />,
)}
</Tooltip>
</Form.Item>
);
}
public render(): JSX.Element {
const { installedGit } = this.props;
return (
<Form>
<Row>
<Col>
{this.renderZOrder()}
</Col>
</Row>
<Row>
<Col>
{this.renderUzeZipChunks()}
</Col>
</Row>
<Row>
<Col>
{this.renderCreateTaskMethod()}
</Col>
</Row>
<Row type='flex' justify='start'>
<Col span={7}>
{this.renderImageQuality()}
</Col>
<Col span={7} offset={1}>
{this.renderOverlap()}
</Col>
<Col span={7} offset={1}>
{this.renderSegmentSize()}
</Col>
</Row>
<Row type='flex' justify='start'>
<Col span={7}>
{this.renderStartFrame()}
</Col>
<Col span={7} offset={1}>
{this.renderStopFrame()}
</Col>
<Col span={7} offset={1}>
{this.renderFrameStep()}
</Col>
</Row>
<Row type='flex' justify='start'>
<Col span={7}>
{this.renderChunkSize()}
</Col>
</Row>
{ installedGit ? this.renderGit() : null}
<Row>
<Col>
{this.renderBugTracker()}
</Col>
</Row>
</Form>
);
}
}
export default Form.create<Props>()(AdvancedConfigurationForm);