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.
479 lines
16 KiB
TypeScript
479 lines
16 KiB
TypeScript
// Copyright (C) 2020-2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React, { RefObject } from 'react';
|
|
import { Row, Col } from 'antd/lib/grid';
|
|
import { PercentageOutlined } from '@ant-design/icons';
|
|
import Input from 'antd/lib/input';
|
|
import Select from 'antd/lib/select';
|
|
import Radio from 'antd/lib/radio';
|
|
import Checkbox from 'antd/lib/checkbox';
|
|
import Form, { FormInstance, RuleObject, RuleRender } from 'antd/lib/form';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import { Store } from 'antd/lib/form/interface';
|
|
import CVATTooltip from 'components/common/cvat-tooltip';
|
|
import patterns from 'utils/validation-patterns';
|
|
|
|
const { Option } = Select;
|
|
|
|
export enum SortingMethod {
|
|
LEXICOGRAPHICAL = 'lexicographical',
|
|
NATURAL = 'natural',
|
|
PREDEFINED = 'predefined',
|
|
RANDOM = 'random',
|
|
}
|
|
|
|
export interface AdvancedConfiguration {
|
|
bugTracker?: string;
|
|
imageQuality?: number;
|
|
overlapSize?: number;
|
|
segmentSize?: number;
|
|
startFrame?: number;
|
|
stopFrame?: number;
|
|
frameFilter?: string;
|
|
lfs: boolean;
|
|
format?: string,
|
|
repository?: string;
|
|
useZipChunks: boolean;
|
|
dataChunkSize?: number;
|
|
useCache: boolean;
|
|
copyData?: boolean;
|
|
sortingMethod: SortingMethod;
|
|
}
|
|
|
|
const initialValues: AdvancedConfiguration = {
|
|
imageQuality: 70,
|
|
lfs: false,
|
|
useZipChunks: true,
|
|
useCache: true,
|
|
copyData: false,
|
|
sortingMethod: SortingMethod.LEXICOGRAPHICAL,
|
|
};
|
|
|
|
interface Props {
|
|
onSubmit(values: AdvancedConfiguration): void;
|
|
installedGit: boolean;
|
|
activeFileManagerTab: string;
|
|
dumpers: []
|
|
}
|
|
|
|
function validateURL(_: RuleObject, value: string): Promise<void> {
|
|
if (value && !patterns.validateURL.pattern.test(value)) {
|
|
return Promise.reject(new Error('URL is not a valid URL'));
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
function validateRepositoryPath(_: RuleObject, value: string): Promise<void> {
|
|
if (value && !patterns.validatePath.pattern.test(value)) {
|
|
return Promise.reject(new Error('Repository path is not a valid path'));
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
function validateRepository(_: RuleObject, value: string): Promise<[void, void]> | Promise<void> {
|
|
if (value) {
|
|
const [url, path] = value.split(/\s+/);
|
|
return Promise.all([validateURL(_, url), validateRepositoryPath(_, path)]);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const isInteger = ({ min, max }: { min?: number; max?: number }) => (
|
|
_: RuleObject,
|
|
value?: number | string,
|
|
): Promise<void> => {
|
|
if (typeof value === 'undefined' || value === '') {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const intValue = +value;
|
|
if (Number.isNaN(intValue) || !Number.isInteger(intValue)) {
|
|
return Promise.reject(new Error('Value must be a positive integer'));
|
|
}
|
|
|
|
if (typeof min !== 'undefined' && intValue < min) {
|
|
return Promise.reject(new Error(`Value must be more than ${min}`));
|
|
}
|
|
|
|
if (typeof max !== 'undefined' && intValue > max) {
|
|
return Promise.reject(new Error(`Value must be less than ${max}`));
|
|
}
|
|
|
|
return Promise.resolve();
|
|
};
|
|
|
|
const validateOverlapSize: RuleRender = ({ getFieldValue }): RuleObject => ({
|
|
validator(_: RuleObject, value?: string | number): Promise<void> {
|
|
if (typeof value !== 'undefined' && value !== '') {
|
|
const segmentSize = getFieldValue('segmentSize');
|
|
if (typeof segmentSize !== 'undefined' && segmentSize !== '') {
|
|
if (+segmentSize <= +value) {
|
|
return Promise.reject(new Error('Segment size must be more than overlap size'));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Promise.resolve();
|
|
},
|
|
});
|
|
|
|
const validateStopFrame: RuleRender = ({ getFieldValue }): RuleObject => ({
|
|
validator(_: RuleObject, value?: string | number): Promise<void> {
|
|
if (typeof value !== 'undefined' && value !== '') {
|
|
const startFrame = getFieldValue('startFrame');
|
|
if (typeof startFrame !== 'undefined' && startFrame !== '') {
|
|
if (+startFrame > +value) {
|
|
return Promise.reject(new Error('Start frame must not be more than stop frame'));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Promise.resolve();
|
|
},
|
|
});
|
|
|
|
class AdvancedConfigurationForm extends React.PureComponent<Props> {
|
|
private formRef: RefObject<FormInstance>;
|
|
|
|
public constructor(props: Props) {
|
|
super(props);
|
|
this.formRef = React.createRef<FormInstance>();
|
|
}
|
|
|
|
public submit(): Promise<void> {
|
|
const { onSubmit } = this.props;
|
|
if (this.formRef.current) {
|
|
return this.formRef.current.validateFields().then(
|
|
(values: Store): Promise<void> => {
|
|
const frameFilter = values.frameStep ? `step=${values.frameStep}` : undefined;
|
|
const entries = Object.entries(values).filter(
|
|
(entry: [string, unknown]): boolean => entry[0] !== frameFilter,
|
|
);
|
|
|
|
onSubmit({
|
|
...((Object.fromEntries(entries) as any) as AdvancedConfiguration),
|
|
frameFilter,
|
|
});
|
|
return Promise.resolve();
|
|
},
|
|
);
|
|
}
|
|
|
|
return Promise.reject(new Error('Form ref is empty'));
|
|
}
|
|
|
|
public resetFields(): void {
|
|
if (this.formRef.current) {
|
|
this.formRef.current.resetFields();
|
|
}
|
|
}
|
|
|
|
/* eslint-disable class-methods-use-this */
|
|
private renderCopyDataChechbox(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
help='If you have a low data transfer rate over the network you can copy data into CVAT to speed up work'
|
|
name='copyData'
|
|
valuePropName='checked'
|
|
>
|
|
<Checkbox>
|
|
<Text className='cvat-text-color'>Copy data into CVAT</Text>
|
|
</Checkbox>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderSortingMethodRadio(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
label='Sorting method'
|
|
name='sortingMethod'
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: 'The field is required.',
|
|
},
|
|
]}
|
|
help='Specify how to sort images. It is not relevant for videos.'
|
|
>
|
|
<Radio.Group>
|
|
<Radio value={SortingMethod.LEXICOGRAPHICAL} key={SortingMethod.LEXICOGRAPHICAL}>
|
|
Lexicographical
|
|
</Radio>
|
|
<Radio value={SortingMethod.NATURAL} key={SortingMethod.NATURAL}>Natural</Radio>
|
|
<Radio value={SortingMethod.PREDEFINED} key={SortingMethod.PREDEFINED}>
|
|
Predefined
|
|
</Radio>
|
|
<Radio value={SortingMethod.RANDOM} key={SortingMethod.RANDOM}>Random</Radio>
|
|
</Radio.Group>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderImageQuality(): JSX.Element {
|
|
return (
|
|
<CVATTooltip title='Defines images compression level'>
|
|
<Form.Item
|
|
label='Image quality'
|
|
name='imageQuality'
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: 'The field is required.',
|
|
},
|
|
{ validator: isInteger({ min: 5, max: 100 }) },
|
|
]}
|
|
>
|
|
<Input size='large' type='number' min={5} max={100} suffix={<PercentageOutlined />} />
|
|
</Form.Item>
|
|
</CVATTooltip>
|
|
);
|
|
}
|
|
|
|
private renderOverlap(): JSX.Element {
|
|
return (
|
|
<CVATTooltip title='Defines a number of intersected frames between different segments'>
|
|
<Form.Item
|
|
label='Overlap size'
|
|
name='overlapSize'
|
|
dependencies={['segmentSize']}
|
|
rules={[{ validator: isInteger({ min: 0 }) }, validateOverlapSize]}
|
|
>
|
|
<Input size='large' type='number' min={0} />
|
|
</Form.Item>
|
|
</CVATTooltip>
|
|
);
|
|
}
|
|
|
|
private renderSegmentSize(): JSX.Element {
|
|
return (
|
|
<CVATTooltip title='Defines a number of frames in a segment'>
|
|
<Form.Item label='Segment size' name='segmentSize' rules={[{ validator: isInteger({ min: 1 }) }]}>
|
|
<Input size='large' type='number' min={1} />
|
|
</Form.Item>
|
|
</CVATTooltip>
|
|
);
|
|
}
|
|
|
|
private renderStartFrame(): JSX.Element {
|
|
return (
|
|
<Form.Item label='Start frame' name='startFrame' rules={[{ validator: isInteger({ min: 0 }) }]}>
|
|
<Input size='large' type='number' min={0} step={1} />
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderStopFrame(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
label='Stop frame'
|
|
name='stopFrame'
|
|
dependencies={['startFrame']}
|
|
rules={[{ validator: isInteger({ min: 0 }) }, validateStopFrame]}
|
|
>
|
|
<Input size='large' type='number' min={0} step={1} />
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderFrameStep(): JSX.Element {
|
|
return (
|
|
<Form.Item label='Frame step' name='frameStep' rules={[{ validator: isInteger({ min: 1 }) }]}>
|
|
<Input size='large' type='number' min={1} step={1} />
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderGitLFSBox(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
help='If annotation files are large, you can use git LFS feature'
|
|
name='lfs'
|
|
valuePropName='checked'
|
|
>
|
|
<Checkbox>
|
|
<Text className='cvat-text-color'>Use LFS (Large File Support):</Text>
|
|
</Checkbox>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderGitRepositoryURL(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
hasFeedback
|
|
name='repository'
|
|
label='Dataset repository URL'
|
|
extra='Attach a repository to store annotations there'
|
|
rules={[{ validator: validateRepository }]}
|
|
>
|
|
<Input size='large' placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]' />
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderGitFormat(): JSX.Element {
|
|
const { dumpers } = this.props;
|
|
return (
|
|
<Form.Item
|
|
initialValue='CVAT for video 1.1'
|
|
name='format'
|
|
label='Choose format'
|
|
>
|
|
<Select style={{ width: '100%' }}>
|
|
{
|
|
dumpers.map((dumper: any) => (
|
|
<Option
|
|
key={dumper.name}
|
|
value={dumper.name}
|
|
>
|
|
{dumper.name}
|
|
</Option>
|
|
))
|
|
}
|
|
</Select>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderGit(): JSX.Element {
|
|
return (
|
|
<>
|
|
<Row>
|
|
<Col span={24}>{this.renderGitRepositoryURL()}</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col span={24}>{this.renderGitFormat()}</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col span={24}>{this.renderGitLFSBox()}</Col>
|
|
</Row>
|
|
|
|
</>
|
|
);
|
|
}
|
|
|
|
private renderBugTracker(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
hasFeedback
|
|
name='bugTracker'
|
|
label='Issue tracker'
|
|
extra='Attach issue tracker where the task is described'
|
|
rules={[{ validator: validateURL }]}
|
|
>
|
|
<Input size='large' />
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderUzeZipChunks(): JSX.Element {
|
|
return (
|
|
<Form.Item
|
|
help='Force to use zip chunks as compressed data. Actual for videos only.'
|
|
name='useZipChunks'
|
|
valuePropName='checked'
|
|
>
|
|
<Checkbox>
|
|
<Text className='cvat-text-color'>Use zip chunks</Text>
|
|
</Checkbox>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderCreateTaskMethod(): JSX.Element {
|
|
return (
|
|
<Form.Item help='Using cache to store data.' name='useCache' valuePropName='checked'>
|
|
<Checkbox>
|
|
<Text className='cvat-text-color'>Use cache</Text>
|
|
</Checkbox>
|
|
</Form.Item>
|
|
);
|
|
}
|
|
|
|
private renderChunkSize(): JSX.Element {
|
|
return (
|
|
<CVATTooltip
|
|
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
|
|
</>
|
|
)}
|
|
>
|
|
<Form.Item label='Chunk size' name='dataChunkSize' rules={[{ validator: isInteger({ min: 1 }) }]}>
|
|
<Input size='large' type='number' />
|
|
</Form.Item>
|
|
</CVATTooltip>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const { installedGit, activeFileManagerTab } = this.props;
|
|
return (
|
|
<Form initialValues={initialValues} ref={this.formRef} layout='vertical'>
|
|
<Row>
|
|
<Col>{this.renderSortingMethodRadio()}</Col>
|
|
</Row>
|
|
{activeFileManagerTab === 'share' ? (
|
|
<Row>
|
|
<Col>{this.renderCopyDataChechbox()}</Col>
|
|
</Row>
|
|
) : null}
|
|
<Row>
|
|
<Col>{this.renderUzeZipChunks()}</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col>{this.renderCreateTaskMethod()}</Col>
|
|
</Row>
|
|
<Row 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 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 justify='start'>
|
|
<Col span={7}>{this.renderChunkSize()}</Col>
|
|
</Row>
|
|
|
|
{installedGit ? this.renderGit() : null}
|
|
|
|
<Row>
|
|
<Col span={24}>{this.renderBugTracker()}</Col>
|
|
</Row>
|
|
</Form>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default AdvancedConfigurationForm;
|