提交 821c456e 编写于 作者: K klakhov

fixed more comments

上级 27a18618
......@@ -906,7 +906,7 @@ class CanvasWrapperComponent extends React.PureComponent<Props> {
frame,
groundTruthJobFramesMeta,
workspace,
filterTags: true,
exclude: [ObjectType.TAG],
});
canvasInstance.setup(
......
......@@ -13,7 +13,6 @@ import ObjectSideBarComponent from 'components/annotation-page/standard-workspac
import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list';
import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu';
import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator';
import ConflictAggregatorComponent from 'components/annotation-page/review/conflicts-aggregator';
export default function ReviewWorkspaceComponent(): JSX.Element {
return (
......@@ -23,7 +22,6 @@ export default function ReviewWorkspaceComponent(): JSX.Element {
<ObjectSideBarComponent objectsList={<ObjectsListContainer readonly />} />
<CanvasContextMenuContainer readonly />
<IssueAggregatorComponent />
<ConflictAggregatorComponent />
</Layout>
);
}
// Copyright (C) 2023 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ActiveControl, CombinedState } from 'reducers';
import { Canvas } from 'cvat-canvas/src/typescript/canvas';
import { AnnotationConflict, ObjectState, QualityConflict } from 'cvat-core-wrapper';
import { highlightConflict } from 'actions/annotation-actions';
import ConflictLabel from './conflict-label';
export default function ConflictAggregatorComponent(): JSX.Element | null {
const dispatch = useDispatch();
const qualityConflicts = useSelector((state: CombinedState) => state.review.frameConflicts);
const objectStates = useSelector((state: CombinedState) => state.annotation.annotations.states);
const currentFrame = useSelector((state: CombinedState) => state.annotation.player.frame.number);
const showConflicts = useSelector((state: CombinedState) => state.settings.shapes.showGroundTruth);
const highlightedConflict = useSelector((state: CombinedState) => state.annotation.annotations.highlightedConflict);
const highlightedObjectsIDs = highlightedConflict?.annotationConflicts?.map((c: AnnotationConflict) => c.clientID);
const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance);
const canvasIsReady = useSelector((state: CombinedState): boolean => state.annotation.canvas.ready);
const [geometry, setGeometry] = useState<Canvas['geometry'] | null>(null);
const ready = useSelector((state: CombinedState) => state.annotation.canvas.ready);
const activeControl = useSelector((state: CombinedState) => state.annotation.canvas.activeControl);
const onEnter = useCallback((conflict: QualityConflict) => {
if (ready && activeControl === ActiveControl.CURSOR) {
dispatch(highlightConflict(conflict));
}
}, [ready, activeControl]);
const onLeave = useCallback(() => {
if (ready && activeControl === ActiveControl.CURSOR) {
dispatch(highlightConflict(null));
}
}, [ready, activeControl]);
const conflictLabels: JSX.Element[] = [];
useEffect(() => {
if (canvasInstance instanceof Canvas) {
const { geometry: updatedGeometry } = canvasInstance;
setGeometry(updatedGeometry);
const geometryListener = (): void => {
dispatch(highlightConflict(null));
setGeometry(canvasInstance.geometry);
};
canvasInstance.html().addEventListener('canvas.zoom', geometryListener);
canvasInstance.html().addEventListener('canvas.fit', geometryListener);
canvasInstance.html().addEventListener('canvas.reshape', geometryListener);
return () => {
canvasInstance.html().removeEventListener('canvas.zoom', geometryListener);
canvasInstance.html().removeEventListener('canvas.fit', geometryListener);
canvasInstance.html().addEventListener('canvas.reshape', geometryListener);
};
}
return () => {};
}, [canvasInstance, ready]);
const [mapping, setMapping] = useState<any>([]);
useEffect(() => {
if (canvasInstance instanceof Canvas && geometry && showConflicts) {
const newMapping = qualityConflicts.map((conflict: QualityConflict) => {
const c = conflict.annotationConflicts[0];
const state = objectStates.find((s: ObjectState) => s.jobID === c.jobID && s.serverID === c.serverID);
if (state && canvasInstance) {
const points = canvasInstance.setupConflictsRegions(state);
if (points) {
return {
description: conflict.description,
severity: conflict.severity,
x: points[0],
y: points[1],
clientID: c.clientID,
conflict,
};
}
}
return [];
}).filter((el) => !Array.isArray(el));
setMapping(newMapping);
} else {
setMapping([]);
}
}, [geometry, objectStates, showConflicts, currentFrame, canvasInstance, ready]);
if (!(canvasInstance instanceof Canvas) || !canvasIsReady || !geometry) {
return null;
}
for (const conflict of mapping) {
conflictLabels.push(
<ConflictLabel
key={(Math.random() + 1).toString(36).substring(7)}
text={conflict.description}
top={conflict.y}
left={conflict.x}
angle={-geometry.angle}
scale={1 / geometry.scale}
severity={conflict.severity}
darken={!!highlightedConflict && !!highlightedObjectsIDs &&
(!highlightedObjectsIDs.includes(conflict.clientID))}
conflict={conflict.conflict}
onEnter={onEnter}
onLeave={onLeave}
tooltipVisible={!!highlightedConflict && !!highlightedObjectsIDs &&
highlightedObjectsIDs.includes(conflict.clientID)}
/>,
);
}
return (
<>
{conflictLabels}
</>
);
}
......@@ -4,17 +4,31 @@
// SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { CombinedState } from 'reducers';
import { ActiveControl, CombinedState } from 'reducers';
import { Canvas } from 'cvat-canvas/src/typescript/canvas';
import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions';
import {
AnnotationConflict, ConflictSeverity, ObjectState, QualityConflict,
} from 'cvat-core-wrapper';
import { highlightConflict } from 'actions/annotation-actions';
import CreateIssueDialog from './create-issue-dialog';
import HiddenIssueLabel from './hidden-issue-label';
import IssueDialog from './issue-dialog';
import ConflictLabel from './conflict-label';
interface ConflictMappingElement {
description: string;
severity: ConflictSeverity;
x: number;
y: number;
clientID: number;
conflict: QualityConflict;
}
export default function IssueAggregatorComponent(): JSX.Element | null {
const dispatch = useDispatch();
......@@ -26,14 +40,38 @@ export default function IssueAggregatorComponent(): JSX.Element | null {
const canvasIsReady = useSelector((state: CombinedState): boolean => state.annotation.canvas.ready);
const newIssuePosition = useSelector((state: CombinedState): number[] | null => state.review.newIssuePosition);
const issueFetching = useSelector((state: CombinedState): number | null => state.review.fetching.issueId);
const ready = useSelector((state: CombinedState) => state.annotation.canvas.ready);
const [geometry, setGeometry] = useState<Canvas['geometry'] | null>(null);
const qualityConflicts = useSelector((state: CombinedState) => state.review.frameConflicts);
const objectStates = useSelector((state: CombinedState) => state.annotation.annotations.states);
const showConflicts = useSelector((state: CombinedState) => state.settings.shapes.showGroundTruth);
const highlightedConflict = useSelector((state: CombinedState) => state.annotation.annotations.highlightedConflict);
const highlightedObjectsIDs = highlightedConflict?.annotationConflicts?.map((c: AnnotationConflict) => c.clientID);
const activeControl = useSelector((state: CombinedState) => state.annotation.canvas.activeControl);
const readyToRender = canvasInstance instanceof Canvas && canvasIsReady;
const onEnter = useCallback((conflict: QualityConflict) => {
if (readyToRender && activeControl === ActiveControl.CURSOR) {
dispatch(highlightConflict(conflict));
}
}, [readyToRender, activeControl]);
const onLeave = useCallback(() => {
if (readyToRender && activeControl === ActiveControl.CURSOR) {
dispatch(highlightConflict(null));
}
}, [readyToRender, activeControl]);
const [conflictMapping, setConflictMapping] = useState<ConflictMappingElement[]>([]);
const issueLabels: JSX.Element[] = [];
const issueDialogs: JSX.Element[] = [];
const conflictLabels: JSX.Element[] = [];
useEffect(() => {
if (canvasInstance instanceof Canvas) {
if (readyToRender) {
const { geometry: updatedGeometry } = canvasInstance;
setGeometry(updatedGeometry);
......@@ -53,10 +91,10 @@ export default function IssueAggregatorComponent(): JSX.Element | null {
}
return () => {};
}, [canvasInstance, ready]);
}, [readyToRender]);
useEffect(() => {
if (canvasInstance instanceof Canvas) {
if (readyToRender) {
type IssueRegionSet = Record<number, { hidden: boolean; points: number[] }>;
const regions = !issuesHidden ? frameIssues
.filter((_issue: any) => !issuesResolvedHidden || !_issue.resolved)
......@@ -86,9 +124,35 @@ export default function IssueAggregatorComponent(): JSX.Element | null {
}
}
}
}, [newIssuePosition, frameIssues, issuesResolvedHidden, issuesHidden, canvasInstance]);
}, [newIssuePosition, frameIssues, issuesResolvedHidden, issuesHidden, readyToRender, showConflicts]);
useEffect(() => {
if (readyToRender && showConflicts) {
const newconflictMapping = qualityConflicts.map((conflict: QualityConflict) => {
const c = conflict.annotationConflicts[0];
const state = objectStates.find((s: ObjectState) => s.jobID === c.jobID && s.serverID === c.serverID);
if (state) {
const points = canvasInstance.setupConflictsRegions(state);
if (points) {
return {
description: conflict.description,
severity: conflict.severity,
x: points[0],
y: points[1],
clientID: c.clientID,
conflict,
};
}
}
return null;
}).filter((element) => element);
setConflictMapping(newconflictMapping as ConflictMappingElement[]);
} else {
setConflictMapping([]);
}
}, [geometry, objectStates, showConflicts, readyToRender]);
if (!(canvasInstance instanceof Canvas) || !canvasIsReady || !geometry) {
if (!readyToRender || !geometry) {
return null;
}
......@@ -173,6 +237,27 @@ export default function IssueAggregatorComponent(): JSX.Element | null {
Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 !== 0)) :
null;
for (const conflict of conflictMapping) {
conflictLabels.push(
<ConflictLabel
key={(Math.random() + 1).toString(36).substring(7)}
text={conflict.description}
top={conflict.y}
left={conflict.x}
angle={-geometry.angle}
scale={1 / geometry.scale}
severity={conflict.severity}
darken={!!highlightedConflict && !!highlightedObjectsIDs &&
(!highlightedObjectsIDs.includes(conflict.clientID))}
conflict={conflict.conflict}
onEnter={onEnter}
onLeave={onLeave}
tooltipVisible={!!highlightedConflict && !!highlightedObjectsIDs &&
highlightedObjectsIDs.includes(conflict.clientID)}
/>,
);
}
return (
<>
{createLeft !== null && createTop !== null ? (
......@@ -185,6 +270,7 @@ export default function IssueAggregatorComponent(): JSX.Element | null {
) : null}
{issueDialogs}
{issueLabels}
{conflictLabels}
</>
);
}
......@@ -11,12 +11,14 @@ import { ThunkDispatch } from 'redux-thunk';
import {
removeObject as removeObjectAction,
} from 'actions/annotation-actions';
import { CombinedState, ObjectType } from 'reducers';
import { CombinedState, ObjectType, Workspace } from 'reducers';
import { ObjectState } from 'cvat-core-wrapper';
import { filterAnnotations } from 'utils/filter-annotations';
interface StateToProps {
states: ObjectState[];
statesSources: number[];
workspace: Workspace;
}
interface DispatchToProps {
......@@ -27,12 +29,14 @@ function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
annotations: { states, statesSources },
workspace,
},
} = state;
return {
states,
statesSources,
workspace,
};
}
......@@ -48,6 +52,7 @@ function FrameTags(props: StateToProps & DispatchToProps): JSX.Element {
const {
states,
statesSources,
workspace,
removeObject,
} = props;
......@@ -59,12 +64,11 @@ function FrameTags(props: StateToProps & DispatchToProps): JSX.Element {
useEffect(() => {
setFrameTags(
states.filter(
(objectState: ObjectState): boolean => (
objectState.objectType === ObjectType.TAG &&
(!objectState.jobID || statesSources.includes(objectState.jobID))
),
),
filterAnnotations(states, {
statesSources,
workspace,
include: [ObjectType.TAG],
}),
);
}, [states, statesSources]);
......
......@@ -9,16 +9,26 @@ export interface FilterAnnotationsParams {
workspace: Workspace;
statesSources: number[];
groundTruthJobFramesMeta?: FramesMetaData | null;
filterTags?: boolean;
frame: number;
exclude?: ObjectType[];
include?: ObjectType[];
frame?: number;
}
export function filterAnnotations(annotations: ObjectState[], params: FilterAnnotationsParams): ObjectState[] {
const {
workspace, statesSources, groundTruthJobFramesMeta, filterTags, frame,
workspace, statesSources, groundTruthJobFramesMeta, exclude, include, frame,
} = params;
if (Array.isArray(exclude) && Array.isArray(include)) {
throw Error('Can not filter annotations with exclude and include filters simultaneously');
}
const filteredAnnotations = annotations.filter((state) => {
if (filterTags && state.objectType === ObjectType.TAG) {
if (Array.isArray(exclude) && exclude.includes(state.objectType)) {
return false;
}
if (Array.isArray(include) && !include.includes(state.objectType)) {
return false;
}
......@@ -27,7 +37,7 @@ export function filterAnnotations(annotations: ObjectState[], params: FilterAnno
}
// GT tracks are shown only on GT frames
if (workspace === Workspace.REVIEW_WORKSPACE && groundTruthJobFramesMeta) {
if (workspace === Workspace.REVIEW_WORKSPACE && groundTruthJobFramesMeta && frame) {
if (state.objectType === ObjectType.TRACK && state.isGroundTruth) {
return groundTruthJobFramesMeta.includedFrames.includes(frame);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册