Merge pull request #2509 from openvinotoolkit/bs/label_selector_refactoring

Added dedicated component for label selection, added tooltip
main
Boris Sekachev 5 years ago committed by GitHub
commit 0c986a33bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added basic projects implementation (<https://github.com/openvinotoolkit/cvat/pull/2255>)
- Added documentation on how to mount cloud starage(AWS S3 bucket, Azure container, Google Drive) as FUSE (<https://github.com/openvinotoolkit/cvat/pull/2377>)
- Added ability to work with share files without copying inside (<https://github.com/openvinotoolkit/cvat/pull/2377>)
- Tooltips in label selectors (<https://github.com/openvinotoolkit/cvat/pull/2509>)
### Changed

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.11.2",
"version": "1.11.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.11.2",
"version": "1.11.3",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -4,7 +4,6 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Select, { OptionProps } from 'antd/lib/select';
import Button from 'antd/lib/button';
import InputNumber from 'antd/lib/input-number';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
@ -14,6 +13,7 @@ import Text from 'antd/lib/typography/Text';
import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { ShapeType } from 'reducers/interfaces';
import { clamp } from 'utils/math';
import LabelSelector from 'components/label-selector/label-selector';
interface Props {
shapeType: ShapeType;
@ -22,7 +22,7 @@ interface Props {
rectDrawingMethod?: RectDrawingMethod;
cuboidDrawingMethod?: CuboidDrawingMethod;
numberOfPoints?: number;
selectedLabeID: number;
selectedLabelID: number;
repeatShapeShortcut: string;
onChangeLabel(value: string): void;
onChangePoints(value: number | undefined): void;
@ -37,7 +37,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
labels,
shapeType,
minimumPoints,
selectedLabeID,
selectedLabelID,
numberOfPoints,
rectDrawingMethod,
cuboidDrawingMethod,
@ -64,25 +64,12 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='center'>
<Col span={24}>
<Select
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
value={`${selectedLabeID}`}
<LabelSelector
style={{ width: '100%' }}
labels={labels}
value={selectedLabelID}
onChange={onChangeLabel}
>
{labels.map((label: any) => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
/>
</Col>
</Row>
{shapeType === ShapeType.RECTANGLE && (

@ -4,21 +4,23 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip';
import Text from 'antd/lib/typography/Text';
import LabelSelector from 'components/label-selector/label-selector';
interface Props {
labels: any[];
selectedLabeID: number;
selectedLabelID: number;
repeatShapeShortcut: string;
onChangeLabel(value: string): void;
onSetup(labelID: number): void;
}
function SetupTagPopover(props: Props): JSX.Element {
const { labels, selectedLabeID, repeatShapeShortcut, onChangeLabel, onSetup } = props;
const {
labels, selectedLabelID, repeatShapeShortcut, onChangeLabel, onSetup,
} = props;
return (
<div className='cvat-draw-shape-popover-content'>
@ -36,19 +38,18 @@ function SetupTagPopover(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='center'>
<Col span={24}>
<Select value={`${selectedLabeID}`} onChange={onChangeLabel}>
{labels.map((label: any) => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
<LabelSelector
style={{ width: '100%' }}
labels={labels}
value={selectedLabelID}
onChange={onChangeLabel}
/>
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Col span={24}>
<Tooltip title={`Press ${repeatShapeShortcut} to add a tag again`} mouseLeaveDelay={0}>
<Button onClick={() => onSetup(selectedLabeID)}>Tag</Button>
<Button onClick={() => onSetup(selectedLabelID)}>Tag</Button>
</Tooltip>
</Col>
</Row>

@ -6,7 +6,7 @@ import React, { MutableRefObject } from 'react';
import { connect } from 'react-redux';
import Icon from 'antd/lib/icon';
import Popover from 'antd/lib/popover';
import Select, { OptionProps } from 'antd/lib/select';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Text from 'antd/lib/typography/Text';
@ -14,12 +14,15 @@ import Tabs from 'antd/lib/tabs';
import { Row, Col } from 'antd/lib/grid';
import notification from 'antd/lib/notification';
import Progress from 'antd/lib/progress';
import InputNumber from 'antd/lib/input-number';
import { AIToolsIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
import range from 'utils/range';
import getCore from 'cvat-core-wrapper';
import { CombinedState, ActiveControl, Model, ObjectType, ShapeType } from 'reducers/interfaces';
import {
CombinedState, ActiveControl, Model, ObjectType, ShapeType,
} from 'reducers/interfaces';
import {
interactWithCanvas,
fetchAnnotationsAsync,
@ -28,7 +31,7 @@ import {
} from 'actions/annotation-actions';
import { InteractionResult } from 'cvat-canvas/src/typescript/canvas';
import DetectorRunner from 'components/model-runner-modal/detector-runner';
import InputNumber from 'antd/lib/input-number';
import LabelSelector from 'components/label-selector/label-selector';
interface StateToProps {
canvasInstance: Canvas;
@ -178,7 +181,9 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
};
private cancelListener = async (): Promise<void> => {
const { isActivated, jobInstance, frame, fetchAnnotations } = this.props;
const {
isActivated, jobInstance, frame, fetchAnnotations,
} = this.props;
const { interactiveStateID, fetching } = this.state;
if (isActivated) {
@ -313,7 +318,9 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
};
private onTracking = async (e: Event): Promise<void> => {
const { isActivated, jobInstance, frame, curZOrder, fetchAnnotations } = this.props;
const {
isActivated, jobInstance, frame, curZOrder, fetchAnnotations,
} = this.props;
const { activeLabelID } = this.state;
const [label] = jobInstance.task.labels.filter((_label: any): boolean => _label.id === activeLabelID);
@ -457,28 +464,12 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</Row>
<Row type='flex' justify='center'>
<Col span={24}>
<Select
<LabelSelector
style={{ width: '100%' }}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
value={`${activeLabelID}`}
onChange={(value: string) => {
this.setState({ activeLabelID: +value });
}}
>
{labels.map((label: any) => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
labels={labels}
value={activeLabelID}
onChange={(value: any) => this.setState({ activeLabelID: value.id })}
/>
</Col>
</Row>
</>
@ -486,8 +477,12 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
private renderTrackerBlock(): JSX.Element {
const { trackers, canvasInstance, jobInstance, frame, onInteractionStart } = this.props;
const { activeTracker, activeLabelID, fetching, trackingFrames } = this.state;
const {
trackers, canvasInstance, jobInstance, frame, onInteractionStart,
} = this.props;
const {
activeTracker, activeLabelID, fetching, trackingFrames,
} = this.state;
if (!trackers.length) {
return (
@ -516,9 +511,9 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
onChange={this.setActiveTracker}
>
{trackers.map(
(interactor: Model): JSX.Element => (
<Select.Option title={interactor.description} key={interactor.id}>
{interactor.name}
(tracker: Model): JSX.Element => (
<Select.Option title={tracker.description} key={tracker.id}>
{tracker.name}
</Select.Option>
),
)}
@ -650,7 +645,9 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
private renderDetectorBlock(): JSX.Element {
const { jobInstance, detectors, curZOrder, frame, fetchAnnotations } = this.props;
const {
jobInstance, detectors, curZOrder, frame, fetchAnnotations,
} = this.props;
if (!detectors.length) {
return (
@ -682,18 +679,17 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
});
const states = result.map(
(data: any): any =>
new core.classes.ObjectState({
shapeType: data.type,
label: task.labels.filter((label: any): boolean => label.name === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
(data: any): any => new core.classes.ObjectState({
shapeType: data.type,
label: task.labels.filter((label: any): boolean => label.name === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
);
await jobInstance.annotations.put(states);
@ -739,29 +735,31 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
public render(): JSX.Element | null {
const { interactors, detectors, trackers, isActivated, canvasInstance } = this.props;
const {
interactors, detectors, trackers, isActivated, canvasInstance,
} = this.props;
const { fetching, trackingProgress } = this.state;
if (![...interactors, ...detectors, ...trackers].length) return null;
const dynamcPopoverPros = isActivated
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isActivated
? {
className: 'cvat-active-canvas-control cvat-tools-control',
onClick: (): void => {
canvasInstance.interact({ enabled: false });
},
}
: {
className: 'cvat-tools-control',
};
const dynamcPopoverPros = isActivated ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isActivated ?
{
className: 'cvat-active-canvas-control cvat-tools-control',
onClick: (): void => {
canvasInstance.interact({ enabled: false });
},
} :
{
className: 'cvat-tools-control',
};
return (
<>

@ -5,12 +5,12 @@
import React, { useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select, { OptionProps } from 'antd/lib/select';
import Dropdown from 'antd/lib/dropdown';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces';
import LabelSelector from 'components/label-selector/label-selector';
import ItemMenu from './object-item-menu';
interface Props {
@ -33,7 +33,7 @@ interface Props {
toForegroundShortcut: string;
removeShortcut: string;
changeColor(color: string): void;
changeLabel(labelID: string): void;
changeLabel(label: any): void;
copy(): void;
remove(): void;
propagate(): void;
@ -103,30 +103,8 @@ function ItemTopComponent(props: Props): JSX.Element {
</Text>
</Col>
<Col span={12}>
<Tooltip title={readonly ? 'Current label' : 'Change current label'} mouseLeaveDelay={0}>
<Select
disabled={readonly}
size='small'
value={`${labelID}`}
onChange={changeLabel}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
>
{labels.map(
(label: any): JSX.Element => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
),
)}
</Select>
<Tooltip title='Change current label' mouseLeaveDelay={0}>
<LabelSelector disabled={readonly} size='small' labels={labels} value={labelID} onChange={changeLabel} />
</Tooltip>
</Col>
<Col span={2}>

@ -35,7 +35,7 @@ interface Props {
toBackground(): void;
toForeground(): void;
remove(): void;
changeLabel(labelID: string): void;
changeLabel(label: any): void;
changeAttribute(attrID: number, value: string): void;
changeColor(color: string): void;
collapse(): void;

@ -12,8 +12,8 @@ import Layout, { SiderProps } from 'antd/lib/layout';
import Button from 'antd/lib/button/button';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import Select from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox';
import Tag from 'antd/lib/tag';
import {
createAnnotationsAsync,
@ -23,7 +23,7 @@ import {
} from 'actions/annotation-actions';
import { Canvas } from 'cvat-canvas-wrapper';
import { CombinedState, ObjectType } from 'reducers/interfaces';
import Tag from 'antd/lib/tag';
import LabelSelector from 'components/label-selector/label-selector';
import getCore from 'cvat-core-wrapper';
import ShortcutsSelect from './shortcuts-select';
@ -111,7 +111,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [frameTags, setFrameTags] = useState([] as any[]);
const [selectedLabelID, setSelectedLabelID] = useState(defaultLabelID);
const [selectedLabelID, setSelectedLabelID] = useState<number>(defaultLabelID);
const [skipFrame, setSkipFrame] = useState(false);
useEffect(() => {
@ -155,8 +155,8 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
collapsed: sidebarCollapsed,
};
const onChangeLabel = (value: string): void => {
setSelectedLabelID(Number.parseInt(value, 10));
const onChangeLabel = (value: any): void => {
setSelectedLabelID(value.id);
};
const onRemoveState = (objectState: any): void => {
@ -216,13 +216,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
<Row type='flex' justify='start' className='cvat-tag-annotation-sidebar-label-select'>
<Col>
<Text strong>Tag label</Text>
<Select value={`${selectedLabelID}`} onChange={onChangeLabel} size='default'>
{labels.map((label: any) => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
<LabelSelector labels={labels} value={selectedLabelID} onChange={onChangeLabel} />
</Col>
</Row>
<Row type='flex' justify='space-around' className='cvat-tag-annotation-sidebar-buttons'>

@ -0,0 +1,54 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Select, { OptionProps, SelectProps } from 'antd/lib/select';
interface Props extends SelectProps {
labels: any[];
value: any | number | null;
onChange: (label: any) => void;
}
export default function LabelSelector(props: Props): JSX.Element {
const {
labels, value, onChange, ...rest
} = props;
const dinamicProps = value ?
{
value: typeof value === 'number' ? value : value.id,
} :
{};
return (
<Select
{...rest}
{...dinamicProps}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
defaultValue={labels[0].id}
onChange={(newValue: string) => {
const [label] = labels.filter((_label: any): boolean => _label.id === +newValue);
if (label) {
onChange(label);
} else {
throw new Error(`Label with id ${newValue} was not found within the list`);
}
}}
>
{labels.map((label: any) => (
<Select.Option title={label.name} key={label.id} value={label.id}>
{label.name}
</Select.Option>
))}
</Select>
);
}

@ -102,7 +102,9 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
private onDraw(objectType: ObjectType): void {
const { canvasInstance, shapeType, onDrawStart } = this.props;
const { rectDrawingMethod, cuboidDrawingMethod, numberOfPoints, selectedLabelID } = this.state;
const {
rectDrawingMethod, cuboidDrawingMethod, numberOfPoints, selectedLabelID,
} = this.state;
canvasInstance.cancel();
canvasInstance.draw({
@ -143,14 +145,16 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
});
};
private onChangeLabel = (value: string): void => {
private onChangeLabel = (value: any): void => {
this.setState({
selectedLabelID: +value,
selectedLabelID: value.id,
});
};
public render(): JSX.Element {
const { rectDrawingMethod, cuboidDrawingMethod, selectedLabelID, numberOfPoints } = this.state;
const {
rectDrawingMethod, cuboidDrawingMethod, selectedLabelID, numberOfPoints,
} = this.state;
const { normalizedKeyMap, labels, shapeType } = this.props;
@ -159,7 +163,7 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
labels={labels}
shapeType={shapeType}
minimumPoints={this.minimumPoints}
selectedLabeID={selectedLabelID}
selectedLabelID={selectedLabelID}
numberOfPoints={numberOfPoints}
rectDrawingMethod={rectDrawingMethod}
cuboidDrawingMethod={cuboidDrawingMethod}

@ -74,14 +74,16 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
};
}
private onChangeLabel = (value: string): void => {
private onChangeLabel = (value: any): void => {
this.setState({
selectedLabelID: +value,
selectedLabelID: value.id,
});
};
private onSetup = (): void => {
const { frame, labels, jobInstance, canvasInstance, onAnnotationCreate, onRememberObject } = this.props;
const {
frame, labels, jobInstance, canvasInstance, onAnnotationCreate, onRememberObject,
} = this.props;
const { selectedLabelID } = this.state;
@ -105,7 +107,7 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
return (
<SetupTagPopoverComponent
labels={labels}
selectedLabeID={selectedLabelID}
selectedLabelID={selectedLabelID}
repeatShapeShortcut={normalizedKeyMap.SWITCH_DRAW_MODE}
onChangeLabel={this.onChangeLabel}
onSetup={this.onSetup}

@ -254,15 +254,12 @@ class ObjectItemContainer extends React.PureComponent<Props> {
}
};
private changeLabel = (labelID: string): void => {
const { objectState, readonly, labels } = this.props;
private changeLabel = (label: any): void => {
const { objectState, readonly } = this.props;
if (!readonly) {
const [label] = labels.filter((_label: any): boolean => _label.id === +labelID);
objectState.label = label;
this.commit();
}
this.commit();
};
private changeAttribute = (id: number, value: string): void => {

Loading…
Cancel
Save