Skip to content

Commit

Permalink
Merge branch 'zm/validation-management-core' into bs/reset_not_releva…
Browse files Browse the repository at this point in the history
…nt_cache
  • Loading branch information
zhiltsov-max authored Oct 3, 2024
2 parents e88d0ad + 876237d commit fccf64d
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 110 deletions.
8 changes: 2 additions & 6 deletions cvat-ui/src/actions/tasks-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,8 @@ ThunkAction {
validation_params: {
mode: data.quality.validationMode,
frame_selection_method: data.quality.frameSelectionMethod,
frame_share: data.quality.validationFramesPercent ? (
data.quality.validationFramesPercent / 100
) : data.quality.validationFramesPercent,
frames_per_job_share: data.quality.validationFramesPerJobPercent ? (
data.quality.validationFramesPerJobPercent / 100
) : data.quality.validationFramesPerJobPercent,
frame_share: data.quality.validationFramesPercent,
frames_per_job_share: data.quality.validationFramesPerJobPercent,
},
};
}
Expand Down
63 changes: 29 additions & 34 deletions cvat-ui/src/components/create-task-page/create-task-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { RemoteFile } from 'components/file-manager/remote-browser';
import { getFileContentType, getContentTypeRemoteFile, getFileNameFromPath } from 'utils/files';

import { FrameSelectionMethod } from 'components/create-job-page/job-form';
import { ArgumentError } from 'cvat-core/src/exceptions';
import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form';
import ProjectSearchField from './project-search-field';
import ProjectSubsetField from './project-subset-field';
Expand Down Expand Up @@ -441,7 +440,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
};

