Merge pull request #1349 from opencv/bs/show_text_always

React UI: Added option to display shape text always
main
Dmitry Kalinin 6 years ago committed by GitHub
commit 16afc698a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dialog window with some helpful information about using filters
- Ability to display a bitmap in the new UI
- Button to reset colors settings (brightness, saturation, contrast) in the new UI
- Added option to display shape text always
### Changed
-

@ -50,6 +50,11 @@ Canvas itself handles:
ZOOM_CANVAS = 'zoom_canvas',
}
interface Configuration {
displayAllText?: boolean;
undefinedAttrValue?: string;
}
interface DrawData {
enabled: boolean;
shapeType?: string;
@ -83,7 +88,6 @@ Canvas itself handles:
}
interface Canvas {
mode(): Mode;
html(): HTMLDivElement;
setZLayer(zLayer: number | null): void;
setup(frameData: any, objectStates: any[]): void;
@ -104,7 +108,9 @@ Canvas itself handles:
dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void;
mode(): Mode;
cancel(): void;
configure(configuration: Configuration): void;
}
```
@ -190,5 +196,6 @@ Standard JS events are used.
| dragCanvas() | + | - | - | - | - | - | + | - |
| zoomCanvas() | + | - | - | - | - | - | - | + |
| cancel() | - | + | + | + | + | + | + | + |
| configure() | + | - | - | - | - | - | - | - |
| bitmap() | + | + | + | + | + | + | + | + |
| setZLayer() | + | + | + | + | + | + | + | + |

@ -11,6 +11,7 @@ import {
CanvasModel,
CanvasModelImpl,
RectDrawingMethod,
Configuration,
} from './canvasModel';
import {
@ -53,8 +54,9 @@ interface Canvas {
dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void;
mode(): void;
mode(): Mode;
cancel(): void;
configure(configuration: Configuration): void;
}
class CanvasImpl implements Canvas {
@ -146,11 +148,16 @@ class CanvasImpl implements Canvas {
public cancel(): void {
this.model.cancel();
}
public configure(configuration: Configuration): void {
this.model.configure(configuration);
}
}
export {
CanvasImpl as Canvas,
CanvasVersion,
Configuration,
RectDrawingMethod,
Mode as CanvasMode,
};

@ -36,7 +36,6 @@ export interface CanvasController {
enableDrag(x: number, y: number): void;
drag(x: number, y: number): void;
disableDrag(): void;
fit(): void;
}

@ -46,6 +46,11 @@ export enum RectDrawingMethod {
EXTREME_POINTS = 'By 4 points'
}
export interface Configuration {
displayAllText?: boolean;
undefinedAttrValue?: string;
}
export interface DrawData {
enabled: boolean;
shapeType?: string;
@ -101,6 +106,7 @@ export enum UpdateReasons {
BITMAP = 'bitmap',
DRAG_CANVAS = 'drag_canvas',
ZOOM_CANVAS = 'zoom_canvas',
CONFIG_UPDATED = 'config_updated',
}
export enum Mode {
@ -128,6 +134,7 @@ export interface CanvasModel {
readonly mergeData: MergeData;
readonly splitData: SplitData;
readonly groupData: GroupData;
readonly configuration: Configuration;
readonly selected: any;
geometry: Geometry;
mode: Mode;
@ -154,6 +161,7 @@ export interface CanvasModel {
dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void;
configure(configuration: Configuration): void;
cancel(): void;
}
@ -162,6 +170,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
activeElement: ActiveElement;
angle: number;
canvasSize: Size;
configuration: Configuration;
imageBitmap: boolean;
image: Image | null;
imageID: number | null;
@ -195,6 +204,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
height: 0,
width: 0,
},
configuration: {
displayAllText: false,
undefinedAttrValue: '',
},
imageBitmap: false,
image: null,
imageID: null,
@ -495,10 +508,30 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.selected = null;
}
public configure(configuration: Configuration): void {
if (this.data.mode !== Mode.IDLE) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (typeof (configuration.displayAllText) !== 'undefined') {
this.data.configuration.displayAllText = configuration.displayAllText;
}
if (typeof (configuration.undefinedAttrValue) !== 'undefined') {
this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue;
}
this.notify(UpdateReasons.CONFIG_UPDATED);
}
public cancel(): void {
this.notify(UpdateReasons.CANCEL);
}
public get configuration(): Configuration {
return { ...this.data.configuration };
}
public get geometry(): Geometry {
return {
angle: this.data.angle,

@ -36,6 +36,7 @@ import {
GroupData,
Mode,
Size,
Configuration,
} from './canvasModel';
export interface CanvasView {
@ -66,6 +67,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private groupHandler: GroupHandler;
private zoomHandler: ZoomHandler;
private activeElement: ActiveElement;
private configuration: Configuration;
private set mode(value: Mode) {
this.controller.mode = value;
@ -539,6 +541,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
clientID: null,
attributeID: null,
};
this.configuration = model.configuration;
this.mode = Mode.IDLE;
// Create HTML elements
@ -707,8 +710,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
public notify(model: CanvasModel & Master, reason: UpdateReasons): void {
this.geometry = this.controller.geometry;
if (reason === UpdateReasons.BITMAP) {
if (reason === UpdateReasons.CONFIG_UPDATED) {
this.configuration = model.configuration;
this.setupObjects([]);
this.setupObjects(model.objects);
} else if (reason === UpdateReasons.BITMAP) {
const { imageBitmap } = model;
if (imageBitmap) {
this.bitmap.style.display = '';
@ -961,31 +967,44 @@ export class CanvasViewImpl implements CanvasView, Listener {
for (const state of states) {
const { clientID } = state;
const drawnState = this.drawnStates[clientID];
const shape = this.svgShapes[state.clientID];
const text = this.svgTexts[state.clientID];
if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) {
const none = state.hidden || state.outside;
if (state.shapeType === 'points') {
this.svgShapes[clientID].remember('_selectHandler').nested
.style('display', none ? 'none' : '');
const isInvisible = state.hidden || state.outside;
if (isInvisible) {
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.style('display', 'none');
if (text) {
text.addClass('cvat_canvas_hidden');
}
} else {
this.svgShapes[clientID].style('display', none ? 'none' : '');
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.style('display', '');
if (text) {
text.removeClass('cvat_canvas_hidden');
this.updateTextPosition(
text,
shape,
);
}
}
}
if (drawnState.zOrder !== state.zOrder) {
if (state.shapeType === 'points') {
this.svgShapes[clientID].remember('_selectHandler').nested
shape.remember('_selectHandler').nested
.attr('data-z-order', state.zOrder);
} else {
this.svgShapes[clientID].attr('data-z-order', state.zOrder);
shape.attr('data-z-order', state.zOrder);
}
}
if (drawnState.occluded !== state.occluded) {
if (state.occluded) {
this.svgShapes[clientID].addClass('cvat_canvas_shape_occluded');
shape.addClass('cvat_canvas_shape_occluded');
} else {
this.svgShapes[clientID].removeClass('cvat_canvas_shape_occluded');
shape.removeClass('cvat_canvas_shape_occluded');
}
}
@ -1003,7 +1022,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (state.shapeType === 'rectangle') {
const [xtl, ytl, xbr, ybr] = translatedPoints;
this.svgShapes[clientID].attr({
shape.attr({
x: xtl,
y: ytl,
width: xbr - xtl,
@ -1019,21 +1038,20 @@ export class CanvasViewImpl implements CanvasView, Listener {
return `${acc}${val},`;
}, '',
);
(this.svgShapes[clientID] as any).clear();
this.svgShapes[clientID].attr('points', stringified);
(shape as any).clear();
shape.attr('points', stringified);
if (state.shapeType === 'points') {
this.selectize(false, this.svgShapes[clientID]);
this.setupPoints(this.svgShapes[clientID] as SVG.PolyLine, state);
this.selectize(false, shape);
this.setupPoints(shape as SVG.PolyLine, state);
}
}
}
for (const attrID of Object.keys(state.attributes)) {
if (state.attributes[attrID] !== drawnState.attributes[attrID]) {
const text = this.svgTexts[state.clientID];
if (text) {
const [span] = this.svgTexts[state.clientID].node
const [span] = text.node
.querySelectorAll(`[attrID="${attrID}"]`) as any as SVGTSpanElement[];
if (span && span.textContent) {
const prefix = span.textContent.split(':').slice(0, -1).join(':');
@ -1048,6 +1066,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
private addObjects(states: any[], translate: (points: number[]) => number[]): void {
const { displayAllText } = this.configuration;
for (const state of states) {
if (state.objectType === 'tag') {
this.addTag(state);
@ -1091,6 +1111,14 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
}));
});
if (displayAllText) {
this.svgTexts[state.clientID] = this.addText(state);
this.updateTextPosition(
this.svgTexts[state.clientID],
this.svgShapes[state.clientID],
);
}
}
this.saveState(state);
@ -1139,6 +1167,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private deactivateShape(): void {
if (this.activeElement.clientID !== null) {
const { displayAllText } = this.configuration;
const { clientID } = this.activeElement;
const drawnState = this.drawnStates[clientID];
const shape = this.svgShapes[clientID];
@ -1162,7 +1191,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
// TODO: Hide text only if it is hidden by settings
const text = this.svgTexts[clientID];
if (text) {
if (text && !displayAllText) {
text.remove();
delete this.svgTexts[clientID];
}
@ -1370,6 +1399,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Update text position after corresponding box has been moved, resized, etc.
private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void {
if (text.node.style.display === 'none') return; // wrong transformation matrix
let box = (shape.node as any).getBBox();
// Translate the whole box to the client coordinate system
@ -1408,6 +1438,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration;
const { label, clientID, attributes } = state;
const attrNames = label.attributes.reduce((acc: any, val: any): void => {
acc[val.id] = val.name;
@ -1417,7 +1448,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
return this.adoptedText.text((block): void => {
block.tspan(`${label.name} ${clientID}`).style('text-transform', 'uppercase');
for (const attrID of Object.keys(attributes)) {
const value = attributes[attrID] === consts.UNDEFINED_ATTRIBUTE_VALUE
const value = attributes[attrID] === undefinedAttrValue
? '' : attributes[attrID];
block.tspan(`${attrNames[attrID]}: ${value}`).attr({
attrID,

@ -29,6 +29,7 @@ export enum SettingsActionTypes {
CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL',
CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN',
SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS',
SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS',
}
export function changeShapesOpacity(opacity: number): AnyAction {
@ -210,3 +211,12 @@ export function switchShowingInterpolatedTracks(showAllInterpolationTracks: bool
},
};
}
export function switchShowingObjectsTextAlways(showObjectsTextAlways: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS,
payload: {
showObjectsTextAlways,
},
};
}

@ -21,6 +21,7 @@ import {
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas';
import getCore from 'cvat-core';
import consts from 'consts';
const cvat = getCore();
@ -59,6 +60,7 @@ interface Props {
contextVisible: boolean;
contextType: ContextMenuType;
aamZoomMargin: number;
showObjectsTextAlways: boolean;
workspace: Workspace;
keyMap: Record<string, ExtendedKeyMapOptions>;
onSetupCanvas: () => void;
@ -92,6 +94,7 @@ interface Props {
export default class CanvasWrapperComponent extends React.PureComponent<Props> {
public componentDidMount(): void {
const {
showObjectsTextAlways,
canvasInstance,
curZLayer,
} = this.props;
@ -102,7 +105,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
.getElementsByClassName('cvat-canvas-container');
wrapper.appendChild(canvasInstance.html());
canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways,
});
canvasInstance.setZLayer(curZLayer);
this.initialSetup();
this.updateCanvas();
}
@ -130,8 +138,16 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
saturationLevel,
workspace,
frameFetching,
showObjectsTextAlways,
} = this.props;
if (prevProps.showObjectsTextAlways !== showObjectsTextAlways) {
canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways,
});
}
if (prevProps.sidebarCollapsed !== sidebarCollapsed) {
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
if (sidebar) {

@ -23,6 +23,8 @@
.cvat-player-settings-grid,
.cvat-workspace-settings-auto-save,
.cvat-workspace-settings-show-text-always,
.cvat-workspace-settings-show-text-always-checkbox,
.cvat-workspace-settings-show-interpolated-checkbox {
margin-bottom: 10px;
}
@ -34,6 +36,7 @@
.cvat-player-settings-speed,
.cvat-player-settings-reset-zoom,
.cvat-player-settings-rotate-all,
.cvat-workspace-settings-show-text-always,
.cvat-workspace-settings-show-interpolated,
.cvat-workspace-settings-aam-zoom-margin,
.cvat-workspace-settings-auto-save-interval {

@ -16,10 +16,12 @@ interface Props {
autoSaveInterval: number;
aamZoomMargin: number;
showAllInterpolationTracks: boolean;
showObjectsTextAlways: boolean;
onSwitchAutoSave(enabled: boolean): void;
onChangeAutoSaveInterval(interval: number): void;
onChangeAAMZoomMargin(margin: number): void;
onSwitchShowingInterpolatedTracks(enabled: boolean): void;
onSwitchShowingObjectsTextAlways(enabled: boolean): void;
}
export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
@ -28,10 +30,12 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
autoSaveInterval,
aamZoomMargin,
showAllInterpolationTracks,
showObjectsTextAlways,
onSwitchAutoSave,
onChangeAutoSaveInterval,
onChangeAAMZoomMargin,
onSwitchShowingInterpolatedTracks,
onSwitchShowingObjectsTextAlways,
} = props;
const minAutoSaveInterval = 5;
@ -93,6 +97,22 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
<Text type='secondary'> Show hidden interpolated objects in the side panel </Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-show-text-always'>
<Col className='cvat-workspace-settings-show-text-always-checkbox'>
<Checkbox
className='cvat-text-color'
checked={showObjectsTextAlways}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchShowingObjectsTextAlways(event.target.checked);
}}
>
Always show object details
</Checkbox>
</Col>
<Col>
<Text type='secondary'> Show text for an object on the canvas not only when the object is activated </Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-aam-zoom-margin'>
<Col>
<Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text>

@ -74,6 +74,7 @@ interface StateToProps {
saturationLevel: number;
resetZoom: boolean;
aamZoomMargin: number;
showObjectsTextAlways: boolean;
workspace: Workspace;
minZLayer: number;
maxZLayer: number;
@ -164,6 +165,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
},
workspace: {
aamZoomMargin,
showObjectsTextAlways,
},
shapes: {
opacity,
@ -206,6 +208,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
saturationLevel,
resetZoom,
aamZoomMargin,
showObjectsTextAlways,
curZLayer,
minZLayer,
maxZLayer,

@ -10,11 +10,10 @@ import {
changeAutoSaveInterval,
changeAAMZoomMargin,
switchShowingInterpolatedTracks,
switchShowingObjectsTextAlways,
} from 'actions/settings-actions';
import {
CombinedState,
} from 'reducers/interfaces';
import { CombinedState } from 'reducers/interfaces';
import WorkspaceSettingsComponent from 'components/settings-page/workspace-settings';
@ -23,6 +22,7 @@ interface StateToProps {
autoSaveInterval: number;
aamZoomMargin: number;
showAllInterpolationTracks: boolean;
showObjectsTextAlways: boolean;
}
interface DispatchToProps {
@ -30,6 +30,7 @@ interface DispatchToProps {
onChangeAutoSaveInterval(interval: number): void;
onChangeAAMZoomMargin(margin: number): void;
onSwitchShowingInterpolatedTracks(enabled: boolean): void;
onSwitchShowingObjectsTextAlways(enabled: boolean): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
@ -39,6 +40,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
autoSaveInterval,
aamZoomMargin,
showAllInterpolationTracks,
showObjectsTextAlways,
} = workspace;
return {
@ -46,6 +48,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
autoSaveInterval,
aamZoomMargin,
showAllInterpolationTracks,
showObjectsTextAlways,
};
}
@ -63,6 +66,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
onSwitchShowingInterpolatedTracks(enabled: boolean): void {
dispatch(switchShowingInterpolatedTracks(enabled));
},
onSwitchShowingObjectsTextAlways(enabled: boolean): void {
dispatch(switchShowingObjectsTextAlways(enabled));
},
};
}

@ -426,6 +426,7 @@ export interface WorkspaceSettingsState {
autoSave: boolean;
autoSaveInterval: number; // in ms
aamZoomMargin: number;
showObjectsTextAlways: boolean;
showAllInterpolationTracks: boolean;
}

@ -28,6 +28,7 @@ const defaultState: SettingsState = {
autoSave: false,
autoSaveInterval: 15 * 60 * 1000,
aamZoomMargin: 100,
showObjectsTextAlways: false,
showAllInterpolationTracks: false,
},
player: {
@ -227,6 +228,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
},
};
}
case SettingsActionTypes.SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS: {
return {
...state,
workspace: {
...state.workspace,
showObjectsTextAlways: action.payload.showObjectsTextAlways,
},
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AnnotationActionTypes.GET_JOB_SUCCESS: {
const { job } = action.payload;

Loading…
Cancel
Save