private validateBlocks = (): Promise<any> => new Promise((resolve, reject) => {
const { projectId, quality } = this.state;
const { projectId } = this.state;
if (!this.validateLabelsOrProject()) {
notification.error({
message: 'Could not create a task',
Expand All @@ -452,21 +451,6 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
return;
}

try {
this.validateQualityParams(quality);
} catch (e) {
if (e instanceof Error) {
notification.error({
message: 'Could not create a task',
description: e.message,
className: 'cvat-notification-create-task-fail',
});
reject();
return;
}
throw e;
}

if (!this.validateFiles()) {
notification.error({
message: 'Could not create a task',
Expand All @@ -485,7 +469,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
this.basicConfigurationComponent.current
.submit()
.then(() => {
const promises = [];
const promises: Promise<void>[] = [];

if (this.advancedConfigurationComponent.current) {
promises.push(this.advancedConfigurationComponent.current.submit());
Expand All @@ -495,7 +479,33 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
promises.push(this.qualityConfigurationComponent.current.submit());
}

return Promise.all(promises);
const formIsValid = new Promise<void>((_resolve, _reject) => {
Promise.all(promises).then(() => {
const { quality, advanced } = this.state;
if (
quality.validationMode === ValidationMode.HONEYPOTS &&
advanced.sortingMethod !== SortingMethod.RANDOM
) {
this.setState({
advanced: {
...advanced,
sortingMethod: SortingMethod.RANDOM,
},
}, () => {
_resolve();
notification.info({
message: 'Task parameters were automatically updated',
description: 'Sorting method has been updated as Honeypots' +
' quality method only supports RANDOM sorting',
});
});
} else {
_resolve();
}
}).catch(_reject);
});

return formIsValid;
}).then(() => {
if (projectId) {
return core.projects.get({ id: projectId }).then((response) => {
Expand Down Expand Up @@ -779,21 +789,6 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
basic.name;
};

private validateQualityParams(value: QualityConfiguration): QualityConfiguration {
const { advanced } = this.state;
if (
advanced.sortingMethod !== SortingMethod.RANDOM &&
value.validationMode === ValidationMode.HONEYPOTS
) {
throw new ArgumentError(
`Validation mode ${value.validationMode} ` +
`can only be used with ${SortingMethod.RANDOM} sorting method`,
);
}

return value;
}

private renderBasicBlock(): JSX.Element {
const { many } = this.props;
const exampleMultiTaskName = many ? this.getTaskName(0, 'local', 'fileName.mp4') : '';
Expand Down
130 changes: 64 additions & 66 deletions cvat-ui/src/components/create-task-page/quality-configuration-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ import Input from 'antd/lib/input';
import Form, { FormInstance } from 'antd/lib/form';
import { PercentageOutlined } from '@ant-design/icons';
import Radio from 'antd/lib/radio';
import { FrameSelectionMethod } from 'components/create-job-page/job-form';
import { Col, Row } from 'antd/lib/grid';
import Select from 'antd/lib/select';

import { FrameSelectionMethod } from 'components/create-job-page/job-form';

export interface QualityConfiguration {
validationMode: ValidationMode;
validationFramesPercent: number;
validationFramesPerJobPercent: number;
validationFramesPercent?: number;
validationFramesPerJobPercent?: number;
frameSelectionMethod: FrameSelectionMethod;
}

interface Props {
onSubmit(values: QualityConfiguration): Promise<void>;
initialValues: QualityConfiguration;
frameSelectionMethod: FrameSelectionMethod;
onChangeFrameSelectionMethod: (method: FrameSelectionMethod) => void;
validationMode: ValidationMode;
onSubmit(values: QualityConfiguration): Promise<void>;
onChangeFrameSelectionMethod: (method: FrameSelectionMethod) => void;
onChangeValidationMode: (method: ValidationMode) => void;
}

Expand All @@ -44,9 +45,18 @@ export default class QualityConfigurationForm extends React.PureComponent<Props>
public submit(): Promise<void> {
const { onSubmit } = this.props;
if (this.formRef.current) {
return this.formRef.current.validateFields().then((values: QualityConfiguration) => {
onSubmit(values);
});
return this.formRef.current.validateFields().then((values: QualityConfiguration) => onSubmit({
...values,
frameSelectionMethod: values.validationMode === ValidationMode.HONEYPOTS ?
FrameSelectionMethod.RANDOM : values.frameSelectionMethod,
...(typeof values.validationFramesPercent === 'number' ? {
validationFramesPercent: values.validationFramesPercent / 100,
} : {}),
...(typeof values.validationFramesPerJobPercent === 'number' ? {
validationFramesPerJobPercent: values.validationFramesPerJobPercent / 100,
} : {}),
}),
);
}

return Promise.reject(new Error('Quality form ref is empty'));
Expand Down Expand Up @@ -78,57 +88,54 @@ export default class QualityConfigurationForm extends React.PureComponent<Props>
</Col>

{
(frameSelectionMethod === FrameSelectionMethod.RANDOM) ?
(
<Col span={7}>
<Form.Item
label='Quantity'
name='validationFramesPercent'
normalize={(value) => +value}
rules={[
{ required: true, message: 'The field is required' },
{
type: 'number', min: 0, max: 100, message: 'Value is not valid',
},
]}
>
<Input
size='large'
type='number'
min={0}
max={100}
suffix={<PercentageOutlined />}
/>
</Form.Item>
</Col>
) : ('')
frameSelectionMethod === FrameSelectionMethod.RANDOM && (
<Col span={7}>
<Form.Item
label='Quantity'
name='validationFramesPercent'
normalize={(value) => +value}
rules={[
{ required: true, message: 'The field is required' },
{
type: 'number', min: 0, max: 100, message: 'Value is not valid',
},
]}
>
<Input
size='large'
type='number'
min={0}
max={100}
suffix={<PercentageOutlined />}
/>
</Form.Item>
</Col>
)
}

{
(frameSelectionMethod === FrameSelectionMethod.RANDOM_PER_JOB) ?
(
<Col span={7}>
<Form.Item
label='Quantity per job'
name='validationFramesPerJobPercent'
normalize={(value) => +value}
rules={[
{ required: true, message: 'The field is required' },
{
type: 'number', min: 0, max: 100, message: 'Value is not valid',
},
]}
>
<Input
size='large'
type='number'
min={0}
max={100}
suffix={<PercentageOutlined />}
/>
</Form.Item>
</Col>
) : ('')
frameSelectionMethod === FrameSelectionMethod.RANDOM_PER_JOB && (
<Col span={7}>
<Form.Item
label='Quantity per job'
name='validationFramesPerJobPercent'
normalize={(value) => +value}
rules={[
{ required: true, message: 'The field is required' },
{
type: 'number', min: 0, max: 100, message: 'Value is not valid',
},
]}
>
<Input
size='large'
type='number'
min={0}
max={100}
suffix={<PercentageOutlined />}
/>
</Form.Item>
</Col>
)
}
</>
);
Expand All @@ -137,15 +144,6 @@ export default class QualityConfigurationForm extends React.PureComponent<Props>
private honeypotsParamsBlock(): JSX.Element {
return (
<Row>
<Col>
<Form.Item
name='frameSelectionMethod'
label='Frame selection method'
rules={[{ required: true, message: 'Please, specify frame selection method' }]}
hidden
initialValue={FrameSelectionMethod.RANDOM}
/>
</Col>
<Col span={7}>
<Form.Item
label='Total honeypots'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ interface TableRowData extends RowData {
key: Key;
}

// Temporary solution: this function is necessary in one of plugins which imports it directly from CVAT code
// Further this solution should be re-designed
// Until then, *DO NOT RENAME/REMOVE* this exported function
export function getAllocationTableContents(gtJobMeta: FramesMetaData, gtJob: Job): TableRowData[] {
// A workaround for meta "includedFrames" using source data numbers
// TODO: remove once meta is migrated to relative frame numbers
Expand Down
20 changes: 20 additions & 0 deletions cvat/apps/engine/model_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (C) 2024 CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT

from __future__ import annotations

from typing import TypeVar, Union

_T = TypeVar("_T")


class Undefined:
pass


MaybeUndefined = Union[_T, Undefined]
"""
The reverse side of one-to-one relationship.
May be undefined in the object, should be accessed via getattr().
"""
19 changes: 15 additions & 4 deletions cvat/apps/engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
from django.db.models import Q, TextChoices
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from cvat.apps.engine.lazy_list import LazyList

from cvat.apps.engine.lazy_list import LazyList
from cvat.apps.engine.model_utils import MaybeUndefined
from cvat.apps.engine.utils import parse_specific_attributes, chunked_list
from cvat.apps.events.utils import cache_deleted

Expand Down Expand Up @@ -292,14 +293,20 @@ class Data(models.Model):
sorting_method = models.CharField(max_length=15, choices=SortingMethod.choices(), default=SortingMethod.LEXICOGRAPHICAL)
deleted_frames = IntArrayField(store_sorted=True, unique_values=True)

validation_params: ValidationParams
images: models.manager.RelatedManager[Image]
video: MaybeUndefined[Video]
related_files: models.manager.RelatedManager[RelatedFile]
validation_layout: MaybeUndefined[ValidationLayout]

client_files: models.manager.RelatedManager[ClientFile]
server_files: models.manager.RelatedManager[ServerFile]
remote_files: models.manager.RelatedManager[RemoteFile]
validation_params: MaybeUndefined[ValidationParams]
"""
Represents user-requested validation params before task is created.
After the task creation, 'validation_layout' is used instead.
"""

validation_layout: ValidationLayout

class Meta:
default_permissions = ()

Expand Down Expand Up @@ -535,6 +542,8 @@ class Task(TimestampedModel):
target_storage = models.ForeignKey('Storage', null=True, default=None,
blank=True, on_delete=models.SET_NULL, related_name='+')

segment_set: models.manager.RelatedManager[Segment]

# Extend default permission model
class Meta:
default_permissions = ()
Expand Down Expand Up @@ -701,6 +710,8 @@ class Segment(models.Model):
# SegmentType.SPECIFIC_FRAMES fields
frames = IntArrayField(store_sorted=True, unique_values=True, default='', blank=True)

job_set: models.manager.RelatedManager[Job]

def contains_frame(self, idx: int) -> bool:
return idx in self.frame_set

Expand Down
1 change: 1 addition & 0 deletions dev/format_python_code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ for paths in \
"cvat/apps/engine/cache.py" \
"cvat/apps/engine/default_settings.py" \
"cvat/apps/engine/field_validation.py" \
"cvat/apps/engine/model_utils.py" \
; do
${BLACK} -- ${paths}
${ISORT} -- ${paths}
Expand Down

0 comments on commit fccf64d

Please sign in to comment.