@@ -71,6 +80,8 @@ export default function QualitySettingsForm(props: Readonly
): JSX.Element
<>
{makeTooltipFragment('Target metric', targetMetricDescription)}
{makeTooltipFragment('Target metric threshold', settings.descriptions.targetMetricThreshold)}
+ {makeTooltipFragment('Compare attributes', settings.descriptions.compareAttributes)}
+ {makeTooltipFragment('Match empty frames', settings.descriptions.matchEmptyFrames)}
>,
);
@@ -89,6 +100,10 @@ export default function QualitySettingsForm(props: Readonly): JSX.Element
makeTooltipFragment('Object Keypoint Similarity (OKS)', settings.descriptions.oksSigma),
);
+ const pointTooltip = makeTooltip(
+ makeTooltipFragment('Point size base', pointSizeBaseDescription),
+ );
+
const linesTooltip = makeTooltip(
<>
{makeTooltipFragment('Line thickness', settings.descriptions.lineThickness)}
@@ -169,6 +184,30 @@ export default function QualitySettingsForm(props: Readonly): JSX.Element
+
+
+
+
+ Compare attributes
+
+
+
+
+
+
+ Match empty frames
+
+
+
+
@@ -249,6 +288,38 @@ export default function QualitySettingsForm(props: Readonly): JSX.Element
+
+
+ Point Comparison
+
+
+
+
+
+
+
+
+
+
+
+
+
Line Comparison
diff --git a/cvat-ui/src/components/requests-page/request-card.tsx b/cvat-ui/src/components/requests-page/request-card.tsx
index 52c109e3822c..dd2a8886941a 100644
--- a/cvat-ui/src/components/requests-page/request-card.tsx
+++ b/cvat-ui/src/components/requests-page/request-card.tsx
@@ -149,7 +149,7 @@ function RequestCard(props: Props): JSX.Element {
const dispatch = useDispatch();
const linkToEntity = constructLink(request);
- const percent = request.status === RQStatus.FINISHED ? 100 : request.progress;
+ const percent = request.status === RQStatus.FINISHED ? 100 : (request.progress ?? 0) * 100;
const timestamps = constructTimestamps(request);
const name = constructName(operation);
diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx
index 1d92a5e4eb59..a371a113c3d6 100644
--- a/cvat-ui/src/components/task-page/task-page.tsx
+++ b/cvat-ui/src/components/task-page/task-page.tsx
@@ -9,12 +9,12 @@ import { useHistory, useParams } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
import { Row, Col } from 'antd/lib/grid';
import Spin from 'antd/lib/spin';
-import Result from 'antd/lib/result';
import notification from 'antd/lib/notification';
import { getInferenceStatusAsync } from 'actions/models-actions';
import { updateJobAsync } from 'actions/jobs-actions';
import { getCore, Task, Job } from 'cvat-core-wrapper';
+import { TaskNotFoundComponent } from 'components/common/not-found';
import JobListComponent from 'components/task-page/job-list';
import ModelRunnerModal from 'components/model-runner-modal/model-runner-dialog';
import CVATLoadingSpinner from 'components/common/loading-spinner';
@@ -78,14 +78,7 @@ function TaskPageComponent(): JSX.Element {
}
if (!taskInstance) {
- return (
-
- );
+ return ;
}
const onUpdateTask = (task: Task): Promise => (
diff --git a/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx b/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx
index 52afa4c97377..ef1631545b48 100644
--- a/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx
+++ b/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx
@@ -8,12 +8,12 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
import Spin from 'antd/lib/spin';
-import Result from 'antd/lib/result';
import Text from 'antd/lib/typography/Text';
import { CombinedState } from 'reducers';
import { getCloudStoragesAsync } from 'actions/cloud-storage-actions';
import CreateCloudStorageForm from 'components/create-cloud-storage-page/cloud-storage-form';
+import { CloudStorageNotFoundComponent } from 'components/common/not-found';
interface ParamType {
id: string;
@@ -45,14 +45,7 @@ export default function UpdateCloudStoragePageComponent(): JSX.Element {
}
if (!cloudStorage) {
- return (
-
- );
+ return ;
}
return (
diff --git a/cvat-ui/src/config.tsx b/cvat-ui/src/config.tsx
index 7e0b404b093a..05732d9e83ce 100644
--- a/cvat-ui/src/config.tsx
+++ b/cvat-ui/src/config.tsx
@@ -108,6 +108,7 @@ const DEFAULT_GOOGLE_CLOUD_STORAGE_LOCATIONS: string[][] = [
['NAM4', 'US-CENTRAL1 and US-EAST1'],
];
+const MAXIMUM_NOTIFICATION_MESSAGE_LENGTH = 600; // all above will be sent to console
const HEALTH_CHECK_RETRIES = 10;
const HEALTH_CHECK_PERIOD = 3000; // ms
const HEALTH_CHECK_REQUEST_TIMEOUT = 15000; // ms
@@ -190,4 +191,5 @@ export default {
REQUEST_SUCCESS_NOTIFICATION_DURATION,
BLACKLISTED_GO_BACK_PATHS,
PAID_PLACEHOLDER_CONFIG,
+ MAXIMUM_NOTIFICATION_MESSAGE_LENGTH,
};
diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx
index 61f6c1064c61..9e2748047294 100644
--- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx
+++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { ObjectState, Job } from 'cvat-core-wrapper';
import isAbleToChangeFrame from 'utils/is-able-to-change-frame';
import { ThunkDispatch } from 'utils/redux';
-import { updateAnnotationsAsync, changeFrameAsync } from 'actions/annotation-actions';
+import { updateAnnotationsAsync, changeFrameAsync, changeHideActiveObjectAsync } from 'actions/annotation-actions';
import { CombinedState } from 'reducers';
import ItemButtonsComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons';
@@ -29,11 +29,13 @@ interface StateToProps {
outsideDisabled: boolean;
hiddenDisabled: boolean;
keyframeDisabled: boolean;
+ editedState: ObjectState | null,
}
interface DispatchToProps {
updateAnnotations(statesToUpdate: any[]): void;
changeFrame(frame: number): void;
+ changeHideEditedState(value: boolean): void;
}
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
@@ -44,6 +46,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
player: {
frame: { number: frameNumber },
},
+ editing: { objectState: editedState },
},
shortcuts: { normalizedKeyMap },
} = state;
@@ -61,6 +64,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
objectState,
normalizedKeyMap,
frameNumber,
+ editedState,
jobInstance: jobInstance as Job,
outsideDisabled: typeof outsideDisabled === 'undefined' ? false : outsideDisabled,
hiddenDisabled: typeof hiddenDisabled === 'undefined' ? false : hiddenDisabled,
@@ -76,6 +80,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
changeFrame(frame: number): void {
dispatch(changeFrameAsync(frame));
},
+ changeHideEditedState(value: boolean): void {
+ dispatch(changeHideActiveObjectAsync(value));
+ },
};
}
@@ -145,15 +152,23 @@ class ItemButtonsWrapper extends React.PureComponent {
- const { objectState } = this.props;
- objectState.hidden = false;
- this.commit();
+ const { objectState, editedState, changeHideEditedState } = this.props;
+ if (objectState.clientID === editedState?.clientID) {
+ changeHideEditedState(false);
+ } else {
+ objectState.hidden = false;
+ this.commit();
+ }
};
private hide = (): void => {
- const { objectState } = this.props;
- objectState.hidden = true;
- this.commit();
+ const { objectState, editedState, changeHideEditedState } = this.props;
+ if (objectState.clientID === editedState?.clientID) {
+ changeHideEditedState(true);
+ } else {
+ objectState.hidden = true;
+ this.commit();
+ }
};
private setOccluded = (): void => {
diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx
index 7f120cc981d6..16ccdc08bff7 100644
--- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx
+++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx
@@ -19,6 +19,7 @@ import {
switchPropagateVisibility as switchPropagateVisibilityAction,
removeObject as removeObjectAction,
fetchAnnotationsAsync,
+ changeHideActiveObjectAsync,
} from 'actions/annotation-actions';
import {
changeShowGroundTruth as changeShowGroundTruthAction,
@@ -26,6 +27,7 @@ import {
import isAbleToChangeFrame from 'utils/is-able-to-change-frame';
import {
CombinedState, StatesOrdering, ObjectType, ColorBy, Workspace,
+ ActiveControl,
} from 'reducers';
import { ObjectState, ShapeType } from 'cvat-core-wrapper';
import { filterAnnotations } from 'utils/filter-annotations';
@@ -56,6 +58,9 @@ interface StateToProps {
normalizedKeyMap: Record;
showGroundTruth: boolean;
workspace: Workspace;
+ editedState: ObjectState | null,
+ activeControl: ActiveControl,
+ activeObjectHidden: boolean,
}
interface DispatchToProps {
@@ -67,6 +72,7 @@ interface DispatchToProps {
changeFrame(frame: number): void;
changeGroupColor(group: number, color: string): void;
changeShowGroundTruth(value: boolean): void;
+ changeHideEditedState(value: boolean): void;
}
const componentShortcuts = {
@@ -186,6 +192,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
player: {
frame: { number: frameNumber },
},
+ canvas: {
+ activeControl, activeObjectHidden,
+ },
+ editing: { objectState: editedState },
colors,
workspace,
},
@@ -233,6 +243,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
normalizedKeyMap,
showGroundTruth,
workspace,
+ editedState,
+ activeControl,
+ activeObjectHidden,
};
}
@@ -263,6 +276,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
dispatch(changeShowGroundTruthAction(value));
dispatch(fetchAnnotationsAsync());
},
+ changeHideEditedState(value: boolean): void {
+ dispatch(changeHideActiveObjectAsync(value));
+ },
};
}
@@ -388,9 +404,13 @@ class ObjectsListContainer extends React.PureComponent {
}
private hideAllStates(hidden: boolean): void {
- const { updateAnnotations } = this.props;
+ const { updateAnnotations, editedState, changeHideEditedState } = this.props;
const { filteredStates } = this.state;
+ if (editedState?.shapeType === ShapeType.MASK) {
+ changeHideEditedState(hidden);
+ }
+
for (const objectState of filteredStates) {
objectState.hidden = hidden;
}
@@ -475,6 +495,13 @@ class ObjectsListContainer extends React.PureComponent {
SWITCH_HIDDEN: (event: KeyboardEvent | undefined) => {
preventDefault(event);
const state = activatedState();
+ const {
+ editedState, changeHideEditedState, activeControl, activeObjectHidden,
+ } = this.props;
+ if (editedState?.shapeType === ShapeType.MASK || activeControl === ActiveControl.DRAW_MASK) {
+ const hide = editedState ? !editedState.hidden : !activeObjectHidden;
+ changeHideEditedState(hide);
+ }
if (state) {
state.hidden = !state.hidden;
updateAnnotations([state]);
diff --git a/cvat-ui/src/containers/tasks-page/task-item.tsx b/cvat-ui/src/containers/tasks-page/task-item.tsx
index 2131fd643417..b8950cd60f5b 100644
--- a/cvat-ui/src/containers/tasks-page/task-item.tsx
+++ b/cvat-ui/src/containers/tasks-page/task-item.tsx
@@ -6,11 +6,9 @@
import { connect } from 'react-redux';
import { Task, Request } from 'cvat-core-wrapper';
-import {
- TasksQuery, CombinedState, ActiveInference, PluginComponent,
-} from 'reducers';
+import { CombinedState, ActiveInference, PluginComponent } from 'reducers';
import TaskItemComponent from 'components/tasks-page/task-item';
-import { getTasksAsync, updateTaskInState as updateTaskInStateAction, getTaskPreviewAsync } from 'actions/tasks-actions';
+import { updateTaskInState as updateTaskInStateAction, getTaskPreviewAsync } from 'actions/tasks-actions';
import { cancelInferenceAsync } from 'actions/models-actions';
interface StateToProps {
@@ -22,7 +20,6 @@ interface StateToProps {
}
interface DispatchToProps {
- getTasks(query: TasksQuery): void;
updateTaskInState(task: Task): void;
cancelAutoAnnotation(): void;
}
@@ -53,9 +50,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {
return {
- getTasks(query: TasksQuery): void {
- dispatch(getTasksAsync(query));
- },
cancelAutoAnnotation(): void {
dispatch(cancelInferenceAsync(own.taskID));
},
diff --git a/cvat-ui/src/containers/tasks-page/tasks-list.tsx b/cvat-ui/src/containers/tasks-page/tasks-list.tsx
index b32cfaf7186a..630e81647691 100644
--- a/cvat-ui/src/containers/tasks-page/tasks-list.tsx
+++ b/cvat-ui/src/containers/tasks-page/tasks-list.tsx
@@ -1,39 +1,24 @@
// Copyright (C) 2020-2022 Intel Corporation
-// Copyright (C) 2022 CVAT.ai Corporation
+// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
-import { TasksState, TasksQuery, CombinedState } from 'reducers';
+import { TasksState, CombinedState } from 'reducers';
import TasksListComponent from 'components/tasks-page/task-list';
-import { getTasksAsync } from 'actions/tasks-actions';
interface StateToProps {
tasks: TasksState;
}
-interface DispatchToProps {
- getTasks: (query: TasksQuery) => void;
-}
-
function mapStateToProps(state: CombinedState): StateToProps {
return {
tasks: state.tasks,
};
}
-function mapDispatchToProps(dispatch: any): DispatchToProps {
- return {
- getTasks: (query: TasksQuery): void => {
- dispatch(getTasksAsync(query));
- },
- };
-}
-
-type TasksListContainerProps = StateToProps & DispatchToProps;
-
-function TasksListContainer(props: TasksListContainerProps): JSX.Element {
+function TasksListContainer(props: StateToProps): JSX.Element {
const { tasks } = props;
return (
@@ -43,4 +28,4 @@ function TasksListContainer(props: TasksListContainerProps): JSX.Element {
);
}
-export default connect(mapStateToProps, mapDispatchToProps)(TasksListContainer);
+export default connect(mapStateToProps)(TasksListContainer);
diff --git a/cvat-ui/src/cvat-core-wrapper.ts b/cvat-ui/src/cvat-core-wrapper.ts
index 94b70373a1c7..52f71d6044bc 100644
--- a/cvat-ui/src/cvat-core-wrapper.ts
+++ b/cvat-ui/src/cvat-core-wrapper.ts
@@ -28,7 +28,7 @@ import { ServerError, RequestError } from 'cvat-core/src/exceptions';
import {
ShapeType, LabelType, ModelKind, ModelProviders,
ModelReturnType, DimensionType, JobType,
- JobStage, JobState, RQStatus,
+ JobStage, JobState, RQStatus, StorageLocation,
} from 'cvat-core/src/enums';
import { Storage, StorageData } from 'cvat-core/src/storage';
import Issue from 'cvat-core/src/issue';
@@ -109,6 +109,7 @@ export {
Request,
JobValidationLayout,
TaskValidationLayout,
+ StorageLocation,
};
export type {
diff --git a/cvat-ui/src/index.html b/cvat-ui/src/index.html
index 65e4812fe512..f23b42af30d9 100644
--- a/cvat-ui/src/index.html
+++ b/cvat-ui/src/index.html
@@ -16,7 +16,6 @@
name="description"
content="Computer Vision Annotation Tool (CVAT) is a free, open source, web-based image and video annotation tool which is used for labeling data for computer vision algorithms. CVAT supports the primary tasks of supervised machine learning: object detection, image classification, and image segmentation. CVAT allows users to annotate data for each of these cases"
/>
-
diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts
index 847a0b84d93d..c21aff497548 100644
--- a/cvat-ui/src/reducers/annotation-reducer.ts
+++ b/cvat-ui/src/reducers/annotation-reducer.ts
@@ -51,6 +51,7 @@ const defaultState: AnnotationState = {
instance: null,
ready: false,
activeControl: ActiveControl.CURSOR,
+ activeObjectHidden: false,
},
job: {
openTime: null,
@@ -66,6 +67,7 @@ const defaultState: AnnotationState = {
groundTruthJobFramesMeta: null,
groundTruthInstance: null,
},
+ frameNumbers: [],
instance: null,
meta: null,
attributes: {},
@@ -94,6 +96,9 @@ const defaultState: AnnotationState = {
activeLabelID: null,
activeObjectType: ObjectType.SHAPE,
},
+ editing: {
+ objectState: null,
+ },
annotations: {
activatedStateID: null,
activatedElementID: null,
@@ -161,6 +166,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
job,
jobMeta,
openTime,
+ frameNumbers,
frameNumber: number,
frameFilename: filename,
relatedFiles,
@@ -205,6 +211,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
job: {
...state.job,
openTime,
+ frameNumbers,
fetching: false,
instance: job,
meta: jobMeta,
@@ -633,6 +640,26 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
+ case AnnotationActionTypes.UPDATE_EDITED_STATE: {
+ const { objectState } = action.payload;
+ return {
+ ...state,
+ editing: {
+ ...state.editing,
+ objectState,
+ },
+ };
+ }
+ case AnnotationActionTypes.HIDE_ACTIVE_OBJECT: {
+ const { hide } = action.payload;
+ return {
+ ...state,
+ canvas: {
+ ...state.canvas,
+ activeObjectHidden: hide,
+ },
+ };
+ }
case AnnotationActionTypes.REMOVE_OBJECT_SUCCESS: {
const { objectState, history } = action.payload;
const contextMenuClientID = state.canvas.contextMenu.clientID;
@@ -980,7 +1007,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}
case AnnotationActionTypes.CHANGE_WORKSPACE: {
const { workspace } = action.payload;
- if (state.canvas.activeControl !== ActiveControl.CURSOR) {
+ if (state.canvas.activeControl !== ActiveControl.CURSOR && state.workspace !== Workspace.SINGLE_SHAPE) {
return state;
}
@@ -992,6 +1019,11 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
states: state.annotations.states.filter((_state) => !_state.isGroundTruth),
activatedStateID: null,
activatedAttributeID: null,
+
+ },
+ canvas: {
+ ...state.canvas,
+ activeControl: ActiveControl.CURSOR,
},
};
}
diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts
index 6c297cd5f4ac..14196846a393 100644
--- a/cvat-ui/src/reducers/index.ts
+++ b/cvat-ui/src/reducers/index.ts
@@ -8,7 +8,7 @@ import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrap
import {
Webhook, MLModel, Organization, Job, Task, Project, Label, User,
QualityConflict, FramesMetaData, RQStatus, Event, Invitation, SerializedAPISchema,
- Request, JobValidationLayout, QualitySettings, TaskValidationLayout,
+ Request, JobValidationLayout, QualitySettings, TaskValidationLayout, ObjectState,
} from 'cvat-core-wrapper';
import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors';
import { KeyMap, KeyMapItem } from 'utils/mousetrap-react';
@@ -38,6 +38,7 @@ interface Preview {
}
export interface ProjectsState {
+ fetchingTimestamp: number;
initialized: boolean;
fetching: boolean;
count: number;
@@ -75,6 +76,7 @@ export interface JobsQuery {
}
export interface JobsState {
+ fetchingTimestamp: number;
query: JobsQuery;
fetching: boolean;
count: number;
@@ -90,6 +92,7 @@ export interface JobsState {
}
export interface TasksState {
+ fetchingTimestamp: number;
initialized: boolean;
fetching: boolean;
moveTask: {
@@ -694,6 +697,10 @@ export enum NavigationType {
EMPTY = 'empty',
}
+export interface EditingState {
+ objectState: ObjectState | null;
+}
+
export interface AnnotationState {
activities: {
loads: {
@@ -719,6 +726,7 @@ export interface AnnotationState {
instance: Canvas | Canvas3d | null;
ready: boolean;
activeControl: ActiveControl;
+ activeObjectHidden: boolean;
};
job: {
openTime: null | number;
@@ -726,6 +734,7 @@ export interface AnnotationState {
requestedId: number | null;
meta: FramesMetaData | null;
instance: Job | null | undefined;
+ frameNumbers: number[];
queryParameters: {
initialOpenGuide: boolean;
defaultLabel: string | null;
@@ -768,6 +777,7 @@ export interface AnnotationState {
activeObjectType: ObjectType;
activeInitialState?: any;
};
+ editing: EditingState;
annotations: {
activatedStateID: number | null;
activatedElementID: number | null;
diff --git a/cvat-ui/src/reducers/jobs-reducer.ts b/cvat-ui/src/reducers/jobs-reducer.ts
index c7b07fc1fa30..4be7c5c285f8 100644
--- a/cvat-ui/src/reducers/jobs-reducer.ts
+++ b/cvat-ui/src/reducers/jobs-reducer.ts
@@ -7,6 +7,7 @@ import { JobsActions, JobsActionTypes } from 'actions/jobs-actions';
import { JobsState } from '.';
const defaultState: JobsState = {
+ fetchingTimestamp: Date.now(),
fetching: false,
count: 0,
query: {
@@ -27,6 +28,7 @@ export default (state: JobsState = defaultState, action: JobsActions): JobsState
case JobsActionTypes.GET_JOBS: {
return {
...state,
+ fetchingTimestamp: action.payload.fetchingTimestamp,
fetching: true,
query: {
...defaultState.query,
diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts
index 5bc4becb223e..6bb56f50017c 100644
--- a/cvat-ui/src/reducers/notifications-reducer.ts
+++ b/cvat-ui/src/reducers/notifications-reducer.ts
@@ -5,7 +5,7 @@
import { AnyAction } from 'redux';
-import { ServerError, RequestError } from 'cvat-core-wrapper';
+import { ServerError, RequestError, StorageLocation } from 'cvat-core-wrapper';
import { AuthActionTypes } from 'actions/auth-actions';
import { FormatsActionTypes } from 'actions/formats-actions';
import { ModelsActionTypes } from 'actions/models-actions';
@@ -355,7 +355,7 @@ export default function (state = defaultState, action: AnyAction): Notifications
...state.messages.auth,
requestPasswordResetDone: {
message: `Check your email for a link to reset your password.
- If it doesn’t appear within a few minutes, check your spam folder.`,
+ If it doesn't appear within a few minutes, check your spam folder.`,
},
},
},
@@ -546,9 +546,9 @@ export default function (state = defaultState, action: AnyAction): Notifications
instance, instanceType, resource, target,
} = action.payload;
let description = `Export ${resource} for ${instanceType} ${instance.id} is finished. `;
- if (target === 'local') {
+ if (target === StorageLocation.LOCAL) {
description += 'You can [download it here](/requests).';
- } else if (target === 'cloudstorage') {
+ } else if (target === StorageLocation.CLOUD_STORAGE) {
description =
`Export ${resource} for ${instanceType} ${instance.id} has been uploaded to cloud storage.`;
}
@@ -590,9 +590,9 @@ export default function (state = defaultState, action: AnyAction): Notifications
instance, instanceType, target,
} = action.payload;
let description = `Backup for the ${instanceType} ${instance.id} is finished. `;
- if (target === 'local') {
+ if (target === StorageLocation.LOCAL) {
description += 'You can [download it here](/requests).';
- } else if (target === 'cloudstorage') {
+ } else if (target === StorageLocation.CLOUD_STORAGE) {
description =
`Backup for the ${instanceType} ${instance.id} has been uploaded to cloud storage.`;
}
diff --git a/cvat-ui/src/reducers/projects-reducer.ts b/cvat-ui/src/reducers/projects-reducer.ts
index 7c2db7cd80ac..5f74f1c5c620 100644
--- a/cvat-ui/src/reducers/projects-reducer.ts
+++ b/cvat-ui/src/reducers/projects-reducer.ts
@@ -12,6 +12,7 @@ import { AuthActionTypes } from 'actions/auth-actions';
import { ProjectsState } from '.';
const defaultState: ProjectsState = {
+ fetchingTimestamp: Date.now(),
initialized: false,
fetching: false,
count: 0,
@@ -59,6 +60,7 @@ export default (state: ProjectsState = defaultState, action: AnyAction): Project
case ProjectsActionTypes.GET_PROJECTS:
return {
...state,
+ fetchingTimestamp: action.payload.fetchingTimestamp,
initialized: false,
fetching: true,
count: 0,
diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts
index ce2e88258c0c..b686bbc7f26c 100644
--- a/cvat-ui/src/reducers/tasks-reducer.ts
+++ b/cvat-ui/src/reducers/tasks-reducer.ts
@@ -12,6 +12,7 @@ import { ProjectsActionTypes } from 'actions/projects-actions';
import { TasksState } from '.';
const defaultState: TasksState = {
+ fetchingTimestamp: Date.now(),
initialized: false,
fetching: false,
moveTask: {
@@ -43,6 +44,7 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
...state.activities,
deletes: {},
},
+ fetchingTimestamp: action.payload.fetchingTimestamp,
initialized: false,
fetching: true,
count: 0,
diff --git a/cvat/__init__.py b/cvat/__init__.py
index ac47316ad0a1..10ef426963f5 100644
--- a/cvat/__init__.py
+++ b/cvat/__init__.py
@@ -4,6 +4,6 @@
from cvat.utils.version import get_version
-VERSION = (2, 21, 3, 'final', 0)
+VERSION = (2, 22, 0, "final", 0)
__version__ = get_version(VERSION)
diff --git a/cvat/apps/analytics_report/rules/analytics_reports.rego b/cvat/apps/analytics_report/rules/analytics_reports.rego
index 706d6e701dbe..87910192779b 100644
--- a/cvat/apps/analytics_report/rules/analytics_reports.rego
+++ b/cvat/apps/analytics_report/rules/analytics_reports.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py
index 35d4b902a53a..1c70520a7090 100644
--- a/cvat/apps/dataset_manager/bindings.py
+++ b/cvat/apps/dataset_manager/bindings.py
@@ -285,6 +285,7 @@ def __init__(self,
self._db_data: models.Data = db_task.data
self._use_server_track_ids = use_server_track_ids
self._required_frames = included_frames
+ self._initialized_included_frames: Optional[Set[int]] = None
self._db_subset = db_task.subset
super().__init__(db_task)
@@ -536,12 +537,14 @@ def shapes(self):
yield self._export_labeled_shape(shape)
def get_included_frames(self):
- return set(
- i for i in self.rel_range
- if not self._is_frame_deleted(i)
- and not self._is_frame_excluded(i)
- and self._is_frame_required(i)
- )
+ if self._initialized_included_frames is None:
+ self._initialized_included_frames = set(
+ i for i in self.rel_range
+ if not self._is_frame_deleted(i)
+ and not self._is_frame_excluded(i)
+ and self._is_frame_required(i)
+ )
+ return self._initialized_included_frames
def _is_frame_deleted(self, frame):
return frame in self._deleted_frames
@@ -1112,7 +1115,10 @@ def _init_frame_info(self):
} for frame in range(task.data.size)})
else:
self._frame_info.update({(task.id, self.rel_frame_id(task.id, db_image.frame)): {
- "path": mangle_image_name(db_image.path, defaulted_subset, original_names),
+ # do not modify honeypot names since they will be excluded from the dataset
+ # and their quantity should not affect the validation frame name
+ "path": mangle_image_name(db_image.path, defaulted_subset, original_names) \
+ if not db_image.is_placeholder else db_image.path,
"id": db_image.id,
"width": db_image.width,
"height": db_image.height,
@@ -1271,25 +1277,36 @@ def get_frame(task_id: int, idx: int) -> ProjectData.Frame:
return frames[(frame_info["subset"], abs_frame)]
if include_empty:
- for ident in sorted(self._frame_info):
- if ident not in self._deleted_frames:
- get_frame(*ident)
+ for task_id, frame in sorted(self._frame_info):
+ if not self._tasks_data.get(task_id):
+ self.init_task_data(task_id)
+
+ task_included_frames = self._tasks_data[task_id].get_included_frames()
+ if frame in task_included_frames:
+ get_frame(task_id, frame)
+
+ for task_data in self.task_data:
+ task: Task = task_data.db_instance
- for task in self._db_tasks.values():
anno_manager = AnnotationManager(
self._annotation_irs[task.id], dimension=self._annotation_irs[task.id].dimension
)
+ task_included_frames = task_data.get_included_frames()
+
for shape in sorted(
anno_manager.to_shapes(
task.data.size,
+ included_frames=task_included_frames,
include_outside=False,
use_server_track_ids=self._use_server_track_ids
),
key=lambda shape: shape.get("z_order", 0)
):
- if (task.id, shape['frame']) not in self._frame_info or (task.id, shape['frame']) in self._deleted_frames:
+ if shape['frame'] in task_data.deleted_frames:
continue
+ assert (task.id, shape['frame']) in self._frame_info
+
if 'track_id' in shape:
if shape['outside']:
continue
@@ -1368,23 +1385,33 @@ def soft_attribute_import(self, value: bool):
for task_data in self._tasks_data.values():
task_data.soft_attribute_import = value
+
+ def init_task_data(self, task_id: int) -> TaskData:
+ try:
+ task = self._db_tasks[task_id]
+ except KeyError as ex:
+ raise Exception("There is no such task in the project") from ex
+
+ task_data = TaskData(
+ annotation_ir=self._annotation_irs[task_id],
+ db_task=task,
+ host=self._host,
+ create_callback=self._task_annotations[task_id].create \
+ if self._task_annotations is not None else None,
+ )
+ task_data._MAX_ANNO_SIZE //= len(self._db_tasks)
+ task_data.soft_attribute_import = self.soft_attribute_import
+ self._tasks_data[task_id] = task_data
+
+ return task_data
+
@property
def task_data(self):
- for task_id, task in self._db_tasks.items():
+ for task_id in self._db_tasks.keys():
if task_id in self._tasks_data:
yield self._tasks_data[task_id]
else:
- task_data = TaskData(
- annotation_ir=self._annotation_irs[task_id],
- db_task=task,
- host=self._host,
- create_callback=self._task_annotations[task_id].create \
- if self._task_annotations is not None else None,
- )
- task_data._MAX_ANNO_SIZE //= len(self._db_tasks)
- task_data.soft_attribute_import = self.soft_attribute_import
- self._tasks_data[task_id] = task_data
- yield task_data
+ yield self.init_task_data(task_id)
@staticmethod
def _get_filename(path):
diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py
index 4651fd398451..03ef389599e8 100644
--- a/cvat/apps/dataset_manager/formats/cvat.py
+++ b/cvat/apps/dataset_manager/formats/cvat.py
@@ -9,7 +9,7 @@
from collections import OrderedDict
from glob import glob
from io import BufferedWriter
-from typing import Callable
+from typing import Callable, Union
from datumaro.components.annotation import (AnnotationType, Bbox, Label,
LabelCategories, Points, Polygon,
@@ -22,7 +22,7 @@
from datumaro.util.image import Image
from defusedxml import ElementTree
-from cvat.apps.dataset_manager.bindings import (ProjectData, CommonData, detect_dataset,
+from cvat.apps.dataset_manager.bindings import (ProjectData, TaskData, JobData, detect_dataset,
get_defaulted_subset,
import_dm_annotations,
match_dm_item)
@@ -1370,7 +1370,7 @@ def dump_project_anno(dst_file: BufferedWriter, project_data: ProjectData, callb
callback(dumper, project_data)
dumper.close_document()
-def dump_media_files(instance_data: CommonData, img_dir: str, project_data: ProjectData = None):
+def dump_media_files(instance_data: Union[TaskData, JobData], img_dir: str, project_data: ProjectData = None):
frame_provider = make_frame_provider(instance_data.db_instance)
ext = ''
@@ -1383,9 +1383,11 @@ def dump_media_files(instance_data: CommonData, img_dir: str, project_data: Proj
quality=FrameQuality.ORIGINAL,
out_type=FrameOutputType.BUFFER,
)
+ included_frames = instance_data.get_included_frames()
+
for frame_id, frame in zip(instance_data.rel_range, frames):
- if (project_data is not None and (instance_data.db_instance.id, frame_id) in project_data.deleted_frames) \
- or frame_id in instance_data.deleted_frames:
+ # exclude deleted frames and honeypots
+ if frame_id not in included_frames:
continue
frame_name = instance_data.frame_info[frame_id]['path'] if project_data is None \
else project_data.frame_info[(instance_data.db_instance.id, frame_id)]['path']
diff --git a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py
index 3350a4180efe..059a45f6df2d 100644
--- a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py
+++ b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py
@@ -141,7 +141,7 @@ def setUpTestData(cls):
@classmethod
def create_db_users(cls):
(group_admin, _) = Group.objects.get_or_create(name="admin")
- (group_user, _) = Group.objects.get_or_create(name="business")
+ (group_user, _) = Group.objects.get_or_create(name="user")
user_admin = User.objects.create_superuser(username="admin", email="",
password="admin")
diff --git a/cvat/apps/engine/cache.py b/cvat/apps/engine/cache.py
index 990b2b31009a..295e405a41da 100644
--- a/cvat/apps/engine/cache.py
+++ b/cvat/apps/engine/cache.py
@@ -54,7 +54,7 @@
ZipChunkWriter,
ZipCompressedChunkWriter,
)
-from cvat.apps.engine.utils import md5_hash, load_image
+from cvat.apps.engine.utils import load_image, md5_hash
from utils.dataset_manifest import ImageManifestManager
slogger = ServerLogManager(__name__)
diff --git a/cvat/apps/engine/frame_provider.py b/cvat/apps/engine/frame_provider.py
index f397f0d568b1..1787d84aac40 100644
--- a/cvat/apps/engine/frame_provider.py
+++ b/cvat/apps/engine/frame_provider.py
@@ -369,7 +369,7 @@ def iterate_frames(
quality: FrameQuality = FrameQuality.ORIGINAL,
out_type: FrameOutputType = FrameOutputType.BUFFER,
) -> Iterator[DataWithMeta[AnyFrame]]:
- frame_range = itertools.count(start_frame, self._db_task.data.get_frame_step())
+ frame_range = itertools.count(start_frame)
if stop_frame:
frame_range = itertools.takewhile(lambda x: x <= stop_frame, frame_range)
@@ -377,7 +377,10 @@ def iterate_frames(
db_segment_frame_set = None
db_segment_frame_provider = None
for idx in frame_range:
- if db_segment and idx not in db_segment_frame_set:
+ if (
+ db_segment
+ and self._get_abs_frame_number(self._db_task.data, idx) not in db_segment_frame_set
+ ):
db_segment = None
db_segment_frame_set = None
db_segment_frame_provider = None
diff --git a/cvat/apps/engine/rules/annotationguides.rego b/cvat/apps/engine/rules/annotationguides.rego
index dd512af6d79a..6429eecb23a4 100644
--- a/cvat/apps/engine/rules/annotationguides.rego
+++ b/cvat/apps/engine/rules/annotationguides.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/cloudstorages.rego b/cvat/apps/engine/rules/cloudstorages.rego
index 3e278a35a7d5..04f8e0e45369 100644
--- a/cvat/apps/engine/rules/cloudstorages.rego
+++ b/cvat/apps/engine/rules/cloudstorages.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/comments.rego b/cvat/apps/engine/rules/comments.rego
index 019a5ebcecc4..9384d829b091 100644
--- a/cvat/apps/engine/rules/comments.rego
+++ b/cvat/apps/engine/rules/comments.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/issues.rego b/cvat/apps/engine/rules/issues.rego
index 803dab16c019..d8a487cbcdb1 100644
--- a/cvat/apps/engine/rules/issues.rego
+++ b/cvat/apps/engine/rules/issues.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/jobs.rego b/cvat/apps/engine/rules/jobs.rego
index 8068f7d6fdf9..7980a08d1bc0 100644
--- a/cvat/apps/engine/rules/jobs.rego
+++ b/cvat/apps/engine/rules/jobs.rego
@@ -12,7 +12,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/labels.rego b/cvat/apps/engine/rules/labels.rego
index a50296377683..1d4344da7fe4 100644
--- a/cvat/apps/engine/rules/labels.rego
+++ b/cvat/apps/engine/rules/labels.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/projects.rego b/cvat/apps/engine/rules/projects.rego
index 8e40ddc43c8d..bdaabb120135 100644
--- a/cvat/apps/engine/rules/projects.rego
+++ b/cvat/apps/engine/rules/projects.rego
@@ -12,7 +12,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
@@ -59,19 +59,6 @@ allow if {
organizations.has_perm(organizations.SUPERVISOR)
}
-allow if {
- input.scope in {utils.CREATE, utils.IMPORT_BACKUP}
- utils.is_sandbox
- utils.has_perm(utils.BUSINESS)
-}
-
-allow if {
- input.scope in {utils.CREATE, utils.IMPORT_BACKUP}
- input.auth.organization.id == input.resource.organization.id
- utils.has_perm(utils.BUSINESS)
- organizations.has_perm(organizations.SUPERVISOR)
-}
-
allow if {
input.scope == utils.LIST
utils.is_sandbox
diff --git a/cvat/apps/engine/rules/server.rego b/cvat/apps/engine/rules/server.rego
index bfe3b47a0d46..6833826a0762 100644
--- a/cvat/apps/engine/rules/server.rego
+++ b/cvat/apps/engine/rules/server.rego
@@ -9,7 +9,7 @@ import data.utils
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/rules/tasks.rego b/cvat/apps/engine/rules/tasks.rego
index 99d126d2b443..f020cf4ac976 100644
--- a/cvat/apps/engine/rules/tasks.rego
+++ b/cvat/apps/engine/rules/tasks.rego
@@ -13,7 +13,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
@@ -93,19 +93,6 @@ allow if {
organizations.has_perm(organizations.SUPERVISOR)
}
-allow if {
- input.scope in {utils.CREATE, utils.IMPORT_BACKUP}
- utils.is_sandbox
- utils.has_perm(utils.BUSINESS)
-}
-
-allow if {
- input.scope in {utils.CREATE, utils.IMPORT_BACKUP}
- input.auth.organization.id == input.resource.organization.id
- utils.has_perm(utils.BUSINESS)
- organizations.has_perm(organizations.SUPERVISOR)
-}
-
allow if {
input.scope == utils.CREATE_IN_PROJECT
utils.is_sandbox
@@ -128,20 +115,6 @@ allow if {
is_project_staff
}
-allow if {
- input.scope == utils.CREATE_IN_PROJECT
- utils.is_sandbox
- utils.has_perm(utils.BUSINESS)
- is_project_staff
-}
-
-allow if {
- input.scope == utils.CREATE_IN_PROJECT
- input.auth.organization.id == input.resource.organization.id
- utils.has_perm(utils.BUSINESS)
- organizations.has_perm(organizations.SUPERVISOR)
-}
-
allow if {
input.scope == utils.LIST
utils.is_sandbox
diff --git a/cvat/apps/engine/rules/tests/generators/annotationguides_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/annotationguides_test.gen.rego.py
index 4cf562741677..1dbfcc1167f5 100644
--- a/cvat/apps/engine/rules/tests/generators/annotationguides_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/annotationguides_test.gen.rego.py
@@ -46,7 +46,7 @@ def read_rules(name):
"job:assignee",
"none",
]
-GROUPS = ["admin", "business", "user", "worker"]
+GROUPS = ["admin", "user", "worker"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
diff --git a/cvat/apps/engine/rules/tests/generators/cloudstorages_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/cloudstorages_test.gen.rego.py
index 63460df540b2..4a4941e0fd1c 100644
--- a/cvat/apps/engine/rules/tests/generators/cloudstorages_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/cloudstorages_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["owner", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [False, True]
diff --git a/cvat/apps/engine/rules/tests/generators/comments_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/comments_test.gen.rego.py
index f36c8a7dfa0d..a13a1897c66a 100644
--- a/cvat/apps/engine/rules/tests/generators/comments_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/comments_test.gen.rego.py
@@ -51,7 +51,7 @@ def read_rules(name):
"owner",
"none",
]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
HAS_PROJ = [True, False]
diff --git a/cvat/apps/engine/rules/tests/generators/issues_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/issues_test.gen.rego.py
index 0a35d83880eb..53213eb39d2d 100644
--- a/cvat/apps/engine/rules/tests/generators/issues_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/issues_test.gen.rego.py
@@ -50,7 +50,7 @@ def read_rules(name):
"assignee",
"none",
]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
HAS_PROJ = [True, False]
diff --git a/cvat/apps/engine/rules/tests/generators/jobs_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/jobs_test.gen.rego.py
index ca799f953cd3..e36f8c8ec7be 100644
--- a/cvat/apps/engine/rules/tests/generators/jobs_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/jobs_test.gen.rego.py
@@ -50,7 +50,7 @@ def read_rules(name):
"assignee",
"none",
]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
diff --git a/cvat/apps/engine/rules/tests/generators/projects_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/projects_test.gen.rego.py
index 6657f21d2994..d4a7259893fc 100644
--- a/cvat/apps/engine/rules/tests/generators/projects_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/projects_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["owner", "assignee", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [False, True]
diff --git a/cvat/apps/engine/rules/tests/generators/server_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/server_test.gen.rego.py
index 8e9b57a814d8..c2b4195191a4 100644
--- a/cvat/apps/engine/rules/tests/generators/server_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/server_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
diff --git a/cvat/apps/engine/rules/tests/generators/tasks_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/tasks_test.gen.rego.py
index 61da5c8520de..30925fcee18b 100644
--- a/cvat/apps/engine/rules/tests/generators/tasks_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/tasks_test.gen.rego.py
@@ -43,7 +43,7 @@ def read_rules(name):
SCOPES = list({rule["scope"] for rule in simple_rules})
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["project:owner", "project:assignee", "owner", "assignee", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
diff --git a/cvat/apps/engine/rules/tests/generators/users_test.gen.rego.py b/cvat/apps/engine/rules/tests/generators/users_test.gen.rego.py
index 595cbaae4ee4..a609492868f6 100644
--- a/cvat/apps/engine/rules/tests/generators/users_test.gen.rego.py
+++ b/cvat/apps/engine/rules/tests/generators/users_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["self", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
diff --git a/cvat/apps/engine/rules/users.rego b/cvat/apps/engine/rules/users.rego
index 63469228e11a..34cb0f4866d3 100644
--- a/cvat/apps/engine/rules/users.rego
+++ b/cvat/apps/engine/rules/users.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py
index 07e01461ab73..e6ed6b6c0303 100644
--- a/cvat/apps/engine/tests/test_rest_api.py
+++ b/cvat/apps/engine/tests/test_rest_api.py
@@ -54,7 +54,6 @@
def create_db_users(cls):
(group_admin, _) = Group.objects.get_or_create(name="admin")
- (group_business, _) = Group.objects.get_or_create(name="business")
(group_user, _) = Group.objects.get_or_create(name="user")
(group_annotator, _) = Group.objects.get_or_create(name="worker")
(group_somebody, _) = Group.objects.get_or_create(name="somebody")
@@ -63,7 +62,7 @@ def create_db_users(cls):
password="admin")
user_admin.groups.add(group_admin)
user_owner = User.objects.create_user(username="user1", password="user1")
- user_owner.groups.add(group_business)
+ user_owner.groups.add(group_user)
user_assignee = User.objects.create_user(username="user2", password="user2")
user_assignee.groups.add(group_annotator)
user_annotator = User.objects.create_user(username="user3", password="user3")
diff --git a/cvat/apps/events/rules/events.rego b/cvat/apps/events/rules/events.rego
index 0152ec721ba8..58ec43763b2f 100644
--- a/cvat/apps/events/rules/events.rego
+++ b/cvat/apps/events/rules/events.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/events/rules/tests/generators/events_test.gen.rego.py b/cvat/apps/events/rules/tests/generators/events_test.gen.rego.py
index da9d54d79e22..dee2d4a68963 100644
--- a/cvat/apps/events/rules/tests/generators/events_test.gen.rego.py
+++ b/cvat/apps/events/rules/tests/generators/events_test.gen.rego.py
@@ -42,7 +42,7 @@ def read_rules(name):
SCOPES = list({rule["scope"] for rule in simple_rules})
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
diff --git a/cvat/apps/iam/migrations/0001_remove_business_group.py b/cvat/apps/iam/migrations/0001_remove_business_group.py
new file mode 100644
index 000000000000..2bf1a56b4065
--- /dev/null
+++ b/cvat/apps/iam/migrations/0001_remove_business_group.py
@@ -0,0 +1,31 @@
+# Generated by Django 4.2.16 on 2024-10-30 12:03
+from django.conf import settings
+from django.db import migrations
+
+
+BUSINESS_GROUP_NAME = "business"
+USER_GROUP_NAME = "user"
+
+
+def delete_business_group(apps, schema_editor):
+ Group = apps.get_model('auth', 'Group')
+ User = apps.get_model(settings.AUTH_USER_MODEL)
+
+ if user_group := Group.objects.filter(name=USER_GROUP_NAME).first():
+ user_group.user_set.add(*User.objects.filter(groups__name=BUSINESS_GROUP_NAME))
+
+ Group.objects.filter(name=BUSINESS_GROUP_NAME).delete()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ delete_business_group,
+ reverse_code=migrations.RunPython.noop,
+ ),
+ ]
diff --git a/cvat/apps/iam/migrations/__init__.py b/cvat/apps/iam/migrations/__init__.py
new file mode 100644
index 000000000000..bd6d6576ecf2
--- /dev/null
+++ b/cvat/apps/iam/migrations/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (C) 2024 CVAT.ai Corporation
+#
+# SPDX-License-Identifier: MIT
diff --git a/cvat/apps/iam/permissions.py b/cvat/apps/iam/permissions.py
index 55a91cc570b9..bb2ab44a414f 100644
--- a/cvat/apps/iam/permissions.py
+++ b/cvat/apps/iam/permissions.py
@@ -198,9 +198,9 @@ def filter(self, queryset):
q_objects.append(Q())
# By default, a QuerySet will not eliminate duplicate rows. If your
- # query spans multiple tables (e.g. members__user_id, owner_id), it’s
+ # query spans multiple tables (e.g. members__user_id, owner_id), it's
# possible to get duplicate results when a QuerySet is evaluated.
- # That’s when you’d use distinct().
+ # That's when you'd use distinct().
return queryset.filter(q_objects[0]).distinct()
@classmethod
diff --git a/cvat/apps/iam/rules/utils.rego b/cvat/apps/iam/rules/utils.rego
index 4bd64f0ae108..148b2ec1a2a1 100644
--- a/cvat/apps/iam/rules/utils.rego
+++ b/cvat/apps/iam/rules/utils.rego
@@ -4,7 +4,6 @@ import rego.v1
# Groups
ADMIN := "admin"
-BUSINESS := "business"
USER := "user"
WORKER := "worker"
@@ -65,7 +64,6 @@ UPDATE_VALIDATION_LAYOUT := "update:validation_layout"
get_priority(privilege) := {
ADMIN: 0,
- BUSINESS: 50,
USER: 75,
WORKER: 100,
null: 1000
@@ -79,10 +77,6 @@ is_admin if {
input.auth.user.privilege == ADMIN
}
-is_business if {
- input.auth.user.privilege == BUSINESS
-}
-
is_user if {
input.auth.user.privilege == USER
}
diff --git a/cvat/apps/lambda_manager/rules/lambda.rego b/cvat/apps/lambda_manager/rules/lambda.rego
index 2829860c0932..7b3b6c828975 100644
--- a/cvat/apps/lambda_manager/rules/lambda.rego
+++ b/cvat/apps/lambda_manager/rules/lambda.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/lambda_manager/rules/tests/generators/lambda_test.gen.rego.py b/cvat/apps/lambda_manager/rules/tests/generators/lambda_test.gen.rego.py
index 5a669c5f49fc..94f694988a38 100644
--- a/cvat/apps/lambda_manager/rules/tests/generators/lambda_test.gen.rego.py
+++ b/cvat/apps/lambda_manager/rules/tests/generators/lambda_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = list({rule["scope"] for rule in simple_rules})
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
diff --git a/cvat/apps/lambda_manager/tests/test_lambda.py b/cvat/apps/lambda_manager/tests/test_lambda.py
index 57c74cf2c529..794ef8cefabe 100644
--- a/cvat/apps/lambda_manager/tests/test_lambda.py
+++ b/cvat/apps/lambda_manager/tests/test_lambda.py
@@ -133,7 +133,7 @@ def _invoke_function(self, func, payload):
@classmethod
def _create_db_users(cls):
(group_admin, _) = Group.objects.get_or_create(name="admin")
- (group_user, _) = Group.objects.get_or_create(name="business")
+ (group_user, _) = Group.objects.get_or_create(name="user")
user_admin = User.objects.create_superuser(username="admin", email="",
password="admin")
diff --git a/cvat/apps/log_viewer/permissions.py b/cvat/apps/log_viewer/permissions.py
index c50cf9302c61..d25aa7fe275a 100644
--- a/cvat/apps/log_viewer/permissions.py
+++ b/cvat/apps/log_viewer/permissions.py
@@ -49,6 +49,4 @@ def get_scopes(request, view, obj):
}[view.action]]
def get_resource(self):
- return {
- 'visibility': 'public' if settings.RESTRICTIONS['analytics_visibility'] else 'private',
- }
+ return None
diff --git a/cvat/apps/log_viewer/rules/analytics.rego b/cvat/apps/log_viewer/rules/analytics.rego
index b43a9c1b1115..f40653f63e2b 100644
--- a/cvat/apps/log_viewer/rules/analytics.rego
+++ b/cvat/apps/log_viewer/rules/analytics.rego
@@ -9,7 +9,7 @@ import data.utils
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null,
+# "privilege": <"admin"|"user"|"worker"> or null,
# "has_analytics_access":
# },
# "organization": {
@@ -22,19 +22,10 @@ import data.utils
# }
# } or null,
# },
-# "resource": {
-# "visibility": <"public"|"private"> or null,
-# }
# }
default allow := false
-allow if {
- input.resource.visibility == utils.PUBLIC
- input.scope == utils.VIEW
- utils.has_perm(utils.BUSINESS)
-}
-
allow if {
input.auth.user.has_analytics_access
}
diff --git a/cvat/apps/log_viewer/rules/tests/configs/analytics.csv b/cvat/apps/log_viewer/rules/tests/configs/analytics.csv
index a581e0716e5f..7ff4ea280475 100644
--- a/cvat/apps/log_viewer/rules/tests/configs/analytics.csv
+++ b/cvat/apps/log_viewer/rules/tests/configs/analytics.csv
@@ -1,3 +1,2 @@
Scope,Resource,Context,Ownership,Limit,Method,URL,Privilege,Membership,HasAnalyticsAccess
-view,Analytics,N/A,N/A,resource['visibility']=='public',GET,"/analytics",business,N/A,N/A
view,Analytics,N/A,N/A,,GET,"/analytics",none,N/A,true
diff --git a/cvat/apps/log_viewer/rules/tests/generators/analytics_test.gen.rego.py b/cvat/apps/log_viewer/rules/tests/generators/analytics_test.gen.rego.py
index 7e40f092607c..95d566e4b93a 100644
--- a/cvat/apps/log_viewer/rules/tests/generators/analytics_test.gen.rego.py
+++ b/cvat/apps/log_viewer/rules/tests/generators/analytics_test.gen.rego.py
@@ -41,18 +41,12 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
HAS_ANALYTICS_ACCESS = [True, False]
def RESOURCES(scope):
- if scope == "view":
- return [
- {"visibility": "public"},
- {"visibility": "private"},
- ]
-
return [None]
diff --git a/cvat/apps/organizations/rules/invitations.rego b/cvat/apps/organizations/rules/invitations.rego
index 3a51f76128e5..2e15ba4a8637 100644
--- a/cvat/apps/organizations/rules/invitations.rego
+++ b/cvat/apps/organizations/rules/invitations.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/organizations/rules/memberships.rego b/cvat/apps/organizations/rules/memberships.rego
index c23f3039ff16..09752e4b7007 100644
--- a/cvat/apps/organizations/rules/memberships.rego
+++ b/cvat/apps/organizations/rules/memberships.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/organizations/rules/organizations.rego b/cvat/apps/organizations/rules/organizations.rego
index 24643feab703..6d0a8c29c19b 100644
--- a/cvat/apps/organizations/rules/organizations.rego
+++ b/cvat/apps/organizations/rules/organizations.rego
@@ -9,7 +9,7 @@ import data.utils
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": null,
# },
@@ -69,11 +69,6 @@ allow if {
utils.has_perm(utils.USER)
}
-allow if {
- input.scope == utils.CREATE
- utils.has_perm(utils.BUSINESS)
-}
-
filter := [] if { # Django Q object to filter list of entries
utils.is_admin
} else := qobject if {
diff --git a/cvat/apps/organizations/rules/tests/generators/invitations_test.gen.rego.py b/cvat/apps/organizations/rules/tests/generators/invitations_test.gen.rego.py
index c3ba86abb75f..bf7edec50713 100644
--- a/cvat/apps/organizations/rules/tests/generators/invitations_test.gen.rego.py
+++ b/cvat/apps/organizations/rules/tests/generators/invitations_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["owner", "invitee", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [False, True]
diff --git a/cvat/apps/organizations/rules/tests/generators/memberships_test.gen.rego.py b/cvat/apps/organizations/rules/tests/generators/memberships_test.gen.rego.py
index b86548142da7..c74a4a7c992b 100644
--- a/cvat/apps/organizations/rules/tests/generators/memberships_test.gen.rego.py
+++ b/cvat/apps/organizations/rules/tests/generators/memberships_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["self", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [False, True]
diff --git a/cvat/apps/organizations/rules/tests/generators/organizations_test.gen.rego.py b/cvat/apps/organizations/rules/tests/generators/organizations_test.gen.rego.py
index a6c111bfef40..d2a8a6fb653b 100644
--- a/cvat/apps/organizations/rules/tests/generators/organizations_test.gen.rego.py
+++ b/cvat/apps/organizations/rules/tests/generators/organizations_test.gen.rego.py
@@ -41,7 +41,7 @@ def read_rules(name):
SCOPES = {rule["scope"] for rule in simple_rules}
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["owner", "maintainer", "supervisor", "worker", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
diff --git a/cvat/apps/profiler.py b/cvat/apps/profiler.py
index 45ddbc95f478..0a3885bb00a5 100644
--- a/cvat/apps/profiler.py
+++ b/cvat/apps/profiler.py
@@ -1,13 +1,16 @@
from django.apps import apps
-if apps.is_installed('silk'):
+if apps.is_installed("silk"):
from silk.profiling.profiler import silk_profile # pylint: disable=unused-import
else:
from functools import wraps
+
def silk_profile(name=None):
def profile(f):
@wraps(f)
def wrapped(*args, **kwargs):
return f(*args, **kwargs)
+
return wrapped
+
return profile
diff --git a/cvat/apps/quality_control/migrations/0004_qualitysettings_point_size_base.py b/cvat/apps/quality_control/migrations/0004_qualitysettings_point_size_base.py
new file mode 100644
index 000000000000..024d263356ac
--- /dev/null
+++ b/cvat/apps/quality_control/migrations/0004_qualitysettings_point_size_base.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.15 on 2024-11-06 15:39
+
+from django.db import migrations, models
+
+import cvat.apps.quality_control.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("quality_control", "0003_qualityreport_assignee_last_updated_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="qualitysettings",
+ name="point_size_base",
+ field=models.CharField(
+ choices=[("image_size", "IMAGE_SIZE"), ("group_bbox_size", "GROUP_BBOX_SIZE")],
+ default=cvat.apps.quality_control.models.PointSizeBase["GROUP_BBOX_SIZE"],
+ max_length=32,
+ ),
+ ),
+ ]
diff --git a/cvat/apps/quality_control/migrations/0005_qualitysettings_match_empty.py b/cvat/apps/quality_control/migrations/0005_qualitysettings_match_empty.py
new file mode 100644
index 000000000000..dba6a0c9bc43
--- /dev/null
+++ b/cvat/apps/quality_control/migrations/0005_qualitysettings_match_empty.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.15 on 2024-11-05 14:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("quality_control", "0004_qualitysettings_point_size_base"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="qualitysettings",
+ name="match_empty_frames",
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/cvat/apps/quality_control/models.py b/cvat/apps/quality_control/models.py
index 37f0f1f9612d..b8cf76873597 100644
--- a/cvat/apps/quality_control/models.py
+++ b/cvat/apps/quality_control/models.py
@@ -196,6 +196,18 @@ def clean(self) -> None:
raise ValidationError(f"Unexpected type value '{self.type}'")
+class PointSizeBase(str, Enum):
+ IMAGE_SIZE = "image_size"
+ GROUP_BBOX_SIZE = "group_bbox_size"
+
+ def __str__(self) -> str:
+ return self.value
+
+ @classmethod
+ def choices(cls):
+ return tuple((x.value, x.name) for x in cls)
+
+
class QualitySettings(models.Model):
task = models.OneToOneField(Task, on_delete=models.CASCADE, related_name="quality_settings")
@@ -205,6 +217,10 @@ class QualitySettings(models.Model):
low_overlap_threshold = models.FloatField()
+ point_size_base = models.CharField(
+ max_length=32, choices=PointSizeBase.choices(), default=PointSizeBase.GROUP_BBOX_SIZE
+ )
+
compare_line_orientation = models.BooleanField()
line_orientation_threshold = models.FloatField()
@@ -218,6 +234,8 @@ class QualitySettings(models.Model):
compare_attributes = models.BooleanField()
+ match_empty_frames = models.BooleanField(default=False)
+
target_metric = models.CharField(
max_length=32,
choices=QualityTargetMetricType.choices(),
diff --git a/cvat/apps/quality_control/quality_reports.py b/cvat/apps/quality_control/quality_reports.py
index 437cdb72615f..f5e527468aa3 100644
--- a/cvat/apps/quality_control/quality_reports.py
+++ b/cvat/apps/quality_control/quality_reports.py
@@ -187,6 +187,9 @@ class ComparisonParameters(_Serializable):
oks_sigma: float = 0.09
"Like IoU threshold, but for points, % of the bbox area to match a pair of points"
+ point_size_base: models.PointSizeBase = models.PointSizeBase.GROUP_BBOX_SIZE
+ "Determines how to obtain the object size for point comparisons"
+
line_thickness: float = 0.01
"Thickness of polylines, relatively to the (image area) ^ 0.5"
@@ -214,6 +217,13 @@ class ComparisonParameters(_Serializable):
panoptic_comparison: bool = True
"Use only the visible part of the masks and polygons in comparisons"
+ match_empty_frames: bool = False
+ """
+ Consider unannotated (empty) frames as matching. If disabled, quality metrics, such as accuracy,
+ will be 0 if both GT and DS frames have no annotations. When enabled, they will be 1 instead.
+ This will also add virtual annotations to empty frames in the comparison results.
+ """
+
def _value_serializer(self, v):
if isinstance(v, dm.AnnotationType):
return str(v.name)
@@ -229,11 +239,11 @@ def from_dict(cls, d: dict):
@define(kw_only=True)
class ConfusionMatrix(_Serializable):
labels: List[str]
- rows: np.array
- precision: np.array
- recall: np.array
- accuracy: np.array
- jaccard_index: Optional[np.array]
+ rows: np.ndarray
+ precision: np.ndarray
+ recall: np.ndarray
+ accuracy: np.ndarray
+ jaccard_index: Optional[np.ndarray]
@property
def axes(self):
@@ -955,6 +965,7 @@ def __init__(
# https://cocodataset.org/#keypoints-eval
# https://github.com/cocodataset/cocoapi/blob/8c9bcc3cf640524c4c20a9c40e89cb6a2f2fa0e9/PythonAPI/pycocotools/cocoeval.py#L523
oks_sigma: float = 0.09,
+ point_size_base: models.PointSizeBase = models.PointSizeBase.GROUP_BBOX_SIZE,
compare_line_orientation: bool = False,
line_torso_radius: float = 0.01,
panoptic_comparison: bool = False,
@@ -968,6 +979,9 @@ def __init__(
self.oks_sigma = oks_sigma
"% of the shape area"
+ self.point_size_base = point_size_base
+ "Compare point groups using the group bbox size or the image size"
+
self.compare_line_orientation = compare_line_orientation
"Whether lines are oriented or not"
@@ -1293,13 +1307,20 @@ def _distance(a: dm.Points, b: dm.Points) -> float:
else:
# Complex case: multiple points, grouped points, points with a bbox
# Try to align points and then return the metric
- # match them in their bbox space
- if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0:
- return 0
+ if self.point_size_base == models.PointSizeBase.IMAGE_SIZE:
+ scale = img_h * img_w
+ elif self.point_size_base == models.PointSizeBase.GROUP_BBOX_SIZE:
+ # match points in their bbox space
- bbox = dm.ops.mean_bbox([a_bbox, b_bbox])
- scale = bbox[2] * bbox[3]
+ if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0:
+ # this early exit may not work for points forming an axis-aligned line
+ return 0
+
+ bbox = dm.ops.mean_bbox([a_bbox, b_bbox])
+ scale = bbox[2] * bbox[3]
+ else:
+ assert False, f"Unknown point size base {self.point_size_base}"
a_points = np.reshape(a.points, (-1, 2))
b_points = np.reshape(b.points, (-1, 2))
@@ -1525,6 +1546,7 @@ def __init__(self, categories: dm.CategoriesInfo, *, settings: ComparisonParamet
panoptic_comparison=settings.panoptic_comparison,
iou_threshold=settings.iou_threshold,
oks_sigma=settings.oks_sigma,
+ point_size_base=settings.point_size_base,
line_torso_radius=settings.line_thickness,
compare_line_orientation=False, # should not be taken from outside, handled differently
)
@@ -1957,8 +1979,18 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
gt_label_idx = label_id_map[gt_ann.label] if gt_ann else self._UNMATCHED_IDX
confusion_matrix[ds_label_idx, gt_label_idx] += 1
+ if self.settings.match_empty_frames and not gt_item.annotations and not ds_item.annotations:
+ # Add virtual annotations for empty frames
+ valid_labels_count = 1
+ total_labels_count = 1
+
+ valid_shapes_count = 1
+ total_shapes_count = 1
+ ds_shapes_count = 1
+ gt_shapes_count = 1
+
self._frame_results[frame_id] = ComparisonReportFrameSummary(
- annotations=self._generate_annotations_summary(
+ annotations=self._generate_frame_annotations_summary(
confusion_matrix, confusion_matrix_labels
),
annotation_components=ComparisonReportAnnotationComponentsSummary(
@@ -2000,9 +2032,8 @@ def _make_zero_confusion_matrix(self) -> Tuple[List[str], np.ndarray, Dict[int,
return label_names, confusion_matrix, label_id_idx_map
- @classmethod
- def _generate_annotations_summary(
- cls, confusion_matrix: np.ndarray, confusion_matrix_labels: List[str]
+ def _compute_annotations_summary(
+ self, confusion_matrix: np.ndarray, confusion_matrix_labels: List[str]
) -> ComparisonReportAnnotationsSummary:
matched_ann_counts = np.diag(confusion_matrix)
ds_ann_counts = np.sum(confusion_matrix, axis=1)
@@ -2022,10 +2053,10 @@ def _generate_annotations_summary(
) / (total_annotations_count or 1)
valid_annotations_count = np.sum(matched_ann_counts)
- missing_annotations_count = np.sum(confusion_matrix[cls._UNMATCHED_IDX, :])
- extra_annotations_count = np.sum(confusion_matrix[:, cls._UNMATCHED_IDX])
- ds_annotations_count = np.sum(ds_ann_counts[: cls._UNMATCHED_IDX])
- gt_annotations_count = np.sum(gt_ann_counts[: cls._UNMATCHED_IDX])
+ missing_annotations_count = np.sum(confusion_matrix[self._UNMATCHED_IDX, :])
+ extra_annotations_count = np.sum(confusion_matrix[:, self._UNMATCHED_IDX])
+ ds_annotations_count = np.sum(ds_ann_counts[: self._UNMATCHED_IDX])
+ gt_annotations_count = np.sum(gt_ann_counts[: self._UNMATCHED_IDX])
return ComparisonReportAnnotationsSummary(
valid_count=valid_annotations_count,
@@ -2044,12 +2075,24 @@ def _generate_annotations_summary(
),
)
- def generate_report(self) -> ComparisonReport:
- self._find_gt_conflicts()
+ def _generate_frame_annotations_summary(
+ self, confusion_matrix: np.ndarray, confusion_matrix_labels: List[str]
+ ) -> ComparisonReportAnnotationsSummary:
+ summary = self._compute_annotations_summary(confusion_matrix, confusion_matrix_labels)
+
+ if self.settings.match_empty_frames and summary.total_count == 0:
+ # Add virtual annotations for empty frames
+ summary.valid_count = 1
+ summary.total_count = 1
+ summary.ds_count = 1
+ summary.gt_count = 1
+ return summary
+
+ def _generate_dataset_annotations_summary(
+ self, frame_summaries: Dict[int, ComparisonReportFrameSummary]
+ ) -> Tuple[ComparisonReportAnnotationsSummary, ComparisonReportAnnotationComponentsSummary]:
# accumulate stats
- intersection_frames = []
- conflicts = []
annotation_components = ComparisonReportAnnotationComponentsSummary(
shape=ComparisonReportAnnotationShapeSummary(
valid_count=0,
@@ -2067,19 +2110,52 @@ def generate_report(self) -> ComparisonReport:
),
)
mean_ious = []
+ empty_frame_count = 0
confusion_matrix_labels, confusion_matrix, _ = self._make_zero_confusion_matrix()
- for frame_id, frame_result in self._frame_results.items():
- intersection_frames.append(frame_id)
- conflicts += frame_result.conflicts
+ for frame_result in frame_summaries.values():
confusion_matrix += frame_result.annotations.confusion_matrix.rows
+ if not np.any(frame_result.annotations.confusion_matrix.rows):
+ empty_frame_count += 1
+
if annotation_components is None:
annotation_components = deepcopy(frame_result.annotation_components)
else:
annotation_components.accumulate(frame_result.annotation_components)
+
mean_ious.append(frame_result.annotation_components.shape.mean_iou)
+ annotation_summary = self._compute_annotations_summary(
+ confusion_matrix, confusion_matrix_labels
+ )
+
+ if self.settings.match_empty_frames and empty_frame_count:
+ # Add virtual annotations for empty frames,
+ # they are not included in the confusion matrix
+ annotation_summary.valid_count += empty_frame_count
+ annotation_summary.total_count += empty_frame_count
+ annotation_summary.ds_count += empty_frame_count
+ annotation_summary.gt_count += empty_frame_count
+
+ # Cannot be computed in accumulate()
+ annotation_components.shape.mean_iou = np.mean(mean_ious)
+
+ return annotation_summary, annotation_components
+
+ def generate_report(self) -> ComparisonReport:
+ self._find_gt_conflicts()
+
+ intersection_frames = []
+ conflicts = []
+ for frame_id, frame_result in self._frame_results.items():
+ intersection_frames.append(frame_id)
+ conflicts += frame_result.conflicts
+
+ annotation_summary, annotations_component_summary = (
+ self._generate_dataset_annotations_summary(self._frame_results)
+ )
+
return ComparisonReport(
parameters=self.settings,
comparison_summary=ComparisonReportComparisonSummary(
@@ -2095,25 +2171,8 @@ def generate_report(self) -> ComparisonReport:
[c for c in conflicts if c.severity == AnnotationConflictSeverity.ERROR]
),
conflicts_by_type=Counter(c.type for c in conflicts),
- annotations=self._generate_annotations_summary(
- confusion_matrix, confusion_matrix_labels
- ),
- annotation_components=ComparisonReportAnnotationComponentsSummary(
- shape=ComparisonReportAnnotationShapeSummary(
- valid_count=annotation_components.shape.valid_count,
- missing_count=annotation_components.shape.missing_count,
- extra_count=annotation_components.shape.extra_count,
- total_count=annotation_components.shape.total_count,
- ds_count=annotation_components.shape.ds_count,
- gt_count=annotation_components.shape.gt_count,
- mean_iou=np.mean(mean_ious),
- ),
- label=ComparisonReportAnnotationLabelSummary(
- valid_count=annotation_components.label.valid_count,
- invalid_count=annotation_components.label.invalid_count,
- total_count=annotation_components.label.total_count,
- ),
- ),
+ annotations=annotation_summary,
+ annotation_components=annotations_component_summary,
),
frame_results=self._frame_results,
)
diff --git a/cvat/apps/quality_control/rules/conflicts.rego b/cvat/apps/quality_control/rules/conflicts.rego
index f8e570b58826..883491128209 100644
--- a/cvat/apps/quality_control/rules/conflicts.rego
+++ b/cvat/apps/quality_control/rules/conflicts.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/quality_control/rules/quality_reports.rego b/cvat/apps/quality_control/rules/quality_reports.rego
index e9dd28b3ec32..98626a5f0ca3 100644
--- a/cvat/apps/quality_control/rules/quality_reports.rego
+++ b/cvat/apps/quality_control/rules/quality_reports.rego
@@ -11,7 +11,7 @@ import data.quality_utils
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/quality_control/rules/quality_settings.rego b/cvat/apps/quality_control/rules/quality_settings.rego
index 1fc587159ee7..0b2f6b149e79 100644
--- a/cvat/apps/quality_control/rules/quality_settings.rego
+++ b/cvat/apps/quality_control/rules/quality_settings.rego
@@ -10,7 +10,7 @@ import data.organizations
# "auth": {
# "user": {
# "id": ,
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# },
# "organization": {
# "id": ,
diff --git a/cvat/apps/quality_control/serializers.py b/cvat/apps/quality_control/serializers.py
index fe6b372d9cb4..6164abc12200 100644
--- a/cvat/apps/quality_control/serializers.py
+++ b/cvat/apps/quality_control/serializers.py
@@ -81,6 +81,7 @@ class Meta:
"max_validations_per_job",
"iou_threshold",
"oks_sigma",
+ "point_size_base",
"line_thickness",
"low_overlap_threshold",
"compare_line_orientation",
@@ -91,6 +92,7 @@ class Meta:
"object_visibility_threshold",
"panoptic_comparison",
"compare_attributes",
+ "match_empty_frames",
)
read_only_fields = (
"id",
@@ -98,6 +100,7 @@ class Meta:
)
extra_kwargs = {k: {"required": False} for k in fields}
+ extra_kwargs.setdefault("match_empty_frames", {}).setdefault("default", False)
for field_name, help_text in {
"target_metric": "The primary metric used for quality estimation",
@@ -115,10 +118,26 @@ class Meta:
""",
"oks_sigma": """
Like IoU threshold, but for points.
- The percent of the bbox area, used as the radius of the circle around the GT point,
- where the checked point is expected to be.
+ The percent of the bbox side, used as the radius of the circle around the GT point,
+ where the checked point is expected to be. For boxes with different width and
+ height, the "side" is computed as a geometric mean of the width and height.
Read more: https://cocodataset.org/#keypoints-eval
""",
+ "point_size_base": """
+ When comparing point annotations (including both separate points and point groups),
+ the OKS sigma parameter defines matching area for each GT point based to the
+ object size. The point size base parameter allows to configure how to determine
+ the object size.
+ If {image_size}, the image size is used. Useful if each point
+ annotation represents a separate object or boxes grouped with points do not
+ represent object boundaries.
+ If {group_bbox_size}, the object size is based on
+ the point group bbox size. Useful if each point group represents an object
+ or there is a bbox grouped with points, representing the object size.
+ """.format(
+ image_size=models.PointSizeBase.IMAGE_SIZE,
+ group_bbox_size=models.PointSizeBase.GROUP_BBOX_SIZE,
+ ),
"line_thickness": """
Thickness of polylines, relatively to the (image area) ^ 0.5.
The distance to the boundary around the GT line,
@@ -147,6 +166,12 @@ class Meta:
Use only the visible part of the masks and polygons in comparisons
""",
"compare_attributes": "Enables or disables annotation attribute comparison",
+ "match_empty_frames": """
+ Count empty frames as matching. This affects target metrics like accuracy in cases
+ there are no annotations. If disabled, frames without annotations
+ are counted as not matching (accuracy is 0). If enabled, accuracy will be 1 instead.
+ This will also add virtual annotations to empty frames in the comparison results.
+ """,
}.items():
extra_kwargs.setdefault(field_name, {}).setdefault(
"help_text", textwrap.dedent(help_text.lstrip("\n"))
diff --git a/cvat/apps/webhooks/rules/tests/generators/webhooks_test.gen.rego.py b/cvat/apps/webhooks/rules/tests/generators/webhooks_test.gen.rego.py
index c367a42cc98b..66417f3d096d 100644
--- a/cvat/apps/webhooks/rules/tests/generators/webhooks_test.gen.rego.py
+++ b/cvat/apps/webhooks/rules/tests/generators/webhooks_test.gen.rego.py
@@ -40,7 +40,7 @@ def read_rules(name):
SCOPES = list({rule["scope"] for rule in simple_rules})
CONTEXTS = ["sandbox", "organization"]
OWNERSHIPS = ["project:owner", "owner", "none"]
-GROUPS = ["admin", "business", "user", "worker", "none"]
+GROUPS = ["admin", "user", "worker", "none"]
ORG_ROLES = ["owner", "maintainer", "supervisor", "worker", None]
SAME_ORG = [True, False]
diff --git a/cvat/apps/webhooks/rules/webhooks.rego b/cvat/apps/webhooks/rules/webhooks.rego
index a74a88c6a965..85d577a21ee7 100644
--- a/cvat/apps/webhooks/rules/webhooks.rego
+++ b/cvat/apps/webhooks/rules/webhooks.rego
@@ -11,7 +11,7 @@ import data.organizations
# "auth": {
# "user": {
# "id":
-# "privilege": <"admin"|"business"|"user"|"worker"> or null
+# "privilege": <"admin"|"user"|"worker"> or null
# }
# "organization": {
# "id": ,
diff --git a/cvat/asgi.py b/cvat/asgi.py
index 44ddd0d87131..2fbe40a8d4c6 100644
--- a/cvat/asgi.py
+++ b/cvat/asgi.py
@@ -24,6 +24,7 @@
if debug.is_debugging_enabled():
+
class DebuggerApp(ASGIHandler):
"""
Support for VS code debugger
diff --git a/cvat/rq_patching.py b/cvat/rq_patching.py
index cd8c1ac74225..a12bcaaaedd3 100644
--- a/cvat/rq_patching.py
+++ b/cvat/rq_patching.py
@@ -32,18 +32,25 @@ def custom_started_job_registry_cleanup(self, timestamp: Optional[float] = None)
job_ids = self.get_expired_job_ids(score)
if job_ids:
- failed_job_registry = rq.registry.FailedJobRegistry(self.name, self.connection, serializer=self.serializer)
+ failed_job_registry = rq.registry.FailedJobRegistry(
+ self.name, self.connection, serializer=self.serializer
+ )
queue = self.get_queue()
with self.connection.pipeline() as pipeline:
for job_id in job_ids:
try:
- job = self.job_class.fetch(job_id, connection=self.connection, serializer=self.serializer)
+ job = self.job_class.fetch(
+ job_id, connection=self.connection, serializer=self.serializer
+ )
except NoSuchJobError:
continue
job.execute_failure_callback(
- self.death_penalty_class, AbandonedJobError, AbandonedJobError(), traceback.extract_stack()
+ self.death_penalty_class,
+ AbandonedJobError,
+ AbandonedJobError(),
+ traceback.extract_stack(),
)
retry = job.retries_left and job.retries_left > 0
@@ -54,8 +61,8 @@ def custom_started_job_registry_cleanup(self, timestamp: Optional[float] = None)
else:
exc_string = f"due to {AbandonedJobError.__name__}"
rq.registry.logger.warning(
- f'{self.__class__.__name__} cleanup: Moving job to {rq.registry.FailedJobRegistry.__name__} '
- f'({exc_string})'
+ f"{self.__class__.__name__} cleanup: Moving job to {rq.registry.FailedJobRegistry.__name__} "
+ f"({exc_string})"
)
job.set_status(JobStatus.FAILED)
job._exc_info = f"Moved to {rq.registry.FailedJobRegistry.__name__}, {exc_string}, at {datetime.now()}"
@@ -69,7 +76,8 @@ def custom_started_job_registry_cleanup(self, timestamp: Optional[float] = None)
return job_ids
+
def update_started_job_registry_cleanup() -> None:
# don't forget to check if the issue https://github.com/rq/rq/issues/2006 has been resolved in upstream
- assert VERSION == '1.16.0'
+ assert VERSION == "1.16.0"
rq.registry.StartedJobRegistry.cleanup = custom_started_job_registry_cleanup
diff --git a/cvat/rqworker.py b/cvat/rqworker.py
index d368a1ef2629..8a3e187b74b0 100644
--- a/cvat/rqworker.py
+++ b/cvat/rqworker.py
@@ -42,12 +42,14 @@ def execute_job(self, *args, **kwargs):
# errors during debugging
# https://stackoverflow.com/questions/8242837/django-multiprocessing-and-database-connections/10684672#10684672
from django import db
+
db.connections.close_all()
return self.perform_job(*args, **kwargs)
if debug.is_debugging_enabled():
+
class RemoteDebugWorker(SimpleWorker):
"""
Support for VS code debugger
@@ -68,6 +70,7 @@ def execute_job(self, *args, **kwargs):
if os.environ.get("COVERAGE_PROCESS_START"):
import coverage
+
default_exit = os._exit
def coverage_exit(*args, **kwargs):
diff --git a/cvat/schema.yml b/cvat/schema.yml
index 3af7944889b4..1938cabc5071 100644
--- a/cvat/schema.yml
+++ b/cvat/schema.yml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: CVAT REST API
- version: 2.21.3
+ version: 2.22.0
description: REST API for Computer Vision Annotation Tool (CVAT)
termsOfService: https://www.google.com/policies/terms/
contact:
@@ -9658,9 +9658,28 @@ components:
format: double
description: |
Like IoU threshold, but for points.
- The percent of the bbox area, used as the radius of the circle around the GT point,
- where the checked point is expected to be.
+ The percent of the bbox side, used as the radius of the circle around the GT point,
+ where the checked point is expected to be. For boxes with different width and
+ height, the "side" is computed as a geometric mean of the width and height.
Read more: https://cocodataset.org/#keypoints-eval
+ point_size_base:
+ allOf:
+ - $ref: '#/components/schemas/PointSizeBaseEnum'
+ description: |-
+ When comparing point annotations (including both separate points and point groups),
+ the OKS sigma parameter defines matching area for each GT point based to the
+ object size. The point size base parameter allows to configure how to determine
+ the object size.
+ If image_size, the image size is used. Useful if each point
+ annotation represents a separate object or boxes grouped with points do not
+ represent object boundaries.
+ If group_bbox_size, the object size is based on
+ the point group bbox size. Useful if each point group represents an object
+ or there is a bbox grouped with points, representing the object size.
+
+
+ * `image_size` - IMAGE_SIZE
+ * `group_bbox_size` - GROUP_BBOX_SIZE
line_thickness:
type: number
format: double
@@ -9710,6 +9729,14 @@ components:
compare_attributes:
type: boolean
description: Enables or disables annotation attribute comparison
+ match_empty_frames:
+ type: boolean
+ default: false
+ description: |
+ Count empty frames as matching. This affects target metrics like accuracy in cases
+ there are no annotations. If disabled, frames without annotations
+ are counted as not matching (accuracy is 0). If enabled, accuracy will be 1 instead.
+ This will also add virtual annotations to empty frames in the comparison results.
PatchedTaskValidationLayoutWriteRequest:
type: object
properties:
@@ -9854,6 +9881,14 @@ components:
- GIT_INTEGRATION
- MODELS
- PREDICT
+ PointSizeBaseEnum:
+ enum:
+ - image_size
+ - group_bbox_size
+ type: string
+ description: |-
+ * `image_size` - IMAGE_SIZE
+ * `group_bbox_size` - GROUP_BBOX_SIZE
ProjectFileRequest:
type: object
properties:
@@ -10138,9 +10173,28 @@ components:
format: double
description: |
Like IoU threshold, but for points.
- The percent of the bbox area, used as the radius of the circle around the GT point,
- where the checked point is expected to be.
+ The percent of the bbox side, used as the radius of the circle around the GT point,
+ where the checked point is expected to be. For boxes with different width and
+ height, the "side" is computed as a geometric mean of the width and height.
Read more: https://cocodataset.org/#keypoints-eval
+ point_size_base:
+ allOf:
+ - $ref: '#/components/schemas/PointSizeBaseEnum'
+ description: |-
+ When comparing point annotations (including both separate points and point groups),
+ the OKS sigma parameter defines matching area for each GT point based to the
+ object size. The point size base parameter allows to configure how to determine
+ the object size.
+ If image_size, the image size is used. Useful if each point
+ annotation represents a separate object or boxes grouped with points do not
+ represent object boundaries.
+ If group_bbox_size, the object size is based on
+ the point group bbox size. Useful if each point group represents an object
+ or there is a bbox grouped with points, representing the object size.
+
+
+ * `image_size` - IMAGE_SIZE
+ * `group_bbox_size` - GROUP_BBOX_SIZE
line_thickness:
type: number
format: double
@@ -10190,6 +10244,14 @@ components:
compare_attributes:
type: boolean
description: Enables or disables annotation attribute comparison
+ match_empty_frames:
+ type: boolean
+ default: false
+ description: |
+ Count empty frames as matching. This affects target metrics like accuracy in cases
+ there are no annotations. If disabled, frames without annotations
+ are counted as not matching (accuracy is 0). If enabled, accuracy will be 1 instead.
+ This will also add virtual annotations to empty frames in the comparison results.
RegisterSerializerEx:
type: object
properties:
diff --git a/cvat/settings/base.py b/cvat/settings/base.py
index f439000f9d21..404628fa555e 100644
--- a/cvat/settings/base.py
+++ b/cvat/settings/base.py
@@ -236,7 +236,7 @@ def generate_secret_key():
IAM_ADMIN_ROLE = 'admin'
# Index in the list below corresponds to the priority (0 has highest priority)
-IAM_ROLES = [IAM_ADMIN_ROLE, 'business', 'user', 'worker']
+IAM_ROLES = [IAM_ADMIN_ROLE, 'user', 'worker']
IAM_OPA_HOST = 'http://opa:8181'
IAM_OPA_DATA_URL = f'{IAM_OPA_HOST}/v1/data'
LOGIN_URL = 'rest_login'
@@ -532,12 +532,6 @@ class CVAT_QUEUES(Enum):
DATA_UPLOAD_MAX_NUMBER_FIELDS = None # this django check disabled
DATA_UPLOAD_MAX_NUMBER_FILES = None
-RESTRICTIONS = {
- # allow access to analytics component to users with business role
- # otherwise, only the administrator has access
- 'analytics_visibility': True,
-}
-
redis_ondisk_host = os.getenv('CVAT_REDIS_ONDISK_HOST', 'localhost')
# The default port is not Redis's default port (6379).
# This is so that a developer can run both in-mem Redis and on-disk Kvrocks on their machine
diff --git a/cvat/urls.py b/cvat/urls.py
index 144ed619f766..08257a14b811 100644
--- a/cvat/urls.py
+++ b/cvat/urls.py
@@ -23,31 +23,31 @@
from django.urls import path, include
urlpatterns = [
- path('admin/', admin.site.urls),
- path('', include('cvat.apps.engine.urls')),
- path('django-rq/', include('django_rq.urls')),
+ path("admin/", admin.site.urls),
+ path("", include("cvat.apps.engine.urls")),
+ path("django-rq/", include("django_rq.urls")),
]
-if apps.is_installed('cvat.apps.log_viewer'):
- urlpatterns.append(path('', include('cvat.apps.log_viewer.urls')))
+if apps.is_installed("cvat.apps.log_viewer"):
+ urlpatterns.append(path("", include("cvat.apps.log_viewer.urls")))
-if apps.is_installed('cvat.apps.events'):
- urlpatterns.append(path('api/', include('cvat.apps.events.urls')))
+if apps.is_installed("cvat.apps.events"):
+ urlpatterns.append(path("api/", include("cvat.apps.events.urls")))
-if apps.is_installed('cvat.apps.lambda_manager'):
- urlpatterns.append(path('', include('cvat.apps.lambda_manager.urls')))
+if apps.is_installed("cvat.apps.lambda_manager"):
+ urlpatterns.append(path("", include("cvat.apps.lambda_manager.urls")))
-if apps.is_installed('cvat.apps.webhooks'):
- urlpatterns.append(path('api/', include('cvat.apps.webhooks.urls')))
+if apps.is_installed("cvat.apps.webhooks"):
+ urlpatterns.append(path("api/", include("cvat.apps.webhooks.urls")))
-if apps.is_installed('cvat.apps.quality_control'):
- urlpatterns.append(path('api/', include('cvat.apps.quality_control.urls')))
+if apps.is_installed("cvat.apps.quality_control"):
+ urlpatterns.append(path("api/", include("cvat.apps.quality_control.urls")))
-if apps.is_installed('silk'):
- urlpatterns.append(path('profiler/', include('silk.urls')))
+if apps.is_installed("silk"):
+ urlpatterns.append(path("profiler/", include("silk.urls")))
-if apps.is_installed('health_check'):
- urlpatterns.append(path('api/server/health/', include('health_check.urls')))
+if apps.is_installed("health_check"):
+ urlpatterns.append(path("api/server/health/", include("health_check.urls")))
-if apps.is_installed('cvat.apps.analytics_report'):
- urlpatterns.append(path('api/', include('cvat.apps.analytics_report.urls')))
+if apps.is_installed("cvat.apps.analytics_report"):
+ urlpatterns.append(path("api/", include("cvat.apps.analytics_report.urls")))
diff --git a/cvat/utils/background_jobs.py b/cvat/utils/background_jobs.py
index caf2e859a530..72c93eaeaf8e 100644
--- a/cvat/utils/background_jobs.py
+++ b/cvat/utils/background_jobs.py
@@ -7,12 +7,9 @@
import django_rq
+
def schedule_job_with_throttling(
- queue_name: str,
- job_id_base: str,
- scheduled_time: datetime,
- func: Callable,
- **func_kwargs
+ queue_name: str, job_id_base: str, scheduled_time: datetime, func: Callable, **func_kwargs
) -> None:
"""
This function schedules an RQ job to run at `scheduled_time`,
diff --git a/cvat/utils/http.py b/cvat/utils/http.py
index b2ed89a5d555..2cb1b7498b32 100644
--- a/cvat/utils/http.py
+++ b/cvat/utils/http.py
@@ -19,11 +19,12 @@
if settings.SMOKESCREEN_ENABLED:
PROXIES_FOR_UNTRUSTED_URLS = {
- 'http': 'http://localhost:4750',
- 'https': 'http://localhost:4750',
+ "http": "http://localhost:4750",
+ "https": "http://localhost:4750",
}
+
def make_requests_session() -> requests.Session:
session = requests.Session()
- session.headers['User-Agent'] = _CVAT_USER_AGENT
+ session.headers["User-Agent"] = _CVAT_USER_AGENT
return session
diff --git a/cvat/utils/remote_debugger.py b/cvat/utils/remote_debugger.py
index b4d01baf3c31..bc6ef40ae0e2 100644
--- a/cvat/utils/remote_debugger.py
+++ b/cvat/utils/remote_debugger.py
@@ -6,7 +6,8 @@
def is_debugging_enabled() -> bool:
- return os.environ.get('CVAT_DEBUG_ENABLED') == 'yes'
+ return os.environ.get("CVAT_DEBUG_ENABLED") == "yes"
+
if is_debugging_enabled():
import debugpy
@@ -21,8 +22,8 @@ class RemoteDebugger:
Read more: https://modwsgi.readthedocs.io/en/develop/user-guides/debugging-techniques.html
"""
- ENV_VAR_PORT = 'CVAT_DEBUG_PORT'
- ENV_VAR_WAIT = 'CVAT_DEBUG_WAIT'
+ ENV_VAR_PORT = "CVAT_DEBUG_PORT"
+ ENV_VAR_WAIT = "CVAT_DEBUG_WAIT"
__debugger_initialized = False
@classmethod
@@ -35,7 +36,7 @@ def _singleton_init(cls):
# The only intended use is in Docker.
# Using 127.0.0.1 will not allow host connections
- addr = ('0.0.0.0', port) # nosec - B104:hardcoded_bind_all_interfaces
+ addr = ("0.0.0.0", port) # nosec - B104:hardcoded_bind_all_interfaces
# Debugpy is a singleton
# We put it in the main thread of the process and then report new threads
@@ -45,7 +46,7 @@ def _singleton_init(cls):
# Feel free to enable if needed.
debugpy.configure({"subProcess": False})
- if os.environ.get(cls.ENV_VAR_WAIT) == 'yes':
+ if os.environ.get(cls.ENV_VAR_WAIT) == "yes":
debugpy.wait_for_client()
except Exception as ex:
raise Exception("failed to set debugger") from ex
diff --git a/cvat/utils/version.py b/cvat/utils/version.py
index ecc79eea7051..8b1b53a10384 100644
--- a/cvat/utils/version.py
+++ b/cvat/utils/version.py
@@ -11,6 +11,7 @@
import os
import subprocess
+
def get_version(version):
"""Return a PEP 440-compliant version number from VERSION."""
# Now build the two parts of the version number:
@@ -20,21 +21,23 @@ def get_version(version):
main = get_main_version(version)
- sub = ''
- if version[3] == 'alpha' and version[4] == 0:
+ sub = ""
+ if version[3] == "alpha" and version[4] == 0:
git_changeset = get_git_changeset()
if git_changeset:
- sub = '.dev%s' % git_changeset
+ sub = ".dev%s" % git_changeset
- elif version[3] != 'final':
- mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
+ elif version[3] != "final":
+ mapping = {"alpha": "a", "beta": "b", "rc": "rc"}
sub = mapping[version[3]] + str(version[4])
return main + sub
+
def get_main_version(version):
"""Return main version (X.Y.Z) from VERSION."""
- return '.'.join(str(x) for x in version[:3])
+ return ".".join(str(x) for x in version[:3])
+
def get_git_changeset():
"""Return a numeric identifier of the latest git changeset.
@@ -44,14 +47,16 @@ def get_git_changeset():
so it's sufficient for generating the development version numbers.
"""
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- git_log = subprocess.Popen( # nosec: B603, B607
- ['git', 'log', '--pretty=format:%ct', '--quiet', '-1', 'HEAD'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- cwd=repo_dir, universal_newlines=True,
+ git_log = subprocess.Popen( # nosec: B603, B607
+ ["git", "log", "--pretty=format:%ct", "--quiet", "-1", "HEAD"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=repo_dir,
+ universal_newlines=True,
)
timestamp = git_log.communicate()[0]
try:
timestamp = datetime.datetime.fromtimestamp(int(timestamp), tz=datetime.timezone.utc)
except ValueError:
return None
- return timestamp.strftime('%Y%m%d%H%M%S')
+ return timestamp.strftime("%Y%m%d%H%M%S")
diff --git a/dev/check_changelog_fragments.py b/dev/check_changelog_fragments.py
index d417bcd669f5..e837842efaf0 100755
--- a/dev/check_changelog_fragments.py
+++ b/dev/check_changelog_fragments.py
@@ -6,17 +6,18 @@
REPO_ROOT = Path(__file__).resolve().parents[1]
+
def main():
scriv_config = configparser.ConfigParser()
- scriv_config.read(REPO_ROOT / 'changelog.d/scriv.ini')
+ scriv_config.read(REPO_ROOT / "changelog.d/scriv.ini")
- scriv_section = scriv_config['scriv']
- assert scriv_section['format'] == 'md'
+ scriv_section = scriv_config["scriv"]
+ assert scriv_section["format"] == "md"
- md_header_level = int(scriv_section['md_header_level'])
- md_header_prefix = '#' * md_header_level + '# '
+ md_header_level = int(scriv_section["md_header_level"])
+ md_header_prefix = "#" * md_header_level + "# "
- categories = {s.strip() for s in scriv_section['categories'].split(',')}
+ categories = {s.strip() for s in scriv_section["categories"].split(",")}
success = True
@@ -25,12 +26,12 @@ def complain(message):
success = False
print(f"{fragment_path.relative_to(REPO_ROOT)}:{line_index+1}: {message}", file=sys.stderr)
- for fragment_path in REPO_ROOT.glob('changelog.d/*.md'):
+ for fragment_path in REPO_ROOT.glob("changelog.d/*.md"):
with open(fragment_path) as fragment_file:
for line_index, line in enumerate(fragment_file):
if not line.startswith(md_header_prefix):
# The first line should be a header, and all headers should be of appropriate level.
- if line_index == 0 or line.startswith('#'):
+ if line_index == 0 or line.startswith("#"):
complain(f"line should start with {md_header_prefix!r}")
continue
@@ -40,4 +41,5 @@ def complain(message):
sys.exit(0 if success else 1)
+
main()
diff --git a/dev/update_version.py b/dev/update_version.py
index 6cdaf313f968..ed8d08a40f42 100755
--- a/dev/update_version.py
+++ b/dev/update_version.py
@@ -9,40 +9,43 @@
from typing import Callable, Match, Pattern
-SUCCESS_CHAR = '\u2714'
-FAIL_CHAR = '\u2716'
+SUCCESS_CHAR = "\u2714"
+FAIL_CHAR = "\u2716"
-CVAT_VERSION_PATTERN = re.compile(r'VERSION\s*=\s*\((\d+),\s*(\d*),\s*(\d+),\s*[\',\"](\w+)[\',\"],\s*(\d+)\)')
+CVAT_VERSION_PATTERN = re.compile(
+ r"VERSION\s*=\s*\((\d+),\s*(\d*),\s*(\d+),\s*[\',\"](\w+)[\',\"],\s*(\d+)\)"
+)
REPO_ROOT_DIR = Path(__file__).resolve().parents[1]
-CVAT_INIT_PY_REL_PATH = 'cvat/__init__.py'
+CVAT_INIT_PY_REL_PATH = "cvat/__init__.py"
CVAT_INIT_PY_PATH = REPO_ROOT_DIR / CVAT_INIT_PY_REL_PATH
+
@dataclass()
class Version:
major: int = 0
minor: int = 0
patch: int = 0
- prerelease: str = ''
+ prerelease: str = ""
prerelease_number: int = 0
def __str__(self) -> str:
- return f'{self.major}.{self.minor}.{self.patch}-{self.prerelease}.{self.prerelease_number}'
+ return f"{self.major}.{self.minor}.{self.patch}-{self.prerelease}.{self.prerelease_number}"
def cvat_repr(self):
- return f"({self.major}, {self.minor}, {self.patch}, '{self.prerelease}', {self.prerelease_number})"
+ return f'({self.major}, {self.minor}, {self.patch}, "{self.prerelease}", {self.prerelease_number})'
def compose_repr(self):
- if self.prerelease != 'final':
- return 'dev'
- return f'v{self.major}.{self.minor}.{self.patch}'
+ if self.prerelease != "final":
+ return "dev"
+ return f"v{self.major}.{self.minor}.{self.patch}"
def increment_prerelease_number(self) -> None:
self.prerelease_number += 1
def increment_prerelease(self) -> None:
- flow = ('alpha', 'beta', 'rc', 'final')
+ flow = ("alpha", "beta", "rc", "final")
idx = flow.index(self.prerelease)
if idx == len(flow) - 1:
raise ValueError(f"Cannot increment current '{self.prerelease}' prerelease version")
@@ -51,9 +54,9 @@ def increment_prerelease(self) -> None:
self._set_default_prerelease_number()
def set_prerelease(self, value: str) -> None:
- values = ('alpha', 'beta', 'rc', 'final')
+ values = ("alpha", "beta", "rc", "final")
if value not in values:
- raise ValueError(f'{value} is a wrong, must be one of {values}')
+ raise ValueError(f"{value} is a wrong, must be one of {values}")
self.prerelease = value
self._set_default_prerelease_number()
@@ -71,15 +74,15 @@ def increment_major(self) -> None:
self._set_default_minor()
def set(self, v: str) -> None:
- self.major, self.minor, self.patch = map(int, v.split('.'))
- self.prerelease = 'final'
+ self.major, self.minor, self.patch = map(int, v.split("."))
+ self.prerelease = "final"
self.prerelease_number = 0
def _set_default_prerelease_number(self) -> None:
self.prerelease_number = 0
def _set_default_prerelease(self) -> None:
- self.prerelease = 'alpha'
+ self.prerelease = "alpha"
self._set_default_prerelease_number()
def _set_default_patch(self) -> None:
@@ -90,6 +93,7 @@ def _set_default_minor(self) -> None:
self.minor = 0
self._set_default_patch()
+
@dataclass(frozen=True)
class ReplacementRule:
rel_path: str
@@ -101,89 +105,113 @@ def apply(self, new_version: Version, *, verify_only: bool) -> bool:
text = path.read_text()
new_text, num_replacements = self.pattern.subn(
- functools.partial(self.replacement, new_version), text)
+ functools.partial(self.replacement, new_version), text
+ )
if not num_replacements:
- print(f'{FAIL_CHAR} {self.rel_path}: failed to match version pattern.')
+ print(f"{FAIL_CHAR} {self.rel_path}: failed to match version pattern.")
return False
if text == new_text:
if verify_only:
- print(f'{SUCCESS_CHAR} {self.rel_path}: verified.')
+ print(f"{SUCCESS_CHAR} {self.rel_path}: verified.")
else:
- print(f'{SUCCESS_CHAR} {self.rel_path}: no need to update.')
+ print(f"{SUCCESS_CHAR} {self.rel_path}: no need to update.")
else:
if verify_only:
- print(f'{FAIL_CHAR} {self.rel_path}: verification failed.')
+ print(f"{FAIL_CHAR} {self.rel_path}: verification failed.")
return False
else:
path.write_text(new_text)
- print(f'{SUCCESS_CHAR} {self.rel_path}: updated.')
+ print(f"{SUCCESS_CHAR} {self.rel_path}: updated.")
return True
-REPLACEMENT_RULES = [
- ReplacementRule(CVAT_INIT_PY_REL_PATH, CVAT_VERSION_PATTERN,
- lambda v, m: f'VERSION = {v.cvat_repr()}'),
-
- ReplacementRule('docker-compose.yml',
- re.compile(r'(\$\{CVAT_VERSION:-)([\w.]+)(\})'),
- lambda v, m: m[1] + v.compose_repr() + m[3]),
-
- ReplacementRule('helm-chart/values.yaml',
- re.compile(r'(^ image: cvat/(?:ui|server)\n tag: )([\w.]+)', re.M),
- lambda v, m: m[1] + v.compose_repr()),
- ReplacementRule('cvat-sdk/gen/generate.sh',
+REPLACEMENT_RULES = [
+ ReplacementRule(
+ CVAT_INIT_PY_REL_PATH, CVAT_VERSION_PATTERN, lambda v, m: f"VERSION = {v.cvat_repr()}"
+ ),
+ ReplacementRule(
+ "docker-compose.yml",
+ re.compile(r"(\$\{CVAT_VERSION:-)([\w.]+)(\})"),
+ lambda v, m: m[1] + v.compose_repr() + m[3],
+ ),
+ ReplacementRule(
+ "helm-chart/values.yaml",
+ re.compile(r"(^ image: cvat/(?:ui|server)\n tag: )([\w.]+)", re.M),
+ lambda v, m: m[1] + v.compose_repr(),
+ ),
+ ReplacementRule(
+ "cvat-sdk/gen/generate.sh",
re.compile(r'^VERSION="[\d.]+"$', re.M),
- lambda v, m: f'VERSION="{v.major}.{v.minor}.{v.patch}"'),
-
- ReplacementRule('cvat/schema.yml',
+ lambda v, m: f'VERSION="{v.major}.{v.minor}.{v.patch}"',
+ ),
+ ReplacementRule(
+ "cvat/schema.yml",
re.compile(r"^ version: [\d.]+$", re.M),
- lambda v, m: f' version: {v.major}.{v.minor}.{v.patch}'),
-
- ReplacementRule('cvat-cli/src/cvat_cli/version.py',
+ lambda v, m: f" version: {v.major}.{v.minor}.{v.patch}",
+ ),
+ ReplacementRule(
+ "cvat-cli/src/cvat_cli/version.py",
re.compile(r'^VERSION = "[\d.]+"$', re.M),
- lambda v, m: f'VERSION = "{v.major}.{v.minor}.{v.patch}"'),
-
- ReplacementRule('cvat-cli/requirements/base.txt',
- re.compile(r'^cvat-sdk~=[\d.]+$', re.M),
- lambda v, m: f'cvat-sdk~={v.major}.{v.minor}.{v.patch}'),
+ lambda v, m: f'VERSION = "{v.major}.{v.minor}.{v.patch}"',
+ ),
+ ReplacementRule(
+ "cvat-cli/requirements/base.txt",
+ re.compile(r"^cvat-sdk~=[\d.]+$", re.M),
+ lambda v, m: f"cvat-sdk~={v.major}.{v.minor}.{v.patch}",
+ ),
]
+
def get_current_version() -> Version:
version_text = CVAT_INIT_PY_PATH.read_text()
match = re.search(CVAT_VERSION_PATTERN, version_text)
if not match:
- raise RuntimeError(f'Failed to find version in {CVAT_INIT_PY_PATH}')
+ raise RuntimeError(f"Failed to find version in {CVAT_INIT_PY_PATH}")
return Version(int(match[1]), int(match[2]), int(match[3]), match[4], int(match[5]))
+
def main() -> None:
- parser = argparse.ArgumentParser(description='Bump CVAT version')
+ parser = argparse.ArgumentParser(description="Bump CVAT version")
action_group = parser.add_mutually_exclusive_group(required=True)
- action_group.add_argument('--major', action='store_true',
- help='Increment the existing major version by 1')
- action_group.add_argument('--minor', action='store_true',
- help='Increment the existing minor version by 1')
- action_group.add_argument('--patch', action='store_true',
- help='Increment the existing patch version by 1')
- action_group.add_argument('--prerelease', nargs='?', const='increment',
- help='''Increment prerelease version alpha->beta->rc->final,
- Also it's possible to pass value explicitly''')
- action_group.add_argument('--prerelease_number', action='store_true',
- help='Increment prerelease number by 1')
-
- action_group.add_argument('--current', '--show-current',
- action='store_true', help='Display current version')
- action_group.add_argument('--verify-current',
- action='store_true', help='Check that all version numbers are consistent')
-
- action_group.add_argument('--set', metavar='X.Y.Z',
- help='Set the version to the specified version')
+ action_group.add_argument(
+ "--major", action="store_true", help="Increment the existing major version by 1"
+ )
+ action_group.add_argument(
+ "--minor", action="store_true", help="Increment the existing minor version by 1"
+ )
+ action_group.add_argument(
+ "--patch", action="store_true", help="Increment the existing patch version by 1"
+ )
+ action_group.add_argument(
+ "--prerelease",
+ nargs="?",
+ const="increment",
+ help="""Increment prerelease version alpha->beta->rc->final,
+ Also it's possible to pass value explicitly""",
+ )
+ action_group.add_argument(
+ "--prerelease_number", action="store_true", help="Increment prerelease number by 1"
+ )
+
+ action_group.add_argument(
+ "--current", "--show-current", action="store_true", help="Display current version"
+ )
+ action_group.add_argument(
+ "--verify-current",
+ action="store_true",
+ help="Check that all version numbers are consistent",
+ )
+
+ action_group.add_argument(
+ "--set", metavar="X.Y.Z", help="Set the version to the specified version"
+ )
args = parser.parse_args()
@@ -201,7 +229,7 @@ def main() -> None:
version.increment_prerelease_number()
elif args.prerelease:
- if args.prerelease == 'increment':
+ if args.prerelease == "increment":
version.increment_prerelease()
else:
version.set_prerelease(args.prerelease)
@@ -222,9 +250,9 @@ def main() -> None:
assert False, "Unreachable code"
if verify_only:
- print(f'Verifying that version is {version}...')
+ print(f"Verifying that version is {version}...")
else:
- print(f'Bumping version to {version}...')
+ print(f"Bumping version to {version}...")
print()
success = True
@@ -239,5 +267,6 @@ def main() -> None:
else:
sys.exit("\nFailed to update one or more files!")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/docker-compose.yml b/docker-compose.yml
index b51e38fce7d5..bec741c5536f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -79,7 +79,7 @@ services:
cvat_server:
container_name: cvat_server
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on:
<<: *backend-deps
@@ -113,7 +113,7 @@ services:
cvat_utils:
container_name: cvat_utils
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -130,7 +130,7 @@ services:
cvat_worker_import:
container_name: cvat_worker_import
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -146,7 +146,7 @@ services:
cvat_worker_export:
container_name: cvat_worker_export
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -162,7 +162,7 @@ services:
cvat_worker_annotation:
container_name: cvat_worker_annotation
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -178,7 +178,7 @@ services:
cvat_worker_webhooks:
container_name: cvat_worker_webhooks
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -194,7 +194,7 @@ services:
cvat_worker_quality_reports:
container_name: cvat_worker_quality_reports
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -210,7 +210,7 @@ services:
cvat_worker_analytics_reports:
container_name: cvat_worker_analytics_reports
- image: cvat/server:${CVAT_VERSION:-v2.21.3}
+ image: cvat/server:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on: *backend-deps
environment:
@@ -226,7 +226,7 @@ services:
cvat_ui:
container_name: cvat_ui
- image: cvat/ui:${CVAT_VERSION:-v2.21.3}
+ image: cvat/ui:${CVAT_VERSION:-v2.22.0}
restart: always
depends_on:
- cvat_server
diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml
index d0906952f96d..b99625f1a104 100644
--- a/helm-chart/values.yaml
+++ b/helm-chart/values.yaml
@@ -129,7 +129,7 @@ cvat:
additionalVolumeMounts: []
replicas: 1
image: cvat/server
- tag: v2.21.3
+ tag: v2.22.0
imagePullPolicy: Always
permissionFix:
enabled: true
@@ -153,7 +153,7 @@ cvat:
frontend:
replicas: 1
image: cvat/ui
- tag: v2.21.3
+ tag: v2.22.0
imagePullPolicy: Always
labels: {}
# test: test
diff --git a/pyproject.toml b/pyproject.toml
index 6d0772451578..528bdc579fcc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,3 +7,14 @@ skip_gitignore = true # align tool behavior with Black
[tool.black]
line-length = 100
target-version = ['py39']
+extend-exclude = """
+# TODO: get rid of these
+^/cvat/apps/(
+ dataset_manager|dataset_repo|engine|events
+ |health|iam|lambda_manager|log_viewer
+ |organizations|webhooks
+)/
+| ^/cvat/settings/
+| ^/serverless/
+| ^/utils/dataset_manifest/
+"""
diff --git a/rqscheduler.py b/rqscheduler.py
index 82b7499baf89..5ae76e64a7f0 100644
--- a/rqscheduler.py
+++ b/rqscheduler.py
@@ -9,5 +9,5 @@
from rq_scheduler.scripts import rqscheduler
-if __name__ == '__main__':
+if __name__ == "__main__":
rqscheduler.main()
diff --git a/site/build_docs.py b/site/build_docs.py
index 25af0b0e8f82..2eca3a941330 100755
--- a/site/build_docs.py
+++ b/site/build_docs.py
@@ -157,9 +157,7 @@ def validate_env():
try:
subprocess.run([hugo, "version"], capture_output=True) # nosec
except (subprocess.CalledProcessError, FileNotFoundError) as ex:
- raise Exception(
- f"Failed to run '{hugo}', please make sure it exists."
- ) from ex
+ raise Exception(f"Failed to run '{hugo}', please make sure it exists.") from ex
if __name__ == "__main__":
diff --git a/site/content/en/docs/administration/advanced/ldap.md b/site/content/en/docs/administration/advanced/ldap.md
index c1b6be282f26..c57824d13fa3 100644
--- a/site/content/en/docs/administration/advanced/ldap.md
+++ b/site/content/en/docs/administration/advanced/ldap.md
@@ -100,9 +100,6 @@ AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
AUTH_LDAP_ADMIN_GROUPS = [
'CN=CVAT Admins,%s' % _BASE_DN,
]
-AUTH_LDAP_BUSINESS_GROUPS = [
- 'CN=CVAT Managers,%s' % _BASE_DN,
-]
AUTH_LDAP_WORKER_GROUPS = [
'CN=CVAT Workers,%s' % _BASE_DN,
]
@@ -112,7 +109,6 @@ AUTH_LDAP_USER_GROUPS = [
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
- "business": AUTH_LDAP_BUSINESS_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"worker": AUTH_LDAP_WORKER_GROUPS,
}
@@ -181,9 +177,6 @@ AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
AUTH_LDAP_ADMIN_GROUPS = [
'CN=cvat_admins,CN=Groups,%s' % _BASE_DN,
]
-AUTH_LDAP_BUSINESS_GROUPS = [
- 'CN=cvat_managers,CN=Groups,%s' % _BASE_DN,
-]
AUTH_LDAP_WORKER_GROUPS = [
'CN=cvat_workers,CN=Groups,%s' % _BASE_DN,
]
@@ -193,7 +186,6 @@ AUTH_LDAP_USER_GROUPS = [
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
- "business": AUTH_LDAP_BUSINESS_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"worker": AUTH_LDAP_WORKER_GROUPS,
}
diff --git a/site/content/en/docs/administration/basics/admin-account.md b/site/content/en/docs/administration/basics/admin-account.md
index 08182f80a22b..bb72a99af89b 100644
--- a/site/content/en/docs/administration/basics/admin-account.md
+++ b/site/content/en/docs/administration/basics/admin-account.md
@@ -11,7 +11,7 @@ The user you register by default does not have full permissions on the instance,
so you must create a superuser.
The superuser can use [Django administration panel](http://localhost:8080/admin)
to assign groups (roles) to other users.
-
Available roles are: user (default), admin, business, worker.
+
Available roles are: user (default), admin, worker.
### Prerequisites
diff --git a/site/content/en/docs/manual/advanced/annotation-with-brush-tool.md b/site/content/en/docs/manual/advanced/annotation-with-brush-tool.md
index 8f672c1b892a..bc135b40273d 100644
--- a/site/content/en/docs/manual/advanced/annotation-with-brush-tool.md
+++ b/site/content/en/docs/manual/advanced/annotation-with-brush-tool.md
@@ -42,6 +42,7 @@ It has the following elements:
| ![Brush size](/images/brushing_tools_brush_size.png) | **Brush size** in pixels.
**Note:** Visible only when **Brush** or **Eraser** are selected. |
| ![Brush shape](/images/brushing_tools_brush_shape.png) | **Brush shape** with two options: circle and square.
**Note:** Visible only when **Brush** or **Eraser** are selected. |
| ![Pixel remove](/images/brushing_tools_pixels.png) | **Remove underlying pixels**. When you are drawing or editing a mask with this tool,
pixels on other masks that are located at the same positions as the pixels of the
current mask are deleted. |
+| ![Hide mask](/images/brushing_tools_hide.png) | **Hide mask**. When drawing or editing a mask, you can enable this feature to temporarily hide the mask, allowing you to see the objects underneath more clearly. |
| ![Label](/images/brushing_tools_label_drop.png) | **Label** that will be assigned to the newly created mask | |
| ![Move](/images/brushing_tools_brush_move.png) | **Move**. Click and hold to move the menu bar to the other place on the screen |
diff --git a/site/content/en/images/brushing_tool_menu.png b/site/content/en/images/brushing_tool_menu.png
index f5d6726d1d17..418f58f6ba36 100644
Binary files a/site/content/en/images/brushing_tool_menu.png and b/site/content/en/images/brushing_tool_menu.png differ
diff --git a/site/content/en/images/brushing_tools_hide.png b/site/content/en/images/brushing_tools_hide.png
new file mode 100644
index 000000000000..e9d7ba5552bd
Binary files /dev/null and b/site/content/en/images/brushing_tools_hide.png differ
diff --git a/site/process_sdk_docs.py b/site/process_sdk_docs.py
index 3b1941248410..03324aea691b 100755
--- a/site/process_sdk_docs.py
+++ b/site/process_sdk_docs.py
@@ -25,9 +25,7 @@ def __init__(self, *, input_dir: str, site_root: str) -> None:
self._site_root = site_root
self._content_dir = osp.join(self._site_root, "content")
- self._sdk_reference_dir = osp.join(
- self._content_dir, "en/docs/api_sdk/sdk/reference"
- )
+ self._sdk_reference_dir = osp.join(self._content_dir, "en/docs/api_sdk/sdk/reference")
self._templates_dir = osp.join(self._site_root, "templates")
@staticmethod
@@ -97,9 +95,7 @@ def _move_api_summary(self):
apis_index_filename = osp.join(
osp.relpath(self._sdk_reference_dir, self._content_dir), "apis/_index.md"
)
- apis_index_path = osp.join(
- self._templates_dir, apis_index_filename + ".template"
- )
+ apis_index_path = osp.join(self._templates_dir, apis_index_filename + ".template")
with open(apis_index_path) as f:
contents = f.read()
@@ -126,9 +122,7 @@ def _fix_page_links_and_references(self):
os.rename(src_path, dst_path)
mapping[src_filename] = dst_filename
- self._reference_files = [
- osp.join(self._sdk_reference_dir, p) for p in mapping.values()
- ]
+ self._reference_files = [osp.join(self._sdk_reference_dir, p) for p in mapping.values()]
for p in iglob(self._sdk_reference_dir + "/**/*.md", recursive=True):
with open(p) as f:
@@ -146,9 +140,7 @@ def _fix_page_links_and_references(self):
with open(p, "w") as f:
f.write(contents)
- def _process_non_code_blocks(
- self, text: str, handlers: List[Callable[[str], str]]
- ) -> str:
+ def _process_non_code_blocks(self, text: str, handlers: List[Callable[[str], str]]) -> str:
"""
Allows to process Markdown documents with passed callbacks. Callbacks are only
executed outside code blocks.
diff --git a/supervisord/utils.conf b/supervisord/utils.conf
index 1271e6eef536..dc7030023c35 100644
--- a/supervisord/utils.conf
+++ b/supervisord/utils.conf
@@ -26,19 +26,10 @@ environment=VECTOR_EVENT_HANDLER="SynchronousLogstashHandler"
numprocs=1
autorestart=true
-[program:rqworker-notifications]
+[program:rqworker]
command=%(ENV_HOME)s/wait_for_deps.sh
- python3 %(ENV_HOME)s/manage.py rqworker -v 3 notifications
+ python3 %(ENV_HOME)s/manage.py rqworker -v 3 notifications cleaning
--worker-class cvat.rqworker.DefaultWorker
-environment=VECTOR_EVENT_HANDLER="SynchronousLogstashHandler",CVAT_POSTGRES_APPLICATION_NAME="cvat:worker:notifications"
+environment=VECTOR_EVENT_HANDLER="SynchronousLogstashHandler",CVAT_POSTGRES_APPLICATION_NAME="cvat:worker:notifications+cleaning"
numprocs=%(ENV_NUMPROCS)s
autorestart=true
-
-[program:rqworker-cleaning]
-command=%(ENV_HOME)s/wait_for_deps.sh
- python3 %(ENV_HOME)s/manage.py rqworker -v 3 cleaning
- --worker-class cvat.rqworker.DefaultWorker
-environment=VECTOR_EVENT_HANDLER="SynchronousLogstashHandler",CVAT_POSTGRES_APPLICATION_NAME="cvat:worker:cleaning"
-numprocs=%(ENV_NUMPROCS)s
-process_name=%(program_name)s-%(process_num)d
-autorestart=true
diff --git a/tests/cypress/e2e/actions_objects/case_99_save_filtered_object_in_AAM.js b/tests/cypress/e2e/actions_objects/case_99_save_filtered_object_in_AAM.js
index 6f72aaf6219f..2e80ff356b5c 100644
--- a/tests/cypress/e2e/actions_objects/case_99_save_filtered_object_in_AAM.js
+++ b/tests/cypress/e2e/actions_objects/case_99_save_filtered_object_in_AAM.js
@@ -40,7 +40,7 @@ context('Save filtered object in AAM.', () => {
});
describe(`Testing case "${caseId}"`, () => {
- it(`Set filter label == “${labelName}”.`, () => {
+ it(`Set filter label == "${labelName}".`, () => {
cy.addFiltersRule(0);
cy.setFilter({
groupIndex: 0,
diff --git a/tests/cypress/e2e/actions_objects2/case_16_z_order_features.js b/tests/cypress/e2e/actions_objects2/case_16_z_order_features.js
index cb39d1bcd6c9..745af4157ba5 100644
--- a/tests/cypress/e2e/actions_objects2/case_16_z_order_features.js
+++ b/tests/cypress/e2e/actions_objects2/case_16_z_order_features.js
@@ -63,7 +63,7 @@ context('Actions on polygon', () => {
cy.get('.cvat-canvas-container').click();
});
- it('Second shape is over the first shape', () => {
+ it('Second shape is over the first shape', () => {
// The larger the index of an element in the array the closer it is to us
cy.get('.cvat_canvas_shape').then(($canvasShape) => {
expect(Number($canvasShape[1].id.match(/\d+$/))).to.be.equal(2);
@@ -76,7 +76,7 @@ context('Actions on polygon', () => {
cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated');
});
- it('First shape is over the second shape', () => {
+ it('First shape is over the second shape', () => {
// The larger the index of an element in the array the closer it is to us
cy.get('.cvat_canvas_shape').then(($canvasShape) => {
expect(Number($canvasShape[1].id.match(/\d+$/))).to.be.equal(1);
diff --git a/tests/cypress/e2e/actions_objects2/case_17_lock_hide_features.js b/tests/cypress/e2e/actions_objects2/case_17_lock_hide_features.js
index 855d452bbddc..97fcf850b984 100644
--- a/tests/cypress/e2e/actions_objects2/case_17_lock_hide_features.js
+++ b/tests/cypress/e2e/actions_objects2/case_17_lock_hide_features.js
@@ -218,7 +218,7 @@ context('Lock/hide features.', () => {
cy.contains('Labels').click();
});
});
- it('Repeat hide/lock for one of the labels. Objects with other labels weren’t affected.', () => {
+ it("Repeat hide/lock for one of the labels. Objects with other labels weren't affected.", () => {
const objectsSameLabel = ['cvat_canvas_shape_1', 'cvat_canvas_shape_2', 'cvat_canvas_shape_3'];
cy.get('.cvat-objects-sidebar-labels-list').within(() => {
// Hide and lock all object with "Main task" label (#cvat_canvas_shape_1-3).
diff --git a/tests/cypress/e2e/actions_tasks3/case_44_changing_default_value_for_attribute.js b/tests/cypress/e2e/actions_tasks3/case_44_changing_default_value_for_attribute.js
index fd641be141c0..48227db05641 100644
--- a/tests/cypress/e2e/actions_tasks3/case_44_changing_default_value_for_attribute.js
+++ b/tests/cypress/e2e/actions_tasks3/case_44_changing_default_value_for_attribute.js
@@ -33,7 +33,7 @@ context('Changing a default value for an attribute.', () => {
});
describe(`Testing case "${caseId}", issue 2968`, () => {
- it('Add a label, add text (leave it’s value empty by default) & checkbox attributes.', () => {
+ it('Add a label, add text (leave its value empty by default) & checkbox attributes.', () => {
cy.intercept('PATCH', '/api/tasks/**').as('patchTask');
cy.addNewLabel({ name: additionalLabel }, additionalAttrsLabel);
cy.wait('@patchTask').its('response.statusCode').should('equal', 200);
diff --git a/tests/cypress/e2e/features/masks_basics.js b/tests/cypress/e2e/features/masks_basics.js
index 3e119e97f039..ac7a1358e231 100644
--- a/tests/cypress/e2e/features/masks_basics.js
+++ b/tests/cypress/e2e/features/masks_basics.js
@@ -226,6 +226,59 @@ context('Manipulations with masks', { scrollBehavior: false }, () => {
cy.get('body').type('n');
cy.get('.cvat-brush-tools-toolbox').should('not.be.visible');
});
+
+ it('Check hide mask feature', () => {
+ function checkHideFeature() {
+ cy.get('.cvat-brush-tools-hide').click();
+ cy.get('.cvat-brush-tools-hide').should('have.class', 'cvat-brush-tools-active-tool');
+ cy.get('.cvat_masks_canvas_wrapper').should('not.be.visible');
+ cy.get('.cvat-brush-tools-hide').click();
+ cy.get('.cvat_masks_canvas_wrapper').should('be.visible');
+ }
+
+ function checkHideShortcut() {
+ cy.get('body').type('h');
+ cy.get('.cvat-brush-tools-hide').should('have.class', 'cvat-brush-tools-active-tool');
+ cy.get('.cvat_masks_canvas_wrapper').should('not.be.visible');
+ }
+
+ function checkObjectIsHidden() {
+ cy.get('#cvat-objects-sidebar-state-item-1').within(() => {
+ cy.get('.cvat-object-item-button-hidden-enabled').should('exist');
+ });
+ }
+
+ const mask = [{
+ method: 'brush',
+ coordinates: [[450, 250], [600, 400]],
+ }];
+ const drawPolygon = [{
+ method: 'polygon-plus',
+ coordinates: [[450, 210], [650, 400], [450, 600], [260, 400]],
+ }];
+ cy.startMaskDrawing();
+ cy.drawMask(mask);
+
+ checkHideFeature();
+ checkHideShortcut();
+
+ cy.finishMaskDrawing();
+ cy.get('#cvat_canvas_shape_1').should('be.visible');
+
+ cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Edit');
+ checkHideFeature();
+
+ cy.drawMask(drawPolygon);
+ checkHideShortcut();
+ cy.get('.cvat_canvas_shape_drawing')
+ .invoke('attr', 'fill-opacity')
+ .then((opacity) => expect(+opacity).to.be.equal(0));
+ checkObjectIsHidden();
+ cy.get('.cvat-brush-tools-brush').click();
+ cy.get('.cvat-brush-tools-brush').should('have.class', 'cvat-brush-tools-active-tool');
+ cy.finishMaskDrawing();
+ checkObjectIsHidden();
+ });
});
describe('Tests to make sure that empty masks cannot be created', () => {
diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js
index 91322e9c3695..d3988c2e56a0 100644
--- a/tests/cypress/support/commands.js
+++ b/tests/cypress/support/commands.js
@@ -1565,6 +1565,7 @@ Cypress.Commands.add('startMaskDrawing', () => {
Cypress.Commands.add('finishMaskDrawing', () => {
cy.get('.cvat-brush-tools-brush').click();
cy.get('.cvat-brush-tools-finish').click();
+ cy.hideTooltips();
});
Cypress.Commands.add('sliceShape', (
diff --git a/tests/python/cli/example_function.py b/tests/python/cli/example_function.py
index 4b1b41857825..57d67a5b40a2 100644
--- a/tests/python/cli/example_function.py
+++ b/tests/python/cli/example_function.py
@@ -2,8 +2,6 @@
#
# SPDX-License-Identifier: MIT
-from typing import List
-
import cvat_sdk.auto_annotation as cvataa
import cvat_sdk.models as models
import PIL.Image
@@ -17,7 +15,7 @@
def detect(
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
-) -> List[models.LabeledShapeRequest]:
+) -> list[models.LabeledShapeRequest]:
return [
cvataa.rectangle(0, [1, 2, 3, 4]),
]
diff --git a/tests/python/cli/example_parameterized_function.py b/tests/python/cli/example_parameterized_function.py
index 29d9038e78b4..e46d40867a4c 100644
--- a/tests/python/cli/example_parameterized_function.py
+++ b/tests/python/cli/example_parameterized_function.py
@@ -3,7 +3,6 @@
# SPDX-License-Identifier: MIT
from types import SimpleNamespace as namespace
-from typing import List
import cvat_sdk.auto_annotation as cvataa
import cvat_sdk.models as models
@@ -24,7 +23,7 @@ def create(s: str, i: int, f: float, b: bool) -> cvataa.DetectionFunction:
def detect(
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
- ) -> List[models.LabeledShapeRequest]:
+ ) -> list[models.LabeledShapeRequest]:
return [
cvataa.rectangle(0, [1, 2, 3, 4]),
]
diff --git a/tests/python/cli/util.py b/tests/python/cli/util.py
index ff1173fa4a8d..d399e695dab8 100644
--- a/tests/python/cli/util.py
+++ b/tests/python/cli/util.py
@@ -8,8 +8,9 @@
import ssl
import threading
import unittest
+from collections.abc import Generator
from pathlib import Path
-from typing import Any, Dict, Generator, List, Union
+from typing import Any, Union
import requests
@@ -28,7 +29,7 @@ def run_cli(test: Union[unittest.TestCase, Any], *args: str, expected_code: int
assert expected_code == main(args)
-def generate_images(dst_dir: Path, count: int) -> List[Path]:
+def generate_images(dst_dir: Path, count: int) -> list[Path]:
filenames = []
dst_dir.mkdir(parents=True, exist_ok=True)
for i in range(count):
@@ -70,7 +71,7 @@ def do_POST(self):
response = requests.post(data=self.rfile.read(body_length), **self._shared_request_args())
self._translate_response(response)
- def _shared_request_args(self) -> Dict[str, Any]:
+ def _shared_request_args(self) -> dict[str, Any]:
headers = {k.lower(): v for k, v in self.headers.items()}
del headers["host"]
diff --git a/tests/python/rest_api/test_analytics.py b/tests/python/rest_api/test_analytics.py
index f14cdd206f84..68671889a21c 100644
--- a/tests/python/rest_api/test_analytics.py
+++ b/tests/python/rest_api/test_analytics.py
@@ -38,7 +38,6 @@ def _test_cannot_see(self, user):
"conditions, is_allow",
[
(dict(privilege="admin"), True),
- (dict(privilege="business"), True),
(dict(privilege="worker", has_analytics_access=False), False),
(dict(privilege="worker", has_analytics_access=True), True),
(dict(privilege="user", has_analytics_access=False), False),
diff --git a/tests/python/rest_api/test_analytics_reports.py b/tests/python/rest_api/test_analytics_reports.py
index a50c053fc138..bb48b19e2dd5 100644
--- a/tests/python/rest_api/test_analytics_reports.py
+++ b/tests/python/rest_api/test_analytics_reports.py
@@ -4,7 +4,7 @@
import json
from http import HTTPStatus
-from typing import Any, Dict, Optional
+from typing import Any, Optional
import pytest
from cvat_sdk.api_client import models
@@ -67,7 +67,7 @@ def _test_get_report_200(
job_id: Optional[int] = None,
task_id: Optional[int] = None,
project_id: Optional[int] = None,
- expected_data: Optional[Dict[str, Any]] = None,
+ expected_data: Optional[dict[str, Any]] = None,
**kwargs,
):
params = self._get_query_params(job_id=job_id, task_id=task_id, project_id=project_id)
diff --git a/tests/python/rest_api/test_cloud_storages.py b/tests/python/rest_api/test_cloud_storages.py
index 9fc1739b9e0f..ce2db93cab56 100644
--- a/tests/python/rest_api/test_cloud_storages.py
+++ b/tests/python/rest_api/test_cloud_storages.py
@@ -58,7 +58,6 @@ def _test_cannot_see(self, user, storage_id):
"group, is_owner, is_allow",
[
("admin", False, True),
- ("business", False, False),
("user", True, True),
],
)
@@ -302,7 +301,6 @@ def _test_cannot_update(self, user, storage_id, spec):
"group, is_owner, is_allow",
[
("admin", False, True),
- ("business", False, False),
("worker", True, True),
],
)
@@ -387,7 +385,6 @@ def _test_cannot_see(self, user, storage_id):
"group, is_owner, is_allow",
[
("admin", False, True),
- ("business", False, False),
("user", True, True),
],
)
diff --git a/tests/python/rest_api/test_issues.py b/tests/python/rest_api/test_issues.py
index c6c043f2e449..f1cbfdafacd2 100644
--- a/tests/python/rest_api/test_issues.py
+++ b/tests/python/rest_api/test_issues.py
@@ -6,7 +6,7 @@
import json
from copy import deepcopy
from http import HTTPStatus
-from typing import Any, Dict, List, Tuple
+from typing import Any
import pytest
from cvat_sdk import models
@@ -55,8 +55,6 @@ def _test_check_response(self, user, data, is_allow, **kwargs):
[
("admin", True, True),
("admin", False, True),
- ("business", True, True),
- ("business", False, False),
("worker", True, True),
("worker", False, False),
("user", True, True),
@@ -185,8 +183,6 @@ def get_data(issue_id, *, username: str = None):
[
("admin", True, None, True),
("admin", False, None, True),
- ("business", True, None, True),
- ("business", False, None, False),
("user", True, None, True),
("user", False, None, False),
("worker", False, True, True),
@@ -275,8 +271,6 @@ def _test_check_response(self, user, issue_id, expect_success, **kwargs):
[
("admin", True, None, True),
("admin", False, None, True),
- ("business", True, None, True),
- ("business", False, None, False),
("user", True, None, True),
("user", False, None, False),
("worker", False, True, True),
@@ -373,7 +367,7 @@ def setup(self, restore_db_per_class, admin_user, comments, issues):
def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
return api_client.comments_api.list_endpoint
- def _get_field_samples(self, field: str) -> Tuple[Any, List[Dict[str, Any]]]:
+ def _get_field_samples(self, field: str) -> tuple[Any, list[dict[str, Any]]]:
if field == "job_id":
issue_id, issue_comments = super()._get_field_samples("issue_id")
issue = next((s for s in self.sample_issues if s["id"] == issue_id))
diff --git a/tests/python/rest_api/test_jobs.py b/tests/python/rest_api/test_jobs.py
index 6d5626fcda99..5057f652030c 100644
--- a/tests/python/rest_api/test_jobs.py
+++ b/tests/python/rest_api/test_jobs.py
@@ -15,7 +15,7 @@
from http import HTTPStatus
from io import BytesIO
from itertools import groupby, product
-from typing import Any, Dict, List, Optional, Set, Tuple, Union
+from typing import Any, Optional, Union
import numpy as np
import pytest
@@ -71,7 +71,7 @@ def filter_jobs(jobs, tasks, org):
@pytest.mark.usefixtures("restore_db_per_function")
class TestPostJobs:
- def _test_create_job_ok(self, user: str, data: Dict[str, Any], **kwargs):
+ def _test_create_job_ok(self, user: str, data: dict[str, Any], **kwargs):
with make_api_client(user) as api_client:
(_, response) = api_client.jobs_api.create(
models.JobWriteRequest(**deepcopy(data)), **kwargs
@@ -80,7 +80,7 @@ def _test_create_job_ok(self, user: str, data: Dict[str, Any], **kwargs):
return response
def _test_create_job_fails(
- self, user: str, data: Dict[str, Any], *, expected_status: int, **kwargs
+ self, user: str, data: dict[str, Any], *, expected_status: int, **kwargs
):
with make_api_client(user) as api_client:
(_, response) = api_client.jobs_api.create(
@@ -110,7 +110,7 @@ def test_can_create_gt_job_in_a_task(
tasks,
task_mode: str,
frame_selection_method: str,
- method_params: Set[str],
+ method_params: set[str],
):
required_task_size = 15
@@ -544,7 +544,7 @@ def test_destroy_gt_job_in_org_task(
@pytest.mark.usefixtures("restore_db_per_class")
class TestGetJobs:
def _test_get_job_200(
- self, user, jid, *, expected_data: Optional[Dict[str, Any]] = None, **kwargs
+ self, user, jid, *, expected_data: Optional[dict[str, Any]] = None, **kwargs
):
with make_api_client(user) as client:
(_, response) = client.jobs_api.retrieve(jid, **kwargs)
@@ -568,7 +568,7 @@ def test_admin_can_get_org_job(self, admin_user, jobs, tasks):
job = next(job for job in jobs if tasks[job["task_id"]]["organization"] is not None)
self._test_get_job_200(admin_user, job["id"], expected_data=job)
- @pytest.mark.parametrize("groups", [["business"], ["user"]])
+ @pytest.mark.parametrize("groups", [["user"]])
def test_non_admin_org_staff_can_get_job(
self, groups, users, organizations, org_staff, jobs_by_org
):
@@ -581,7 +581,7 @@ def test_non_admin_org_staff_can_get_job(
job = jobs_by_org[org_id][0]
self._test_get_job_200(user["username"], job["id"], expected_data=job)
- @pytest.mark.parametrize("groups", [["business"], ["user"], ["worker"]])
+ @pytest.mark.parametrize("groups", [["user"], ["worker"]])
def test_non_admin_job_staff_can_get_job(self, groups, users, jobs, is_job_staff):
user, job = next(
(user, job)
@@ -591,7 +591,7 @@ def test_non_admin_job_staff_can_get_job(self, groups, users, jobs, is_job_staff
)
self._test_get_job_200(user["username"], job["id"], expected_data=job)
- @pytest.mark.parametrize("groups", [["business"], ["user"], ["worker"]])
+ @pytest.mark.parametrize("groups", [["user"], ["worker"]])
def test_non_admin_non_job_staff_non_org_staff_cannot_get_job(
self, groups, users, organizations, org_staff, jobs, is_job_staff
):
@@ -955,7 +955,7 @@ def test_admin_list_jobs(self, jobs, tasks, org):
self._test_list_jobs_200("admin1", jobs, **kwargs)
@pytest.mark.parametrize("org_id", ["", None, 1, 2])
- @pytest.mark.parametrize("groups", [["business"], ["user"], ["worker"], []])
+ @pytest.mark.parametrize("groups", [["user"], ["worker"], []])
def test_non_admin_list_jobs(
self, org_id, groups, users, jobs, tasks, projects, org_staff, is_org_member
):
@@ -1024,8 +1024,6 @@ def _test_get_job_annotations_403(self, user, jid):
[
(["admin"], True, True),
(["admin"], False, True),
- (["business"], True, True),
- (["business"], False, False),
(["worker"], True, True),
(["worker"], False, False),
(["user"], True, True),
@@ -1093,7 +1091,7 @@ def test_member_get_job_annotations(
@pytest.mark.parametrize("org", [1])
@pytest.mark.parametrize(
"privilege, expect_success",
- [("admin", True), ("business", False), ("worker", False), ("user", False)],
+ [("admin", True), ("worker", False), ("user", False)],
)
def test_non_member_get_job_annotations(
self,
@@ -1191,7 +1189,7 @@ def test_member_update_job_annotations(
@pytest.mark.parametrize("org", [2])
@pytest.mark.parametrize(
"privilege, expect_success",
- [("admin", True), ("business", False), ("worker", False), ("user", False)],
+ [("admin", True), ("worker", False), ("user", False)],
)
def test_non_member_update_job_annotations(
self,
@@ -1218,8 +1216,6 @@ def test_non_member_update_job_annotations(
[
("admin", True, True),
("admin", False, True),
- ("business", True, True),
- ("business", False, False),
("worker", True, True),
("worker", False, False),
("user", True, True),
@@ -1446,7 +1442,7 @@ def _test_export_dataset(
username: str,
jid: int,
*,
- api_version: Union[int, Tuple[int]],
+ api_version: Union[int, tuple[int]],
local_download: bool = True,
**kwargs,
) -> Optional[bytes]:
@@ -1477,9 +1473,9 @@ def _test_export_annotations(
def test_can_export_dataset_locally_and_to_cloud_with_both_api_versions(
self,
admin_user: str,
- jobs_with_shapes: List,
+ jobs_with_shapes: list,
filter_tasks,
- api_version: Tuple[int],
+ api_version: tuple[int],
local_download: bool,
):
filter_ = "target_storage__location"
@@ -1651,15 +1647,6 @@ def test_admin_get_org_job_preview(self, jobs, tasks):
job_id = next(job["id"] for job in jobs if tasks[job["task_id"]]["organization"])
self._test_get_job_preview_200("admin2", job_id)
- def test_business_can_get_job_preview_in_sandbox(self, find_users, jobs, is_job_staff):
- username, job_id = next(
- (user["username"], job["id"])
- for user in find_users(privilege="business")
- for job in jobs
- if is_job_staff(user["id"], job["id"])
- )
- self._test_get_job_preview_200(username, job_id)
-
def test_user_can_get_job_preview_in_sandbox(self, find_users, jobs, is_job_staff):
username, job_id = next(
(user["username"], job["id"])
@@ -1669,15 +1656,6 @@ def test_user_can_get_job_preview_in_sandbox(self, find_users, jobs, is_job_staf
)
self._test_get_job_preview_200(username, job_id)
- def test_business_cannot_get_job_preview_in_sandbox(self, find_users, jobs, is_job_staff):
- username, job_id = next(
- (user["username"], job["id"])
- for user in find_users(privilege="business")
- for job in jobs
- if not is_job_staff(user["id"], job["id"])
- )
- self._test_get_job_preview_403(username, job_id)
-
def test_user_cannot_get_job_preview_in_sandbox(self, find_users, jobs, is_job_staff):
username, job_id = next(
(user["username"], job["id"])
diff --git a/tests/python/rest_api/test_labels.py b/tests/python/rest_api/test_labels.py
index d64133e7dd36..00cfd6225647 100644
--- a/tests/python/rest_api/test_labels.py
+++ b/tests/python/rest_api/test_labels.py
@@ -7,7 +7,7 @@
from copy import deepcopy
from http import HTTPStatus
from types import SimpleNamespace
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Optional
import pytest
from cvat_sdk import exceptions, models
@@ -60,7 +60,7 @@ def setup(self, _base_setup):
"""
@staticmethod
- def _labels_by_source(labels: List[Dict], *, source_key: str) -> Dict[int, List[Dict]]:
+ def _labels_by_source(labels: list[dict], *, source_key: str) -> dict[int, list[dict]]:
labels_by_source = {}
for label in labels:
label_source = label.get(source_key)
@@ -216,7 +216,7 @@ def setup(self, restore_db_per_class, admin_user, labels, jobs_wlc, tasks_wlc, p
def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
return api_client.labels_api.list_endpoint
- def _get_field_samples(self, field: str) -> Tuple[Any, List[Dict[str, Any]]]:
+ def _get_field_samples(self, field: str) -> tuple[Any, list[dict[str, Any]]]:
if field == "parent":
parent_id, gt_objects = self._get_field_samples("parent_id")
parent_name = self._get_field(
@@ -584,8 +584,8 @@ def _test_update_denied(self, user, lid, data, expected_status=HTTPStatus.FORBID
return response
def _get_patch_data(
- self, original_data: Dict[str, Any], **overrides
- ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ self, original_data: dict[str, Any], **overrides
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
result = deepcopy(original_data)
result.update(overrides)
diff --git a/tests/python/rest_api/test_memberships.py b/tests/python/rest_api/test_memberships.py
index e03cac2e2779..a25074af4890 100644
--- a/tests/python/rest_api/test_memberships.py
+++ b/tests/python/rest_api/test_memberships.py
@@ -4,7 +4,7 @@
# SPDX-License-Identifier: MIT
from http import HTTPStatus
-from typing import ClassVar, List
+from typing import ClassVar
import pytest
from cvat_sdk.api_client.api_client import ApiClient, Endpoint
@@ -40,7 +40,7 @@ def test_can_filter_by_org_id(self, field_value, query_value, memberships):
)
def test_non_admin_can_see_only_self_memberships(self, memberships):
- non_admins = ["business1", "user1", "dummy1", "worker2"]
+ non_admins = ["user1", "dummy1", "worker2"]
for username in non_admins:
data = [obj for obj in memberships if obj["user"]["username"] == username]
self._test_can_see_memberships(username, data)
@@ -80,7 +80,7 @@ def test_can_use_simple_filter_for_object_list(self, field):
@pytest.mark.usefixtures("restore_db_per_function")
class TestPatchMemberships:
_ORG: ClassVar[int] = 1
- ROLES: ClassVar[List[str]] = ["worker", "supervisor", "maintainer", "owner"]
+ ROLES: ClassVar[list[str]] = ["worker", "supervisor", "maintainer", "owner"]
def _test_can_change_membership(self, user, membership_id, new_role):
response = patch_method(
diff --git a/tests/python/rest_api/test_organizations.py b/tests/python/rest_api/test_organizations.py
index 5daee9e53537..50834b1fff83 100644
--- a/tests/python/rest_api/test_organizations.py
+++ b/tests/python/rest_api/test_organizations.py
@@ -31,7 +31,6 @@ class TestMetadataOrganizations:
[
("admin", None, None),
("user", None, False),
- ("business", None, False),
("worker", None, False),
(None, "owner", True),
(None, "maintainer", True),
@@ -79,7 +78,6 @@ class TestGetOrganizations:
[
("admin", None, None, True),
("user", None, False, False),
- ("business", None, False, False),
("worker", None, False, False),
(None, "owner", True, True),
(None, "maintainer", True, True),
@@ -182,7 +180,6 @@ def expected_data(self, organizations, request_data):
[
("admin", None, None, True),
("user", None, False, False),
- ("business", None, False, False),
("worker", None, False, False),
(None, "owner", True, True),
(None, "maintainer", True, True),
@@ -239,7 +236,6 @@ class TestDeleteOrganizations:
(None, "worker", True, False),
(None, "supervisor", True, False),
("user", None, False, False),
- ("business", None, False, False),
("worker", None, False, False),
],
)
diff --git a/tests/python/rest_api/test_projects.py b/tests/python/rest_api/test_projects.py
index b0c8a3b247c4..abfccd5f6b03 100644
--- a/tests/python/rest_api/test_projects.py
+++ b/tests/python/rest_api/test_projects.py
@@ -16,7 +16,7 @@
from itertools import product
from operator import itemgetter
from time import sleep
-from typing import Dict, List, Optional, Tuple, Union
+from typing import Optional, Union
import pytest
from cvat_sdk.api_client import ApiClient, Configuration, models
@@ -34,8 +34,14 @@
patch_method,
post_method,
)
+from shared.utils.helpers import generate_image_files
-from .utils import CollectionSimpleFilterTestBase, export_project_backup, export_project_dataset
+from .utils import (
+ CollectionSimpleFilterTestBase,
+ create_task,
+ export_project_backup,
+ export_project_dataset,
+)
@pytest.mark.usefixtures("restore_db_per_class")
@@ -447,7 +453,7 @@ def test_if_worker_cannot_create_project(self, find_users):
spec = {"name": f"test {username} tries to create a project"}
self._test_create_project_403(username, spec)
- @pytest.mark.parametrize("privilege", ("admin", "business", "user"))
+ @pytest.mark.parametrize("privilege", ("admin", "user"))
def test_if_user_can_create_project(self, find_users, privilege):
privileged_users = find_users(privilege=privilege)
assert len(privileged_users)
@@ -498,7 +504,7 @@ def _create_user(cls, api_client: ApiClient, email: str) -> str:
return json.loads(response.data)
@classmethod
- def _create_org(cls, api_client: ApiClient, members: Optional[Dict[str, str]] = None) -> str:
+ def _create_org(cls, api_client: ApiClient, members: Optional[dict[str, str]] = None) -> str:
with api_client:
(_, response) = api_client.organizations_api.create(
models.OrganizationWriteRequest(slug="test_org_roles"), _parse_response=False
@@ -611,6 +617,7 @@ def _check_cvat_for_video_project_annotations_meta(content, values_to_be_checked
@pytest.mark.usefixtures("restore_db_per_function")
@pytest.mark.usefixtures("restore_redis_inmem_per_function")
+@pytest.mark.usefixtures("restore_redis_ondisk_per_function")
class TestImportExportDatasetProject:
@pytest.fixture(autouse=True)
@@ -622,7 +629,7 @@ def _test_export_dataset(
username: str,
pid: int,
*,
- api_version: Union[int, Tuple[int]],
+ api_version: Union[int, tuple[int]],
local_download: bool = True,
**kwargs,
) -> Optional[bytes]:
@@ -771,7 +778,7 @@ def test_can_import_export_dataset_with_some_format(self, format_name: str, api_
"local_download", (True, pytest.param(False, marks=pytest.mark.with_external_services))
)
def test_can_export_dataset_locally_and_to_cloud_with_both_api_versions(
- self, admin_user: str, filter_projects, api_version: Tuple[int], local_download: bool
+ self, admin_user: str, filter_projects, api_version: tuple[int], local_download: bool
):
filter_ = "target_storage__location"
if local_download:
@@ -1038,10 +1045,65 @@ def test_creates_subfolders_for_subsets_on_export(
len([f for f in zip_file.namelist() if f.startswith(folder_prefix)]) > 0
), f"No {folder_prefix} in {zip_file.namelist()}"
+ def test_export_project_with_honeypots(
+ self,
+ admin_user: str,
+ ):
+ project_spec = {
+ "name": "Project with honeypots",
+ "labels": [{"name": "cat"}],
+ }
+
+ with make_api_client(admin_user) as api_client:
+ project, _ = api_client.projects_api.create(project_spec)
+
+ image_files = generate_image_files(3)
+ image_names = [i.name for i in image_files]
+
+ task_params = {
+ "name": "Task with honeypots",
+ "segment_size": 1,
+ "project_id": project.id,
+ }
+
+ data_params = {
+ "image_quality": 70,
+ "client_files": image_files,
+ "sorting_method": "random",
+ "validation_params": {
+ "mode": "gt_pool",
+ "frame_selection_method": "manual",
+ "frames_per_job_count": 1,
+ "frames": [image_files[-1].name],
+ },
+ }
+
+ create_task(admin_user, spec=task_params, data=data_params)
+
+ dataset = export_project_dataset(
+ admin_user, api_version=2, save_images=True, id=project.id, format="COCO 1.0"
+ )
+
+ with zipfile.ZipFile(io.BytesIO(dataset)) as zip_file:
+ subset_path = "images/default"
+ assert (
+ sorted(
+ [
+ f[len(subset_path) + 1 :]
+ for f in zip_file.namelist()
+ if f.startswith(subset_path)
+ ]
+ )
+ == image_names
+ )
+ with zip_file.open("annotations/instances_default.json") as anno_file:
+ annotations = json.load(anno_file)
+ assert sorted([a["file_name"] for a in annotations["images"]]) == image_names
+
@pytest.mark.usefixtures("restore_db_per_function")
class TestPatchProjectLabel:
- def _get_project_labels(self, pid, user, **kwargs) -> List[models.Label]:
+ def _get_project_labels(self, pid, user, **kwargs) -> list[models.Label]:
kwargs.setdefault("return_json", True)
with make_api_client(user) as api_client:
return get_paginated_collection(
diff --git a/tests/python/rest_api/test_quality_control.py b/tests/python/rest_api/test_quality_control.py
index 1886a0a62ac1..d03675c9156e 100644
--- a/tests/python/rest_api/test_quality_control.py
+++ b/tests/python/rest_api/test_quality_control.py
@@ -3,11 +3,12 @@
# SPDX-License-Identifier: MIT
import json
+from collections.abc import Iterable
from copy import deepcopy
from functools import partial
from http import HTTPStatus
from itertools import groupby
-from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
+from typing import Any, Callable, Optional
import pytest
from cvat_sdk.api_client import exceptions, models
@@ -84,7 +85,7 @@ def create_gt_job(self, user, task_id):
def find_sandbox_task(self, tasks, jobs, users, is_task_staff):
def _find(
is_staff: bool, *, has_gt_jobs: Optional[bool] = None
- ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
task = next(
t
for t in tasks
@@ -116,7 +117,7 @@ def find_sandbox_task_without_gt(self, find_sandbox_task):
def find_org_task(self, tasks, jobs, users, is_org_member, is_task_staff):
def _find(
is_staff: bool, user_org_role: str, *, has_gt_jobs: Optional[bool] = None
- ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
for user in users:
if user["is_superuser"]:
continue
@@ -249,7 +250,7 @@ def test_user_list_reports_in_org_task(
@pytest.mark.usefixtures("restore_db_per_class")
class TestGetQualityReports(_PermissionTestBase):
def _test_get_report_200(
- self, user: str, obj_id: int, *, expected_data: Optional[Dict[str, Any]] = None, **kwargs
+ self, user: str, obj_id: int, *, expected_data: Optional[dict[str, Any]] = None, **kwargs
):
with make_api_client(user) as api_client:
(_, response) = api_client.quality_api.retrieve_report(obj_id, **kwargs)
@@ -308,7 +309,7 @@ def test_user_get_report_in_org_task(
@pytest.mark.usefixtures("restore_db_per_class")
class TestGetQualityReportData(_PermissionTestBase):
def _test_get_report_data_200(
- self, user: str, obj_id: int, *, expected_data: Optional[Dict[str, Any]] = None, **kwargs
+ self, user: str, obj_id: int, *, expected_data: Optional[dict[str, Any]] = None, **kwargs
):
with make_api_client(user) as api_client:
(_, response) = api_client.quality_api.retrieve_report_data(obj_id, **kwargs)
@@ -603,7 +604,7 @@ def _test_check_status_of_report_creation_by_non_rq_job_owner(
def test_non_rq_job_owner_cannot_check_status_of_report_creation_in_sandbox(
self,
- find_sandbox_task_without_gt: Callable[[bool], Tuple[Dict[str, Any], Dict[str, Any]]],
+ find_sandbox_task_without_gt: Callable[[bool], tuple[dict[str, Any], dict[str, Any]]],
admin_user: str,
users: Iterable,
):
@@ -630,8 +631,8 @@ def test_non_rq_job_owner_cannot_check_status_of_report_creation_in_org(
self,
role: str,
admin_user: str,
- find_org_task_without_gt: Callable[[bool, str], Tuple[Dict[str, Any], Dict[str, Any]]],
- find_users: Callable[..., List[Dict[str, Any]]],
+ find_org_task_without_gt: Callable[[bool, str], tuple[dict[str, Any], dict[str, Any]]],
+ find_users: Callable[..., list[dict[str, Any]]],
):
task, task_staff = find_org_task_without_gt(is_staff=True, user_org_role="supervisor")
@@ -657,8 +658,8 @@ def test_admin_can_check_status_of_report_creation(
is_sandbox: bool,
users: Iterable,
admin_user: str,
- find_org_task_without_gt: Callable[[bool, str], Tuple[Dict[str, Any], Dict[str, Any]]],
- find_sandbox_task_without_gt: Callable[[bool], Tuple[Dict[str, Any], Dict[str, Any]]],
+ find_org_task_without_gt: Callable[[bool, str], tuple[dict[str, Any], dict[str, Any]]],
+ find_sandbox_task_without_gt: Callable[[bool], tuple[dict[str, Any], dict[str, Any]]],
):
if is_sandbox:
task, task_staff = find_sandbox_task_without_gt(is_staff=True)
@@ -696,7 +697,7 @@ def setup(self, restore_db_per_class, admin_user, quality_reports, jobs, tasks):
def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
return api_client.quality_api.list_reports_endpoint
- def _get_field_samples(self, field: str) -> Tuple[Any, List[Dict[str, Any]]]:
+ def _get_field_samples(self, field: str) -> tuple[Any, list[dict[str, Any]]]:
if field == "task_id":
# This filter includes both the task and nested job reports
task_id, task_reports = super()._get_field_samples(field)
@@ -819,7 +820,7 @@ def setup(
def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
return api_client.quality_api.list_conflicts_endpoint
- def _get_field_samples(self, field: str) -> Tuple[Any, List[Dict[str, Any]]]:
+ def _get_field_samples(self, field: str) -> tuple[Any, list[dict[str, Any]]]:
if field == "job_id":
# This field is not included in the response
job_id = self._find_valid_field_value(self.report_samples, field_path=["job_id"])
@@ -889,7 +890,7 @@ def test_can_use_simple_filter_for_object_list(self, field):
@pytest.mark.usefixtures("restore_db_per_class")
class TestListSettings(_PermissionTestBase):
def _test_list_settings_200(
- self, user: str, task_id: int, *, expected_data: Optional[Dict[str, Any]] = None, **kwargs
+ self, user: str, task_id: int, *, expected_data: Optional[dict[str, Any]] = None, **kwargs
):
with make_api_client(user) as api_client:
actual = get_paginated_collection(
@@ -951,7 +952,7 @@ def test_user_list_settings_in_org_task(
@pytest.mark.usefixtures("restore_db_per_class")
class TestGetSettings(_PermissionTestBase):
def _test_get_settings_200(
- self, user: str, obj_id: int, *, expected_data: Optional[Dict[str, Any]] = None, **kwargs
+ self, user: str, obj_id: int, *, expected_data: Optional[dict[str, Any]] = None, **kwargs
):
with make_api_client(user) as api_client:
(_, response) = api_client.quality_api.retrieve_settings(obj_id, **kwargs)
@@ -1016,9 +1017,9 @@ def _test_patch_settings_200(
self,
user: str,
obj_id: int,
- data: Dict[str, Any],
+ data: dict[str, Any],
*,
- expected_data: Optional[Dict[str, Any]] = None,
+ expected_data: Optional[dict[str, Any]] = None,
**kwargs,
):
with make_api_client(user) as api_client:
@@ -1032,7 +1033,7 @@ def _test_patch_settings_200(
return response
- def _test_patch_settings_403(self, user: str, obj_id: int, data: Dict[str, Any], **kwargs):
+ def _test_patch_settings_403(self, user: str, obj_id: int, data: dict[str, Any], **kwargs):
with make_api_client(user) as api_client:
(_, response) = api_client.quality_api.partial_update_settings(
obj_id,
@@ -1045,7 +1046,7 @@ def _test_patch_settings_403(self, user: str, obj_id: int, data: Dict[str, Any],
return response
- def _get_request_data(self, data: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ def _get_request_data(self, data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
patched_data = deepcopy(data)
for field, value in data.items():
@@ -1211,6 +1212,8 @@ def test_modified_task_produces_different_metrics(
"oks_sigma",
"compare_line_orientation",
"panoptic_comparison",
+ "point_size_base",
+ "match_empty_frames",
],
)
def test_settings_affect_metrics(
@@ -1228,6 +1231,12 @@ def test_settings_affect_metrics(
settings[parameter] = 1 - settings[parameter]
if parameter == "group_match_threshold":
settings[parameter] = 0.9
+ elif parameter == "point_size_base":
+ settings[parameter] = next(
+ v
+ for v in models.PointSizeBaseEnum.allowed_values[("value",)].values()
+ if v != settings[parameter]
+ )
else:
assert False
@@ -1237,7 +1246,12 @@ def test_settings_affect_metrics(
)
new_report = self.create_quality_report(admin_user, task_id)
- assert new_report["summary"]["conflict_count"] != old_report["summary"]["conflict_count"]
+ if parameter == "match_empty_frames":
+ assert new_report["summary"]["valid_count"] != old_report["summary"]["valid_count"]
+ else:
+ assert (
+ new_report["summary"]["conflict_count"] != old_report["summary"]["conflict_count"]
+ )
def test_old_report_can_be_loaded(self, admin_user, quality_reports):
report = min((r for r in quality_reports if r["task_id"]), key=lambda r: r["id"])
diff --git a/tests/python/rest_api/test_remote_url.py b/tests/python/rest_api/test_remote_url.py
index cc50a3284d58..c230aa3a9584 100644
--- a/tests/python/rest_api/test_remote_url.py
+++ b/tests/python/rest_api/test_remote_url.py
@@ -5,7 +5,7 @@
from http import HTTPStatus
from time import sleep
-from typing import Any, Dict
+from typing import Any
import pytest
@@ -21,7 +21,7 @@ def _post_task_remote_data(username, task_id, resources):
return post_method(username, f"tasks/{task_id}/data", data)
-def _wait_until_task_is_created(username: str, rq_id: str) -> Dict[str, Any]:
+def _wait_until_task_is_created(username: str, rq_id: str) -> dict[str, Any]:
url = f"requests/{rq_id}"
for _ in range(100):
diff --git a/tests/python/rest_api/test_requests.py b/tests/python/rest_api/test_requests.py
index f06e97ae7fba..a6f808f73056 100644
--- a/tests/python/rest_api/test_requests.py
+++ b/tests/python/rest_api/test_requests.py
@@ -4,7 +4,6 @@
import io
from http import HTTPStatus
-from typing import List
from urllib.parse import urlparse
import pytest
@@ -29,6 +28,7 @@
@pytest.mark.usefixtures("restore_db_per_class")
@pytest.mark.usefixtures("restore_redis_inmem_per_function")
+@pytest.mark.usefixtures("restore_redis_ondisk_per_function")
@pytest.mark.timeout(30)
class TestRequestsListFilters(CollectionSimpleFilterTestBase):
@@ -87,7 +87,7 @@ def fxt_make_requests(
fxt_make_export_job_requests,
fxt_download_file,
):
- def _make_requests(project_ids: List[int], task_ids: List[int], job_ids: List[int]):
+ def _make_requests(project_ids: list[int], task_ids: list[int], job_ids: list[int]):
# make requests to export projects|tasks|jobs annotations|datasets|backups
fxt_make_export_project_requests(project_ids[1:])
fxt_make_export_task_requests(task_ids[1:])
@@ -161,7 +161,7 @@ def download_file(resource: str, rid: int, subresource: str):
@pytest.fixture
def fxt_make_export_project_requests(self):
- def make_requests(project_ids: List[int]):
+ def make_requests(project_ids: list[int]):
for project_id in project_ids:
export_project_backup(
self.user, api_version=2, id=project_id, download_result=False
@@ -181,7 +181,7 @@ def make_requests(project_ids: List[int]):
@pytest.fixture
def fxt_make_export_task_requests(self):
- def make_requests(task_ids: List[int]):
+ def make_requests(task_ids: list[int]):
for task_id in task_ids:
export_task_backup(self.user, api_version=2, id=task_id, download_result=False)
export_task_dataset(
@@ -195,7 +195,7 @@ def make_requests(task_ids: List[int]):
@pytest.fixture
def fxt_make_export_job_requests(self):
- def make_requests(job_ids: List[int]):
+ def make_requests(job_ids: list[int]):
for job_id in job_ids:
export_job_dataset(
self.user,
@@ -230,7 +230,7 @@ def make_requests(job_ids: List[int]):
],
)
def test_can_use_simple_filter_for_object_list(
- self, simple_filter: str, values: List, fxt_resources_ids, fxt_make_requests
+ self, simple_filter: str, values: list, fxt_resources_ids, fxt_make_requests
):
project_ids, task_ids, job_ids = fxt_resources_ids
fxt_make_requests(project_ids, task_ids, job_ids)
diff --git a/tests/python/rest_api/test_tasks.py b/tests/python/rest_api/test_tasks.py
index c57dec13f639..a61683d981e2 100644
--- a/tests/python/rest_api/test_tasks.py
+++ b/tests/python/rest_api/test_tasks.py
@@ -14,6 +14,7 @@
import zipfile
from abc import ABCMeta, abstractmethod
from collections import Counter
+from collections.abc import Generator, Iterable, Sequence
from contextlib import closing
from copy import deepcopy
from datetime import datetime
@@ -26,20 +27,7 @@
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory
from time import sleep, time
-from typing import (
- Any,
- Callable,
- ClassVar,
- Dict,
- Generator,
- Iterable,
- List,
- Optional,
- Sequence,
- Set,
- Tuple,
- Union,
-)
+from typing import Any, Callable, ClassVar, Optional, Union
import attrs
import numpy as np
@@ -141,7 +129,6 @@ def _test_assigned_users_to_see_task_data(self, tasks, users, is_task_staff, **k
"groups, is_staff, is_allow",
[
("admin", False, True),
- ("business", False, False),
],
)
def test_project_tasks_visibility(
@@ -350,7 +337,6 @@ def _test_users_to_create_task_in_project(
"groups, is_staff, is_allow",
[
("admin", False, True),
- ("business", False, False),
("user", True, True),
],
)
@@ -511,8 +497,6 @@ def get_data(tid):
[
("admin", True, True),
("admin", False, True),
- ("business", True, True),
- ("business", False, False),
("worker", True, True),
("worker", False, False),
("user", True, True),
@@ -746,7 +730,7 @@ def _test_can_export_dataset(
username: str,
task_id: int,
*,
- api_version: Union[int, Tuple[int]],
+ api_version: Union[int, tuple[int]],
local_download: bool = True,
**kwargs,
) -> Optional[bytes]:
@@ -768,7 +752,7 @@ def test_can_export_task_dataset_locally_and_to_cloud_with_both_api_versions(
admin_user,
tasks_with_shapes,
filter_tasks,
- api_version: Tuple[int],
+ api_version: tuple[int],
local_download: bool,
):
filter_ = "target_storage__location"
@@ -1527,17 +1511,17 @@ def _create_task_with_cloud_data(
request,
cloud_storage: Any,
use_manifest: bool,
- server_files: List[str],
+ server_files: list[str],
use_cache: bool = True,
sorting_method: str = "lexicographical",
data_type: str = "image",
video_frame_count: int = 10,
- server_files_exclude: Optional[List[str]] = None,
+ server_files_exclude: Optional[list[str]] = None,
org: str = "",
- filenames: Optional[List[str]] = None,
- task_spec_kwargs: Optional[Dict[str, Any]] = None,
- data_spec_kwargs: Optional[Dict[str, Any]] = None,
- ) -> Tuple[int, Any]:
+ filenames: Optional[list[str]] = None,
+ task_spec_kwargs: Optional[dict[str, Any]] = None,
+ data_spec_kwargs: Optional[dict[str, Any]] = None,
+ ) -> tuple[int, Any]:
s3_client = s3.make_client(bucket=cloud_storage["resource"])
if data_type == "video":
video = generate_video_file(video_frame_count)
@@ -1643,8 +1627,8 @@ def test_create_task_with_cloud_storage_directories_and_excluded_files(
cloud_storage_id: int,
use_cache: bool,
use_manifest: bool,
- server_files: List[str],
- server_files_exclude: Optional[List[str]],
+ server_files: list[str],
+ server_files_exclude: Optional[list[str]],
task_size: int,
org: str,
cloud_storages,
@@ -1690,8 +1674,8 @@ def test_create_task_with_cloud_storage_directories_and_predefined_sorting(
self,
cloud_storage_id: int,
use_manifest: bool,
- server_files: List[str],
- expected_result: List[str],
+ server_files: list[str],
+ expected_result: list[str],
org: str,
cloud_storages,
request,
@@ -1933,7 +1917,7 @@ def test_create_task_with_cloud_storage_and_retrieve_data(
)
def test_create_task_with_cloud_storage_and_check_data_sorting(
self,
- filenames: List[str],
+ filenames: list[str],
sorting_method: str,
cloud_storage_id: int,
org: str,
@@ -2024,7 +2008,7 @@ def test_can_specify_file_job_mapping(self):
)
with make_api_client(self._USERNAME) as api_client:
- jobs: List[models.JobRead] = get_paginated_collection(
+ jobs: list[models.JobRead] = get_paginated_collection(
api_client.jobs_api.list_endpoint, task_id=task_id, sort="id"
)
(task_meta, _) = api_client.tasks_api.retrieve_data_meta(id=task_id)
@@ -2088,7 +2072,7 @@ def test_create_task_with_cloud_storage_directories_and_default_bucket_prefix(
self,
cloud_storage_id: int,
use_manifest: bool,
- server_files: List[str],
+ server_files: list[str],
default_prefix: str,
expected_task_size: int,
org: str,
@@ -2132,7 +2116,7 @@ def test_can_create_task_with_honeypots(
self,
fxt_test_name,
frame_selection_method: str,
- method_params: Set[str],
+ method_params: set[str],
per_job_count_param: str,
):
base_segment_size = 4
@@ -2316,7 +2300,7 @@ def test_can_create_task_with_gt_job_from_images(
self,
request: pytest.FixtureRequest,
frame_selection_method: str,
- method_params: Set[str],
+ method_params: set[str],
):
segment_size = 4
total_frame_count = 15
@@ -2449,7 +2433,7 @@ def test_can_create_task_with_gt_job_from_video(
self,
request: pytest.FixtureRequest,
frame_selection_method: str,
- method_params: Set[str],
+ method_params: set[str],
):
segment_size = 4
total_frame_count = 15
@@ -2650,8 +2634,8 @@ def read_frame(self, i: int) -> Image.Image: ...
@attrs.define
class _TaskSpecBase(_TaskSpec):
- _params: Union[Dict, models.TaskWriteRequest]
- _data_params: Union[Dict, models.DataRequest]
+ _params: Union[dict, models.TaskWriteRequest]
+ _data_params: Union[dict, models.DataRequest]
size: int = attrs.field(kw_only=True)
@property
@@ -2715,7 +2699,7 @@ def _uploaded_images_task_fxt_base(
step: Optional[int] = None,
segment_size: Optional[int] = None,
**data_kwargs,
- ) -> Generator[Tuple[_ImagesTaskSpec, int], None, None]:
+ ) -> Generator[tuple[_ImagesTaskSpec, int], None, None]:
task_params = {
"name": f"{request.node.name}[{request.fixturename}]",
"labels": [{"name": "a"}],
@@ -2768,13 +2752,13 @@ def get_frame(i: int) -> bytes:
@pytest.fixture(scope="class")
def fxt_uploaded_images_task(
self, request: pytest.FixtureRequest
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_fxt_base(request=request)
@pytest.fixture(scope="class")
def fxt_uploaded_images_task_with_segments(
self, request: pytest.FixtureRequest
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_fxt_base(request=request, segment_size=4)
@fixture(scope="class")
@@ -2783,7 +2767,7 @@ def fxt_uploaded_images_task_with_segments(
@parametrize("start_frame", [3, 7])
def fxt_uploaded_images_task_with_segments_start_stop_step(
self, request: pytest.FixtureRequest, start_frame: int, stop_frame: Optional[int], step: int
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_fxt_base(
request=request,
frame_count=30,
@@ -2800,7 +2784,7 @@ def _uploaded_images_task_with_honeypots_and_segments_base(
start_frame: Optional[int] = None,
step: Optional[int] = None,
random_seed: int = 42,
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
validation_params = models.DataRequestValidationParams._from_openapi_data(
mode="gt_pool",
frame_selection_method="random_uniform",
@@ -2862,14 +2846,14 @@ def _uploaded_images_task_with_honeypots_and_segments_base(
@fixture(scope="class")
def fxt_uploaded_images_task_with_honeypots_and_segments(
self, request: pytest.FixtureRequest
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_with_honeypots_and_segments_base(request)
@fixture(scope="class")
@parametrize("start_frame, step", [(2, 3)])
def fxt_uploaded_images_task_with_honeypots_and_segments_start_step(
self, request: pytest.FixtureRequest, start_frame: Optional[int], step: Optional[int]
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_with_honeypots_and_segments_base(
request, start_frame=start_frame, step=step
)
@@ -2878,7 +2862,7 @@ def fxt_uploaded_images_task_with_honeypots_and_segments_start_step(
@parametrize("random_seed", [1, 2, 5])
def fxt_uploaded_images_task_with_honeypots_and_changed_real_frames(
self, request: pytest.FixtureRequest, random_seed: int
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
with closing(
self._uploaded_images_task_with_honeypots_and_segments_base(
request, start_frame=2, step=3, random_seed=random_seed
@@ -2919,7 +2903,7 @@ def _uploaded_images_task_with_gt_and_segments_base(
start_frame: Optional[int] = None,
step: Optional[int] = None,
frame_selection_method: str = "random_uniform",
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
used_frames_count = 16
total_frame_count = (start_frame or 0) + used_frames_count * (step or 1)
segment_size = 5
@@ -2979,7 +2963,7 @@ def fxt_uploaded_images_task_with_gt_and_segments_start_step(
start_frame: Optional[int],
step: Optional[int],
frame_selection_method: str,
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_images_task_with_gt_and_segments_base(
request,
start_frame=start_frame,
@@ -2996,7 +2980,7 @@ def _uploaded_video_task_fxt_base(
start_frame: Optional[int] = None,
stop_frame: Optional[int] = None,
step: Optional[int] = None,
- ) -> Generator[Tuple[_VideoTaskSpec, int], None, None]:
+ ) -> Generator[tuple[_VideoTaskSpec, int], None, None]:
task_params = {
"name": f"{request.node.name}[{request.fixturename}]",
"labels": [{"name": "a"}],
@@ -3040,13 +3024,13 @@ def get_video_file() -> io.BytesIO:
def fxt_uploaded_video_task(
self,
request: pytest.FixtureRequest,
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_video_task_fxt_base(request=request)
@pytest.fixture(scope="class")
def fxt_uploaded_video_task_with_segments(
self, request: pytest.FixtureRequest
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_video_task_fxt_base(request=request, segment_size=4)
@fixture(scope="class")
@@ -3055,7 +3039,7 @@ def fxt_uploaded_video_task_with_segments(
@parametrize("start_frame", [3, 7])
def fxt_uploaded_video_task_with_segments_start_stop_step(
self, request: pytest.FixtureRequest, start_frame: int, stop_frame: Optional[int], step: int
- ) -> Generator[Tuple[_TaskSpec, int], None, None]:
+ ) -> Generator[tuple[_TaskSpec, int], None, None]:
yield from self._uploaded_video_task_fxt_base(
request=request,
frame_count=30,
@@ -3065,7 +3049,7 @@ def fxt_uploaded_video_task_with_segments_start_stop_step(
step=step,
)
- def _compute_annotation_segment_params(self, task_spec: _TaskSpec) -> List[Tuple[int, int]]:
+ def _compute_annotation_segment_params(self, task_spec: _TaskSpec) -> list[tuple[int, int]]:
segment_params = []
frame_step = task_spec.frame_step
segment_size = getattr(task_spec, "segment_size", 0) or task_spec.size * frame_step
@@ -3589,7 +3573,7 @@ def get_expected_chunk_abs_frame_ids(chunk_id: int):
@pytest.mark.usefixtures("restore_db_per_function")
class TestPatchTaskLabel:
- def _get_task_labels(self, pid, user, **kwargs) -> List[models.Label]:
+ def _get_task_labels(self, pid, user, **kwargs) -> list[models.Label]:
kwargs.setdefault("return_json", True)
with make_api_client(user) as api_client:
return get_paginated_collection(
@@ -3863,7 +3847,7 @@ def setup(
"local_download", (True, pytest.param(False, marks=pytest.mark.with_external_services))
)
def test_can_export_backup_with_both_api_versions(
- self, filter_tasks, api_version: Tuple[int], local_download: bool
+ self, filter_tasks, api_version: tuple[int], local_download: bool
):
task = filter_tasks(
**{("exclude_" if local_download else "") + "target_storage__location": "cloud_storage"}
@@ -4046,7 +4030,7 @@ class TestWorkWithSimpleGtJobTasks:
@fixture
def fxt_task_with_gt_job(
self, tasks, jobs, job_has_annotations
- ) -> Generator[Dict[str, Any], None, None]:
+ ) -> Generator[dict[str, Any], None, None]:
gt_job = next(
j
for j in jobs
@@ -4168,7 +4152,7 @@ class TestWorkWithHoneypotTasks:
@fixture
def fxt_task_with_honeypots(
self, tasks, jobs, job_has_annotations
- ) -> Generator[Dict[str, Any], None, None]:
+ ) -> Generator[dict[str, Any], None, None]:
gt_job = next(
j
for j in jobs
@@ -4535,6 +4519,8 @@ def test_can_change_honeypot_frames_in_task_can_only_select_from_active_validati
def test_can_change_honeypot_frames_in_annotation_jobs(
self, admin_user, task, gt_job, annotation_jobs, frame_selection_method: str
):
+ _MAX_RANDOM_ATTEMPTS = 20 # This test can have random outcomes, it's expected
+
assert gt_job["stop_frame"] - gt_job["start_frame"] + 1 >= 2
with make_api_client(admin_user) as api_client:
@@ -4556,16 +4542,34 @@ def test_can_change_honeypot_frames_in_annotation_jobs(
params["honeypot_real_frames"] = requested_honeypot_real_frames
- new_validation_layout = json.loads(
- api_client.jobs_api.partial_update_validation_layout(
- annotation_job["id"],
- patched_job_validation_layout_write_request=(
- models.PatchedJobValidationLayoutWriteRequest(**params)
- ),
- )[1].data
- )
+ attempt = 0
+ while attempt < _MAX_RANDOM_ATTEMPTS:
+ new_validation_layout = json.loads(
+ api_client.jobs_api.partial_update_validation_layout(
+ annotation_job["id"],
+ patched_job_validation_layout_write_request=(
+ models.PatchedJobValidationLayoutWriteRequest(**params)
+ ),
+ )[1].data
+ )
+
+ new_honeypot_real_frames = new_validation_layout["honeypot_real_frames"]
+
+ if (
+ frame_selection_method == "random_uniform"
+ and new_honeypot_real_frames
+ == old_validation_layout["honeypot_real_frames"]
+ ):
+ attempt += 1
+ # The test is fully random, it's possible to get no changes in the updated
+ # honeypots. Passing a random seed has little sense in this endpoint,
+ # so we retry several times in such a case instead.
+ else:
+ break
- new_honeypot_real_frames = new_validation_layout["honeypot_real_frames"]
+ if attempt >= _MAX_RANDOM_ATTEMPTS and frame_selection_method == "random_uniform":
+ # The situation is unlikely if everything works, so we consider it a fail
+ pytest.fail(f"too many attempts ({attempt}) with random honeypot updating")
assert old_validation_layout["honeypot_count"] == len(new_honeypot_real_frames)
assert all(f in gt_frame_set for f in new_honeypot_real_frames)
@@ -5251,7 +5255,7 @@ def setup_class(
cls._init_tasks()
@classmethod
- def _create_task_with_annotations(cls, filenames: List[str]):
+ def _create_task_with_annotations(cls, filenames: list[str]):
images = generate_image_files(len(filenames), filenames=filenames)
source_archive_path = cls.tmp_dir / "source_data.zip"
diff --git a/tests/python/rest_api/test_webhooks.py b/tests/python/rest_api/test_webhooks.py
index 3c528bc78c15..778eda8430ed 100644
--- a/tests/python/rest_api/test_webhooks.py
+++ b/tests/python/rest_api/test_webhooks.py
@@ -96,7 +96,7 @@ def test_admin_can_create_webhook_for_project_in_org(
assert response.status_code == HTTPStatus.CREATED
assert "secret" not in response.json()
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_sandbox_project_owner_can_create_webhook_for_project(self, privilege, projects, users):
users = [user for user in users if privilege in user["groups"]]
username, project_id = next(
@@ -116,7 +116,7 @@ def test_sandbox_project_owner_can_create_webhook_for_project(self, privilege, p
assert response.status_code == HTTPStatus.CREATED
assert "secret" not in response.json()
- @pytest.mark.parametrize("privilege", ["worker", "user", "business"])
+ @pytest.mark.parametrize("privilege", ["worker", "user"])
def test_sandbox_project_assignee_cannot_create_webhook_for_project(
self, privilege, projects, users
):
@@ -410,7 +410,7 @@ def test_admin_can_get_webhook(self, webhooks, users, projects):
assert "secret" not in response.json()
assert DeepDiff(webhooks[wid], response.json(), ignore_order=True) == {}
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_project_owner_can_get_webhook(self, privilege, webhooks, projects, users):
proj_webhooks = [w for w in webhooks if w["type"] == "project"]
username, wid = next(
@@ -418,7 +418,7 @@ def test_project_owner_can_get_webhook(self, privilege, webhooks, projects, user
(user["username"], webhook["id"])
for user in users
for webhook in proj_webhooks
- if privilege not in user["groups"]
+ if privilege in user["groups"]
and projects[webhook["project_id"]]["owner"]["id"] == user["id"]
)
)
@@ -429,7 +429,7 @@ def test_project_owner_can_get_webhook(self, privilege, webhooks, projects, user
assert "secret" not in response.json()
assert DeepDiff(webhooks[wid], response.json(), ignore_order=True) == {}
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_webhook_owner_can_get_webhook(self, privilege, webhooks, projects, users):
proj_webhooks = [w for w in webhooks if w["type"] == "project"]
username, wid = next(
@@ -447,7 +447,7 @@ def test_webhook_owner_can_get_webhook(self, privilege, webhooks, projects, user
assert "secret" not in response.json()
assert DeepDiff(webhooks[wid], response.json(), ignore_order=True) == {}
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_not_project_staff_cannot_get_webhook(self, privilege, webhooks, projects, users):
proj_webhooks = [w for w in webhooks if w["type"] == "project"]
username, wid = next(
@@ -631,7 +631,7 @@ def test_admin_can_get_webhooks_for_project_in_org(self, webhooks):
assert response.status_code == HTTPStatus.OK
assert DeepDiff(expected_response, response.json()["results"], ignore_order=True) == {}
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_user_cannot_get_webhook_list_for_project(
self, privilege, find_users, webhooks, projects
):
@@ -654,7 +654,7 @@ def test_user_cannot_get_webhook_list_for_project(
assert response.status_code == HTTPStatus.OK
assert DeepDiff([], response.json()["results"], ignore_order=True) == {}
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_user_can_get_webhook_list_for_project(self, privilege, find_users, webhooks, projects):
username, pid = next(
(
@@ -824,7 +824,7 @@ def test_cannot_update_with_nonexistent_contenttype(self):
response = patch_method("admin2", f"webhooks/{self.WID}", patch_data)
assert response.status_code == HTTPStatus.BAD_REQUEST
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_sandbox_user_can_update_webhook(self, privilege, find_users, webhooks):
username, webhook = next(
(
@@ -852,7 +852,7 @@ def test_sandbox_user_can_update_webhook(self, privilege, find_users, webhooks):
== {}
)
- @pytest.mark.parametrize("privilege", ["worker", "user", "business"])
+ @pytest.mark.parametrize("privilege", ["worker", "user"])
def test_sandbox_user_cannot_update_webhook(self, privilege, find_users, webhooks):
username, webhook = next(
(
@@ -1029,9 +1029,7 @@ def test_member_can_update_project_webhook_in_org(
@pytest.mark.usefixtures("restore_db_per_function")
class TestDeleteWebhooks:
- @pytest.mark.parametrize(
- "privilege, allow", [("user", False), ("business", False), ("admin", True)]
- )
+ @pytest.mark.parametrize("privilege, allow", [("user", False), ("admin", True)])
def test_user_can_delete_project_webhook(
self, privilege, allow, find_users, webhooks, projects
):
@@ -1101,7 +1099,7 @@ def test_admin_can_delete_org_webhook(self, find_users, webhooks, is_org_member)
response = get_method(username, f"webhooks/{webhook_id}")
assert response.status_code == HTTPStatus.NOT_FOUND
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_project_owner_can_delete_project_webhook(
self, privilege, find_users, webhooks, projects
):
@@ -1123,7 +1121,7 @@ def test_project_owner_can_delete_project_webhook(
response = get_method(username, f"webhooks/{webhook_id}")
assert response.status_code == HTTPStatus.NOT_FOUND
- @pytest.mark.parametrize("privilege", ["user", "business"])
+ @pytest.mark.parametrize("privilege", ["user"])
def test_webhook_owner_can_delete_project_webhook(
self, privilege, find_users, webhooks, projects
):
diff --git a/tests/python/rest_api/utils.py b/tests/python/rest_api/utils.py
index 0552efc737f4..434c3705ddc3 100644
--- a/tests/python/rest_api/utils.py
+++ b/tests/python/rest_api/utils.py
@@ -4,10 +4,11 @@
import json
from abc import ABCMeta, abstractmethod
+from collections.abc import Iterator, Sequence
from copy import deepcopy
from http import HTTPStatus
from time import sleep
-from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Tuple, Union
+from typing import Any, Callable, Optional, Union
import requests
from cvat_sdk.api_client import apis, models
@@ -191,7 +192,7 @@ def export_v2(
def export_dataset(
api: Union[ProjectsApi, TasksApi, JobsApi],
api_version: Union[
- int, Tuple[int]
+ int, tuple[int]
], # make this parameter required to be sure that all tests was updated and both API versions are used
*,
save_images: bool,
@@ -251,21 +252,21 @@ def _get_endpoint_and_kwargs(version: int) -> Endpoint:
# FUTURE-TODO: support username: optional, api_client: optional
def export_project_dataset(
- username: str, api_version: Union[int, Tuple[int]], *args, **kwargs
+ username: str, api_version: Union[int, tuple[int]], *args, **kwargs
) -> Optional[bytes]:
with make_api_client(username) as api_client:
return export_dataset(api_client.projects_api, api_version, *args, **kwargs)
def export_task_dataset(
- username: str, api_version: Union[int, Tuple[int]], *args, **kwargs
+ username: str, api_version: Union[int, tuple[int]], *args, **kwargs
) -> Optional[bytes]:
with make_api_client(username) as api_client:
return export_dataset(api_client.tasks_api, api_version, *args, **kwargs)
def export_job_dataset(
- username: str, api_version: Union[int, Tuple[int]], *args, **kwargs
+ username: str, api_version: Union[int, tuple[int]], *args, **kwargs
) -> Optional[bytes]:
with make_api_client(username) as api_client:
return export_dataset(api_client.jobs_api, api_version, *args, **kwargs)
@@ -274,7 +275,7 @@ def export_job_dataset(
def export_backup(
api: Union[ProjectsApi, TasksApi],
api_version: Union[
- int, Tuple[int]
+ int, tuple[int]
], # make this parameter required to be sure that all tests was updated and both API versions are used
*,
max_retries: int = 30,
@@ -309,14 +310,14 @@ def export_backup(
def export_project_backup(
- username: str, api_version: Union[int, Tuple[int]], *args, **kwargs
+ username: str, api_version: Union[int, tuple[int]], *args, **kwargs
) -> Optional[bytes]:
with make_api_client(username) as api_client:
return export_backup(api_client.projects_api, api_version, *args, **kwargs)
def export_task_backup(
- username: str, api_version: Union[int, Tuple[int]], *args, **kwargs
+ username: str, api_version: Union[int, tuple[int]], *args, **kwargs
) -> Optional[bytes]:
with make_api_client(username) as api_client:
return export_backup(api_client.tasks_api, api_version, *args, **kwargs)
@@ -379,12 +380,12 @@ def import_backup(
return import_resource(endpoint, max_retries=max_retries, interval=interval, **kwargs)
-def import_project_backup(username: str, data: Dict, **kwargs) -> None:
+def import_project_backup(username: str, data: dict, **kwargs) -> None:
with make_api_client(username) as api_client:
return import_backup(api_client.projects_api, project_file_request=deepcopy(data), **kwargs)
-def import_task_backup(username: str, data: Dict, **kwargs) -> None:
+def import_task_backup(username: str, data: dict, **kwargs) -> None:
with make_api_client(username) as api_client:
return import_backup(api_client.tasks_api, task_file_request=deepcopy(data), **kwargs)
@@ -395,20 +396,20 @@ def import_task_backup(username: str, data: Dict, **kwargs) -> None:
class CollectionSimpleFilterTestBase(metaclass=ABCMeta):
# These fields need to be defined in the subclass
user: str
- samples: List[Dict[str, Any]]
- field_lookups: Dict[str, FieldPath] = None
- cmp_ignore_keys: List[str] = ["updated_date"]
+ samples: list[dict[str, Any]]
+ field_lookups: dict[str, FieldPath] = None
+ cmp_ignore_keys: list[str] = ["updated_date"]
@abstractmethod
def _get_endpoint(self, api_client: ApiClient) -> Endpoint: ...
- def _retrieve_collection(self, **kwargs) -> List:
+ def _retrieve_collection(self, **kwargs) -> list:
kwargs["return_json"] = True
with make_api_client(self.user) as api_client:
return get_paginated_collection(self._get_endpoint(api_client), **kwargs)
@classmethod
- def _get_field(cls, d: Dict[str, Any], path: Union[str, FieldPath]) -> Optional[Any]:
+ def _get_field(cls, d: dict[str, Any], path: Union[str, FieldPath]) -> Optional[Any]:
assert path
for key in path:
if isinstance(d, dict):
@@ -428,7 +429,7 @@ def _map_field(self, name: str) -> FieldPath:
@classmethod
def _find_valid_field_value(
- cls, samples: Iterator[Dict[str, Any]], field_path: FieldPath
+ cls, samples: Iterator[dict[str, Any]], field_path: FieldPath
) -> Any:
value = None
for sample in samples:
@@ -439,7 +440,7 @@ def _find_valid_field_value(
assert value, f"Failed to find a sample for the '{'.'.join(field_path)}' field"
return value
- def _get_field_samples(self, field: str) -> Tuple[Any, List[Dict[str, Any]]]:
+ def _get_field_samples(self, field: str) -> tuple[Any, list[dict[str, Any]]]:
field_path = self._map_field(field)
field_value = self._find_valid_field_value(self.samples, field_path)
@@ -463,7 +464,7 @@ def _compare_results(self, gt_objects, received_objects):
assert diff == {}, diff
def _test_can_use_simple_filter_for_object_list(
- self, field: str, field_values: Optional[List[Any]] = None
+ self, field: str, field_values: Optional[list[Any]] = None
):
gt_objects = []
field_path = self._map_field(field)
@@ -485,12 +486,12 @@ def _test_can_use_simple_filter_for_object_list(
self._compare_results(gt_objects, received_items)
-def get_attrs(obj: Any, attributes: Sequence[str]) -> Tuple[Any, ...]:
+def get_attrs(obj: Any, attributes: Sequence[str]) -> tuple[Any, ...]:
"""Returns 1 or more object attributes as a tuple"""
return (getattr(obj, attr) for attr in attributes)
-def build_exclude_paths_expr(ignore_fields: Iterator[str]) -> List[str]:
+def build_exclude_paths_expr(ignore_fields: Iterator[str]) -> list[str]:
exclude_expr_parts = []
for key in ignore_fields:
if "." in key:
diff --git a/tests/python/sdk/fixtures.py b/tests/python/sdk/fixtures.py
index fa1051141a33..f495c4ce8ef4 100644
--- a/tests/python/sdk/fixtures.py
+++ b/tests/python/sdk/fixtures.py
@@ -3,7 +3,6 @@
# SPDX-License-Identifier: MIT
from pathlib import Path
-from typing import Tuple
from zipfile import ZipFile
import pytest
@@ -72,7 +71,7 @@ def fxt_coco_dataset(tmp_path: Path, fxt_image_file: Path, fxt_coco_file: Path):
@pytest.fixture
-def fxt_new_task(fxt_image_file: Path, fxt_login: Tuple[Client, str]):
+def fxt_new_task(fxt_image_file: Path, fxt_login: tuple[Client, str]):
client, _ = fxt_login
task = client.tasks.create_from_data(
spec={
@@ -87,7 +86,7 @@ def fxt_new_task(fxt_image_file: Path, fxt_login: Tuple[Client, str]):
@pytest.fixture
-def fxt_new_task_with_target_storage(fxt_image_file: Path, fxt_login: Tuple[Client, str]):
+def fxt_new_task_with_target_storage(fxt_image_file: Path, fxt_login: tuple[Client, str]):
client, _ = fxt_login
task = client.tasks.create_from_data(
spec={
diff --git a/tests/python/sdk/test_auto_annotation.py b/tests/python/sdk/test_auto_annotation.py
index e7ac8418b69a..ae4a0d711774 100644
--- a/tests/python/sdk/test_auto_annotation.py
+++ b/tests/python/sdk/test_auto_annotation.py
@@ -6,7 +6,6 @@
from logging import Logger
from pathlib import Path
from types import SimpleNamespace as namespace
-from typing import List, Tuple
import cvat_sdk.auto_annotation as cvataa
import PIL.Image
@@ -27,8 +26,8 @@
@pytest.fixture(autouse=True)
def _common_setup(
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
restore_redis_ondisk_per_function,
):
logger = fxt_logger[0]
@@ -46,7 +45,7 @@ class TestTaskAutoAnnotation:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
+ fxt_login: tuple[Client, str],
):
self.client = fxt_login[0]
self.images = [
@@ -114,7 +113,7 @@ def test_detection_rectangle(self):
def detect(
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
- ) -> List[models.LabeledShapeRequest]:
+ ) -> list[models.LabeledShapeRequest]:
assert context.frame_name in {"1.png", "2.png"}
assert image.width == image.height == 333
return [
@@ -168,7 +167,7 @@ def test_detection_skeleton(self):
],
)
- def detect(context, image: PIL.Image.Image) -> List[models.LabeledShapeRequest]:
+ def detect(context, image: PIL.Image.Image) -> list[models.LabeledShapeRequest]:
assert image.width == image.height == 333
return [
cvataa.skeleton(
@@ -241,7 +240,7 @@ def test_detection_without_clearing(self):
],
)
- def detect(context, image: PIL.Image.Image) -> List[models.LabeledShapeRequest]:
+ def detect(context, image: PIL.Image.Image) -> list[models.LabeledShapeRequest]:
return [
cvataa.rectangle(
123, # car
@@ -570,7 +569,7 @@ def __init__(self, label_id: int) -> None:
super().__init__()
self._label_id = label_id
- def forward(self, images: List[torch.Tensor]) -> List[dict]:
+ def forward(self, images: list[torch.Tensor]) -> list[dict]:
assert isinstance(images, list)
assert all(isinstance(t, torch.Tensor) for t in images)
@@ -589,12 +588,12 @@ def fake_get_detection_model(name: str, weights, test_param):
return FakeTorchvisionDetector(label_id=car_label_id)
class FakeTorchvisionKeypointDetector(nn.Module):
- def __init__(self, label_id: int, keypoint_names: List[str]) -> None:
+ def __init__(self, label_id: int, keypoint_names: list[str]) -> None:
super().__init__()
self._label_id = label_id
self._keypoint_names = keypoint_names
- def forward(self, images: List[torch.Tensor]) -> List[dict]:
+ def forward(self, images: list[torch.Tensor]) -> list[dict]:
assert isinstance(images, list)
assert all(isinstance(t, torch.Tensor) for t in images)
@@ -628,7 +627,7 @@ class TestAutoAnnotationFunctions:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
+ fxt_login: tuple[Client, str],
):
self.client = fxt_login[0]
self.image = generate_image_file("1.png", size=(100, 100))
diff --git a/tests/python/sdk/test_client.py b/tests/python/sdk/test_client.py
index 7554e8f5f2d8..38609176222c 100644
--- a/tests/python/sdk/test_client.py
+++ b/tests/python/sdk/test_client.py
@@ -5,7 +5,6 @@
import io
from contextlib import ExitStack
from logging import Logger
-from typing import List, Tuple
import packaging.version as pv
import pytest
@@ -22,7 +21,7 @@ class TestClientUsecases:
def setup(
self,
restore_db_per_function, # force fixture call order to allow DB setup
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_client: Client,
fxt_stdout: io.StringIO,
admin_user: str,
@@ -95,7 +94,7 @@ def test_can_reject_invalid_server_schema():
@pytest.mark.parametrize("raise_exception", (True, False))
def test_can_warn_on_mismatching_server_version(
- fxt_logger: Tuple[Logger, io.StringIO], monkeypatch, raise_exception: bool
+ fxt_logger: tuple[Logger, io.StringIO], monkeypatch, raise_exception: bool
):
logger, logger_stream = fxt_logger
@@ -118,7 +117,7 @@ def mocked_version(_):
@pytest.mark.parametrize("do_check", (True, False))
def test_can_check_server_version_in_ctor(
- fxt_logger: Tuple[Logger, io.StringIO], monkeypatch, do_check: bool
+ fxt_logger: tuple[Logger, io.StringIO], monkeypatch, do_check: bool
):
logger, logger_stream = fxt_logger
@@ -141,7 +140,7 @@ def mocked_version(_):
) == do_check
-def test_can_check_server_version_in_method(fxt_logger: Tuple[Logger, io.StringIO], monkeypatch):
+def test_can_check_server_version_in_method(fxt_logger: tuple[Logger, io.StringIO], monkeypatch):
logger, logger_stream = fxt_logger
def mocked_version(_):
@@ -183,10 +182,10 @@ def mocked_version(_):
],
)
def test_can_check_server_version_compatibility(
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_logger: tuple[Logger, io.StringIO],
monkeypatch: pytest.MonkeyPatch,
server_version: str,
- supported_versions: List[str],
+ supported_versions: list[str],
expect_supported: bool,
):
logger, _ = fxt_logger
diff --git a/tests/python/sdk/test_datasets.py b/tests/python/sdk/test_datasets.py
index 542ad9a1e80c..525082d0eae3 100644
--- a/tests/python/sdk/test_datasets.py
+++ b/tests/python/sdk/test_datasets.py
@@ -5,7 +5,6 @@
import io
from logging import Logger
from pathlib import Path
-from typing import Tuple
import cvat_sdk.datasets as cvatds
import PIL.Image
@@ -21,8 +20,8 @@
@pytest.fixture(autouse=True)
def _common_setup(
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
restore_redis_ondisk_per_function,
):
logger = fxt_logger[0]
@@ -40,7 +39,7 @@ class TestTaskDataset:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
+ fxt_login: tuple[Client, str],
):
self.client = fxt_login[0]
self.images = generate_image_files(10)
diff --git a/tests/python/sdk/test_issues_comments.py b/tests/python/sdk/test_issues_comments.py
index 12047c75a1f0..f90b663fbefe 100644
--- a/tests/python/sdk/test_issues_comments.py
+++ b/tests/python/sdk/test_issues_comments.py
@@ -5,7 +5,6 @@
import io
from logging import Logger
from pathlib import Path
-from typing import Tuple
import pytest
from cvat_sdk import Client
@@ -18,8 +17,8 @@ class TestIssuesUsecases:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
):
self.tmp_path = tmp_path
@@ -139,8 +138,8 @@ class TestCommentsUsecases:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
):
self.tmp_path = tmp_path
diff --git a/tests/python/sdk/test_jobs.py b/tests/python/sdk/test_jobs.py
index 3202e2957ff0..3d49978d5da5 100644
--- a/tests/python/sdk/test_jobs.py
+++ b/tests/python/sdk/test_jobs.py
@@ -5,7 +5,7 @@
import io
from logging import Logger
from pathlib import Path
-from typing import Optional, Tuple
+from typing import Optional
import pytest
from cvat_sdk import Client
@@ -26,8 +26,8 @@ class TestJobUsecases(TestDatasetExport):
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
restore_redis_ondisk_per_function,
):
diff --git a/tests/python/sdk/test_organizations.py b/tests/python/sdk/test_organizations.py
index 84198c73b24f..54f9798cc849 100644
--- a/tests/python/sdk/test_organizations.py
+++ b/tests/python/sdk/test_organizations.py
@@ -4,7 +4,6 @@
import io
from logging import Logger
-from typing import Tuple
import pytest
from cvat_sdk import Client, models
@@ -16,8 +15,8 @@ class TestOrganizationUsecases:
@pytest.fixture(autouse=True)
def setup(
self,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
):
logger, self.logger_stream = fxt_logger
diff --git a/tests/python/sdk/test_projects.py b/tests/python/sdk/test_projects.py
index b03df660d87a..db0b5f265586 100644
--- a/tests/python/sdk/test_projects.py
+++ b/tests/python/sdk/test_projects.py
@@ -5,7 +5,7 @@
import io
from logging import Logger
from pathlib import Path
-from typing import Optional, Tuple
+from typing import Optional
import pytest
from cvat_sdk import Client, models
@@ -29,8 +29,8 @@ class TestProjectUsecases(TestDatasetExport):
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
restore_redis_ondisk_per_function,
):
diff --git a/tests/python/sdk/test_pytorch.py b/tests/python/sdk/test_pytorch.py
index 2bcbd122abff..8e6918abf301 100644
--- a/tests/python/sdk/test_pytorch.py
+++ b/tests/python/sdk/test_pytorch.py
@@ -7,7 +7,6 @@
import os
from logging import Logger
from pathlib import Path
-from typing import Tuple
import pytest
from cvat_sdk import Client, models
@@ -34,8 +33,8 @@
@pytest.fixture(autouse=True)
def _common_setup(
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
restore_redis_ondisk_per_function,
):
logger = fxt_logger[0]
@@ -54,7 +53,7 @@ class TestTaskVisionDataset:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
+ fxt_login: tuple[Client, str],
):
self.client = fxt_login[0]
self.images = generate_image_files(10)
@@ -298,7 +297,7 @@ class TestProjectVisionDataset:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
+ fxt_login: tuple[Client, str],
):
self.client = fxt_login[0]
diff --git a/tests/python/sdk/test_tasks.py b/tests/python/sdk/test_tasks.py
index 54e0823d3311..0181d5c74d3b 100644
--- a/tests/python/sdk/test_tasks.py
+++ b/tests/python/sdk/test_tasks.py
@@ -7,7 +7,7 @@
import zipfile
from logging import Logger
from pathlib import Path
-from typing import Optional, Tuple
+from typing import Optional
import pytest
from cvat_sdk import Client, models
@@ -30,8 +30,8 @@ class TestTaskUsecases(TestDatasetExport):
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
restore_redis_ondisk_per_function,
):
diff --git a/tests/python/sdk/test_users.py b/tests/python/sdk/test_users.py
index 94c61adac3db..fbdc675e5dd2 100644
--- a/tests/python/sdk/test_users.py
+++ b/tests/python/sdk/test_users.py
@@ -5,7 +5,6 @@
import io
from logging import Logger
from pathlib import Path
-from typing import Tuple
import pytest
from cvat_sdk import Client, models
@@ -17,8 +16,8 @@ class TestUserUsecases:
def setup(
self,
tmp_path: Path,
- fxt_login: Tuple[Client, str],
- fxt_logger: Tuple[Logger, io.StringIO],
+ fxt_login: tuple[Client, str],
+ fxt_logger: tuple[Logger, io.StringIO],
fxt_stdout: io.StringIO,
):
self.tmp_path = tmp_path
diff --git a/tests/python/sdk/util.py b/tests/python/sdk/util.py
index 1686330ad9f1..4384a88d418d 100644
--- a/tests/python/sdk/util.py
+++ b/tests/python/sdk/util.py
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: MIT
import textwrap
+from collections.abc import Container
from pathlib import Path
-from typing import Container, Tuple
from urllib.parse import urlparse
import pytest
@@ -16,7 +16,7 @@ def make_pbar(file, **kwargs):
return DeferredTqdmProgressReporter({"file": file, "mininterval": 0, **kwargs})
-def generate_coco_json(filename: Path, img_info: Tuple[Path, int, int]):
+def generate_coco_json(filename: Path, img_info: tuple[Path, int, int]):
image_filename, image_width, image_height = img_info
content = generate_coco_anno(
diff --git a/tests/python/shared/assets/cloudstorages.json b/tests/python/shared/assets/cloudstorages.json
index 4cda853ec930..8d8d92009aad 100644
--- a/tests/python/shared/assets/cloudstorages.json
+++ b/tests/python/shared/assets/cloudstorages.json
@@ -36,11 +36,11 @@
],
"organization": 2,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"provider_type": "AWS_S3_BUCKET",
"resource": "private",
diff --git a/tests/python/shared/assets/comments.json b/tests/python/shared/assets/comments.json
index f1f7457eae75..4681af9bd7dc 100644
--- a/tests/python/shared/assets/comments.json
+++ b/tests/python/shared/assets/comments.json
@@ -37,11 +37,11 @@
"issue": 3,
"message": "Another one issue",
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"updated_date": "2022-03-16T11:08:18.370000Z"
},
@@ -51,11 +51,11 @@
"issue": 2,
"message": "Something should be here",
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"updated_date": "2022-03-16T11:07:22.173000Z"
},
diff --git a/tests/python/shared/assets/cvat_db/data.json b/tests/python/shared/assets/cvat_db/data.json
index 7bb20c4507c8..5b30d421cb5a 100644
--- a/tests/python/shared/assets/cvat_db/data.json
+++ b/tests/python/shared/assets/cvat_db/data.json
@@ -10,14 +10,6 @@
{
"model": "auth.group",
"pk": 2,
- "fields": {
- "name": "business",
- "permissions": []
- }
-},
-{
- "model": "auth.group",
- "pk": 3,
"fields": {
"name": "user",
"permissions": []
@@ -25,7 +17,7 @@
},
{
"model": "auth.group",
- "pk": 4,
+ "pk": 3,
"fields": {
"name": "worker",
"permissions": []
@@ -236,16 +228,16 @@
"password": "md5$6TyZJsUJ2hAbICwZHKp4p0$961841748b31d28bcaf3094e549d2bd5",
"last_login": "2022-09-28T12:17:51.373Z",
"is_superuser": false,
- "username": "business1",
- "first_name": "Business",
- "last_name": "First",
- "email": "business1@cvat.org",
+ "username": "user7",
+ "first_name": "User",
+ "last_name": "Seventh",
+ "email": "user7@cvat.org",
"is_staff": false,
"is_active": true,
"date_joined": "2021-12-14T18:33:06Z",
"groups": [
[
- "business"
+ "user"
]
],
"user_permissions": []
@@ -258,16 +250,16 @@
"password": "md5$oLNLFFMdjViRqnAw1th3Zl$d816d16307053866451da43fb4443b66",
"last_login": "2022-03-17T07:22:55.930Z",
"is_superuser": false,
- "username": "business2",
- "first_name": "Business",
- "last_name": "Second",
- "email": "business2@cvat.org",
+ "username": "user8",
+ "first_name": "User",
+ "last_name": "Eighth",
+ "email": "user8@cvat.org",
"is_staff": false,
"is_active": true,
"date_joined": "2021-12-14T18:34:01Z",
"groups": [
[
- "business"
+ "user"
]
],
"user_permissions": []
@@ -280,16 +272,16 @@
"password": "md5$7ETBhORLrHl45WPL9CkxnN$af77496152b60ffc73ef877c99807385",
"last_login": null,
"is_superuser": false,
- "username": "business3",
- "first_name": "Business",
- "last_name": "Third",
- "email": "business3@cvat.org",
+ "username": "user9",
+ "first_name": "User",
+ "last_name": "Nineth",
+ "email": "user9@cvat.org",
"is_staff": false,
"is_active": true,
"date_joined": "2021-12-14T18:34:34Z",
"groups": [
[
- "business"
+ "user"
]
],
"user_permissions": []
@@ -302,16 +294,16 @@
"password": "md5$9huaZ72ncQGfmxUqU3Hwnz$6b010216eea87409f0aca7126bd80bbd",
"last_login": null,
"is_superuser": false,
- "username": "business4",
- "first_name": "Business",
- "last_name": "Fourth",
- "email": "business4@cvat.org",
+ "username": "user10",
+ "first_name": "User",
+ "last_name": "Tenth",
+ "email": "user10@cvat.org",
"is_staff": false,
"is_active": true,
"date_joined": "2021-12-14T18:35:15Z",
"groups": [
[
- "business"
+ "user"
]
],
"user_permissions": []
@@ -734,7 +726,7 @@
"pk": "53da3ff9e514d84b56b5170059ff0f595c34157b",
"fields": {
"user": [
- "business2"
+ "user8"
],
"created": "2022-03-17T07:22:55.921Z"
}
@@ -754,7 +746,7 @@
"pk": "c051fe19df24a0ac4c6bec5e635034271c9549dc",
"fields": {
"user": [
- "business1"
+ "user7"
],
"created": "2023-05-01T08:42:48.127Z"
}
@@ -833,7 +825,7 @@
"email": "org2@cvat.org"
},
"owner": [
- "business1"
+ "user7"
]
}
},
@@ -881,7 +873,7 @@
"pk": 4,
"fields": {
"user": [
- "business1"
+ "user7"
],
"organization": 1,
"is_active": true,
@@ -894,7 +886,7 @@
"pk": 5,
"fields": {
"user": [
- "business1"
+ "user7"
],
"organization": 2,
"is_active": true,
@@ -907,7 +899,7 @@
"pk": 6,
"fields": {
"user": [
- "business2"
+ "user8"
],
"organization": 2,
"is_active": true,
@@ -1024,7 +1016,7 @@
"pk": 15,
"fields": {
"user": [
- "business2"
+ "user8"
],
"organization": 1,
"is_active": true,
@@ -1039,7 +1031,7 @@
"created_date": "2022-01-19T13:54:42.005Z",
"sent_date": "2022-01-19T13:54:42.005Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 10
}
@@ -1051,7 +1043,7 @@
"created_date": "2021-12-14T19:54:46.172Z",
"sent_date": "2021-12-14T19:54:46.172Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 7
}
@@ -1063,7 +1055,7 @@
"created_date": "2022-01-19T13:54:42.015Z",
"sent_date": "2022-01-19T13:54:42.015Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 11
}
@@ -1099,7 +1091,7 @@
"created_date": "2021-12-14T19:54:33.591Z",
"sent_date": "2021-12-14T19:54:33.591Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 6
}
@@ -1147,7 +1139,7 @@
"created_date": "2021-12-14T19:55:13.745Z",
"sent_date": "2021-12-14T19:55:13.745Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 9
}
@@ -1171,7 +1163,7 @@
"created_date": "2021-12-14T19:54:56.431Z",
"sent_date": "2021-12-14T19:54:56.431Z",
"owner": [
- "business1"
+ "user7"
],
"membership": 8
}
@@ -3896,7 +3888,7 @@
"updated_date": "2022-11-03T13:57:25.895Z",
"name": "project1",
"owner": [
- "business1"
+ "user7"
],
"assignee": [
"user6"
@@ -3917,7 +3909,7 @@
"updated_date": "2022-06-30T08:56:45.601Z",
"name": "project2",
"owner": [
- "business1"
+ "user7"
],
"assignee": [
"user2"
@@ -4000,7 +3992,7 @@
"user1"
],
"assignee": [
- "business4"
+ "user10"
],
"assignee_updated_date": null,
"bug_tracker": "",
@@ -4179,7 +4171,7 @@
"admin1"
],
"assignee": [
- "business1"
+ "user7"
],
"assignee_updated_date": "2024-09-23T08:09:45.461Z",
"bug_tracker": "",
@@ -4281,7 +4273,7 @@
"name": "task_2_org2",
"mode": "annotation",
"owner": [
- "business2"
+ "user8"
],
"assignee": [
"worker2"
@@ -4337,7 +4329,7 @@
"name": "task1_in_project1",
"mode": "annotation",
"owner": [
- "business1"
+ "user7"
],
"assignee": [
"admin1"
@@ -4365,7 +4357,7 @@
"name": "task1_in_project2",
"mode": "annotation",
"owner": [
- "business1"
+ "user7"
],
"assignee": [
"user5"
@@ -4738,7 +4730,7 @@
"user3"
],
"assignee": [
- "business1"
+ "user7"
],
"assignee_updated_date": "2024-09-23T10:51:45.525Z",
"bug_tracker": "",
@@ -12759,7 +12751,7 @@
"pk": 10,
"fields": {
"user": [
- "business1"
+ "user7"
],
"rating": 0.0,
"has_analytics_access": false
@@ -12770,7 +12762,7 @@
"pk": 11,
"fields": {
"user": [
- "business2"
+ "user8"
],
"rating": 0.0,
"has_analytics_access": false
@@ -12781,7 +12773,7 @@
"pk": 12,
"fields": {
"user": [
- "business3"
+ "user9"
],
"rating": 0.0,
"has_analytics_access": false
@@ -12792,7 +12784,7 @@
"pk": 13,
"fields": {
"user": [
- "business4"
+ "user10"
],
"rating": 0.0,
"has_analytics_access": false
@@ -12912,7 +12904,7 @@
"position": "98.48046875, 696.72265625, 326.1220703125, 841.5859375",
"job": 9,
"owner": [
- "business2"
+ "user8"
],
"assignee": null,
"resolved": false
@@ -12928,7 +12920,7 @@
"position": "108.1845703125, 235.0, 720.0087890625, 703.3505859375",
"job": 16,
"owner": [
- "business2"
+ "user8"
],
"assignee": null,
"resolved": false
@@ -13002,7 +12994,7 @@
"updated_date": "2022-03-16T11:07:22.173Z",
"issue": 2,
"owner": [
- "business2"
+ "user8"
],
"message": "Something should be here"
}
@@ -13015,7 +13007,7 @@
"updated_date": "2022-03-16T11:08:18.370Z",
"issue": 3,
"owner": [
- "business2"
+ "user8"
],
"message": "Another one issue"
}
@@ -13099,7 +13091,7 @@
"resource": "private",
"display_name": "Bucket 2",
"owner": [
- "business2"
+ "user8"
],
"credentials": "minio_access_key minio_secret_key",
"credentials_type": "KEY_SECRET_KEY_PAIR",
@@ -13563,7 +13555,7 @@
"is_active": true,
"enable_ssl": true,
"owner": [
- "business1"
+ "user7"
],
"project": 1,
"organization": null
@@ -13682,9 +13674,9 @@
"user": {
"id": 11,
"url": "http://localhost:8080/api/users/11",
- "username": "business2",
- "last_name": "Second",
- "first_name": "Business"
+ "username": "user8",
+ "last_name": "Eighth",
+ "first_name": "User"
},
"owner": {
"id": 2,
@@ -13771,9 +13763,9 @@
"user": {
"id": 11,
"url": "http://localhost:8080/api/users/11",
- "username": "business2",
- "last_name": "Second",
- "first_name": "Business"
+ "username": "user8",
+ "last_name": "Eighth",
+ "first_name": "User"
},
"is_active": true,
"invitation": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX",
@@ -18172,6 +18164,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18180,6 +18173,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18194,6 +18188,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18202,6 +18197,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18216,6 +18212,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18224,6 +18221,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18238,6 +18236,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18246,6 +18245,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18260,6 +18260,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18268,6 +18269,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18282,6 +18284,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18290,6 +18293,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18304,6 +18308,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18312,6 +18317,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18326,6 +18332,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18334,6 +18341,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18348,6 +18356,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18356,6 +18365,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18370,6 +18380,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18378,6 +18389,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18392,6 +18404,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18400,6 +18413,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18414,6 +18428,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18422,6 +18437,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18436,6 +18452,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18444,6 +18461,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18458,6 +18476,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18466,6 +18485,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18480,6 +18500,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18488,6 +18509,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18502,6 +18524,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18510,6 +18533,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18524,6 +18548,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18532,6 +18557,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18546,6 +18572,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18554,6 +18581,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18568,6 +18596,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18576,6 +18605,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18590,6 +18620,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18598,6 +18629,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18612,6 +18644,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18620,6 +18653,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18634,6 +18668,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18642,6 +18677,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18656,6 +18692,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18664,6 +18701,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -18678,6 +18716,7 @@
"oks_sigma": 0.09,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "point_size_base": "group_bbox_size",
"compare_line_orientation": true,
"line_orientation_threshold": 0.1,
"compare_groups": true,
@@ -18686,6 +18725,7 @@
"object_visibility_threshold": 0.05,
"panoptic_comparison": true,
"compare_attributes": true,
+ "match_empty_frames": false,
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"max_validations_per_job": 0
@@ -19010,7 +19050,7 @@
"user"
],
"object_id": "10",
- "object_repr": "business1",
+ "object_repr": "user7",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19028,7 +19068,7 @@
"user"
],
"object_id": "10",
- "object_repr": "business1",
+ "object_repr": "user7",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\"]}}]"
}
@@ -19046,7 +19086,7 @@
"user"
],
"object_id": "10",
- "object_repr": "business1",
+ "object_repr": "user7",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"Last name\", \"Email address\", \"Groups\"]}}]"
}
@@ -19064,7 +19104,7 @@
"user"
],
"object_id": "11",
- "object_repr": "business2",
+ "object_repr": "user8",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19082,7 +19122,7 @@
"user"
],
"object_id": "11",
- "object_repr": "business2",
+ "object_repr": "user8",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\", \"Groups\"]}}]"
}
@@ -19100,7 +19140,7 @@
"user"
],
"object_id": "12",
- "object_repr": "business3",
+ "object_repr": "user9",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19118,7 +19158,7 @@
"user"
],
"object_id": "12",
- "object_repr": "business3",
+ "object_repr": "user9",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\", \"Groups\"]}}]"
}
@@ -19136,7 +19176,7 @@
"user"
],
"object_id": "13",
- "object_repr": "business4",
+ "object_repr": "user10",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19154,7 +19194,7 @@
"user"
],
"object_id": "13",
- "object_repr": "business4",
+ "object_repr": "user10",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\", \"Groups\"]}}]"
}
diff --git a/tests/python/shared/assets/invitations.json b/tests/python/shared/assets/invitations.json
index 6b0f24528202..9a58f4bbee09 100644
--- a/tests/python/shared/assets/invitations.json
+++ b/tests/python/shared/assets/invitations.json
@@ -21,11 +21,11 @@
},
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
}
},
{
@@ -113,11 +113,11 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "maintainer",
"user": {
@@ -138,11 +138,11 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "supervisor",
"user": {
@@ -163,11 +163,11 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "supervisor",
"user": {
@@ -188,11 +188,11 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "worker",
"user": {
@@ -213,11 +213,11 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "worker",
"user": {
@@ -238,19 +238,19 @@
"slug": "org2"
},
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
}
},
{
@@ -271,11 +271,11 @@
},
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
}
},
{
diff --git a/tests/python/shared/assets/issues.json b/tests/python/shared/assets/issues.json
index 9aff3cf4b632..e719b30ac11d 100644
--- a/tests/python/shared/assets/issues.json
+++ b/tests/python/shared/assets/issues.json
@@ -72,11 +72,11 @@
"id": 3,
"job": 16,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"position": [
108.1845703125,
@@ -98,11 +98,11 @@
"id": 2,
"job": 9,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"position": [
98.48046875,
diff --git a/tests/python/shared/assets/memberships.json b/tests/python/shared/assets/memberships.json
index 9ae6bcc8d950..3c0be8035d2d 100644
--- a/tests/python/shared/assets/memberships.json
+++ b/tests/python/shared/assets/memberships.json
@@ -11,11 +11,11 @@
"organization": 1,
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
}
},
{
@@ -146,11 +146,11 @@
"organization": 2,
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
}
},
{
@@ -161,11 +161,11 @@
"organization": 2,
"role": "owner",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
}
},
{
@@ -176,11 +176,11 @@
"organization": 1,
"role": "maintainer",
"user": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
}
},
{
diff --git a/tests/python/shared/assets/organizations.json b/tests/python/shared/assets/organizations.json
index ad26620a27e0..8106c5b8b6a7 100644
--- a/tests/python/shared/assets/organizations.json
+++ b/tests/python/shared/assets/organizations.json
@@ -12,11 +12,11 @@
"id": 2,
"name": "Organization #2",
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"slug": "org2",
"updated_date": "2021-12-14T19:51:38.667000Z"
diff --git a/tests/python/shared/assets/projects.json b/tests/python/shared/assets/projects.json
index f7c0c25b464e..19d345ee4e30 100644
--- a/tests/python/shared/assets/projects.json
+++ b/tests/python/shared/assets/projects.json
@@ -5,11 +5,11 @@
"results": [
{
"assignee": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"assignee_updated_date": "2024-09-23T08:09:45.461000Z",
"bug_tracker": "",
@@ -383,11 +383,11 @@
},
{
"assignee": {
- "first_name": "Business",
+ "first_name": "User",
"id": 13,
- "last_name": "Fourth",
+ "last_name": "Tenth",
"url": "http://localhost:8080/api/users/13",
- "username": "business4"
+ "username": "user10"
},
"assignee_updated_date": null,
"bug_tracker": "",
@@ -553,11 +553,11 @@
"name": "project2",
"organization": 2,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"source_storage": {
"cloud_storage_id": 3,
@@ -600,11 +600,11 @@
"name": "project1",
"organization": null,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"source_storage": null,
"status": "annotation",
diff --git a/tests/python/shared/assets/quality_settings.json b/tests/python/shared/assets/quality_settings.json
index 54e0c18c63a3..7ddc589bc7bf 100644
--- a/tests/python/shared/assets/quality_settings.json
+++ b/tests/python/shared/assets/quality_settings.json
@@ -14,10 +14,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 2
@@ -33,10 +35,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 5
@@ -52,10 +56,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 6
@@ -71,10 +77,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 7
@@ -90,10 +98,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 8
@@ -109,10 +119,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 9
@@ -128,10 +140,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 11
@@ -147,10 +161,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 12
@@ -166,10 +182,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 13
@@ -185,10 +203,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 14
@@ -204,10 +224,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 15
@@ -223,10 +245,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 17
@@ -242,10 +266,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 18
@@ -261,10 +287,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 19
@@ -280,10 +308,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 20
@@ -299,10 +329,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 21
@@ -318,10 +350,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 22
@@ -337,10 +371,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 23
@@ -356,10 +392,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 24
@@ -375,10 +413,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 25
@@ -394,10 +434,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 26
@@ -413,10 +455,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 27
@@ -432,10 +476,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 28
@@ -451,10 +497,12 @@
"line_orientation_threshold": 0.1,
"line_thickness": 0.01,
"low_overlap_threshold": 0.8,
+ "match_empty_frames": false,
"max_validations_per_job": 0,
"object_visibility_threshold": 0.05,
"oks_sigma": 0.09,
"panoptic_comparison": true,
+ "point_size_base": "group_bbox_size",
"target_metric": "accuracy",
"target_metric_threshold": 0.7,
"task_id": 29
diff --git a/tests/python/shared/assets/tasks.json b/tests/python/shared/assets/tasks.json
index 5a28176ef5ec..cf2d63da785c 100644
--- a/tests/python/shared/assets/tasks.json
+++ b/tests/python/shared/assets/tasks.json
@@ -159,11 +159,11 @@
},
{
"assignee": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"assignee_updated_date": "2024-09-23T10:51:45.525000Z",
"bug_tracker": "",
@@ -890,11 +890,11 @@
"organization": 2,
"overlap": 0,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"project_id": 2,
"segment_size": 11,
@@ -948,11 +948,11 @@
"organization": null,
"overlap": 0,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"project_id": 1,
"segment_size": 5,
@@ -1048,11 +1048,11 @@
"organization": 2,
"overlap": 0,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 11,
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
"project_id": null,
"segment_size": 11,
diff --git a/tests/python/shared/assets/users.json b/tests/python/shared/assets/users.json
index d3a420b297a7..9c4dce1e4fdf 100644
--- a/tests/python/shared/assets/users.json
+++ b/tests/python/shared/assets/users.json
@@ -133,10 +133,10 @@
},
{
"date_joined": "2021-12-14T18:35:15Z",
- "email": "business4@cvat.org",
- "first_name": "Business",
+ "email": "user10@cvat.org",
+ "first_name": "User",
"groups": [
- "business"
+ "user"
],
"has_analytics_access": false,
"id": 13,
@@ -144,16 +144,16 @@
"is_staff": false,
"is_superuser": false,
"last_login": null,
- "last_name": "Fourth",
+ "last_name": "Tenth",
"url": "http://localhost:8080/api/users/13",
- "username": "business4"
+ "username": "user10"
},
{
"date_joined": "2021-12-14T18:34:34Z",
- "email": "business3@cvat.org",
- "first_name": "Business",
+ "email": "user9@cvat.org",
+ "first_name": "User",
"groups": [
- "business"
+ "user"
],
"has_analytics_access": false,
"id": 12,
@@ -161,16 +161,16 @@
"is_staff": false,
"is_superuser": false,
"last_login": null,
- "last_name": "Third",
+ "last_name": "Nineth",
"url": "http://localhost:8080/api/users/12",
- "username": "business3"
+ "username": "user9"
},
{
"date_joined": "2021-12-14T18:34:01Z",
- "email": "business2@cvat.org",
- "first_name": "Business",
+ "email": "user8@cvat.org",
+ "first_name": "User",
"groups": [
- "business"
+ "user"
],
"has_analytics_access": false,
"id": 11,
@@ -178,16 +178,16 @@
"is_staff": false,
"is_superuser": false,
"last_login": "2022-03-17T07:22:55.930000Z",
- "last_name": "Second",
+ "last_name": "Eighth",
"url": "http://localhost:8080/api/users/11",
- "username": "business2"
+ "username": "user8"
},
{
"date_joined": "2021-12-14T18:33:06Z",
- "email": "business1@cvat.org",
- "first_name": "Business",
+ "email": "user7@cvat.org",
+ "first_name": "User",
"groups": [
- "business"
+ "user"
],
"has_analytics_access": false,
"id": 10,
@@ -195,9 +195,9 @@
"is_staff": false,
"is_superuser": false,
"last_login": "2022-09-28T12:17:51.373000Z",
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
{
"date_joined": "2021-12-14T18:32:01Z",
diff --git a/tests/python/shared/assets/webhooks.json b/tests/python/shared/assets/webhooks.json
index da5b0f6837d9..b6a90828ee3c 100644
--- a/tests/python/shared/assets/webhooks.json
+++ b/tests/python/shared/assets/webhooks.json
@@ -95,11 +95,11 @@
"is_active": true,
"organization": null,
"owner": {
- "first_name": "Business",
+ "first_name": "User",
"id": 10,
- "last_name": "First",
+ "last_name": "Seventh",
"url": "http://localhost:8080/api/users/10",
- "username": "business1"
+ "username": "user7"
},
"project_id": 1,
"target_url": "http://example.com/",
diff --git a/tests/python/shared/fixtures/data.py b/tests/python/shared/fixtures/data.py
index 13f128d2edf8..0f6fb6939544 100644
--- a/tests/python/shared/fixtures/data.py
+++ b/tests/python/shared/fixtures/data.py
@@ -5,8 +5,8 @@
import json
import operator
from collections import defaultdict
+from collections.abc import Iterable
from copy import deepcopy
-from typing import Iterable
import pytest
diff --git a/tests/python/shared/fixtures/init.py b/tests/python/shared/fixtures/init.py
index b29c5c30528b..1f5d57ffc5d7 100644
--- a/tests/python/shared/fixtures/init.py
+++ b/tests/python/shared/fixtures/init.py
@@ -10,7 +10,7 @@
from pathlib import Path
from subprocess import PIPE, CalledProcessError, run
from time import sleep
-from typing import List, Union
+from typing import Union
import pytest
import requests
@@ -158,13 +158,13 @@ def docker_exec(container, command, capture_output=True):
return _run(f"docker exec -u root {PREFIX}_{container}_1 {command}", capture_output)
-def docker_exec_cvat(command: Union[List[str], str]):
+def docker_exec_cvat(command: Union[list[str], str]):
base = f"docker exec {PREFIX}_cvat_server_1"
_command = f"{base} {command}" if isinstance(command, str) else base.split() + command
return _run(_command)
-def kube_exec_cvat(command: Union[List[str], str]):
+def kube_exec_cvat(command: Union[list[str], str]):
pod_name = _kube_get_server_pod_name()
base = f"kubectl exec {pod_name} --"
_command = f"{base} {command}" if isinstance(command, str) else base.split() + command
diff --git a/tests/python/shared/utils/config.py b/tests/python/shared/utils/config.py
index f313334c797d..e65ac0b904a5 100644
--- a/tests/python/shared/utils/config.py
+++ b/tests/python/shared/utils/config.py
@@ -2,9 +2,9 @@
#
# SPDX-License-Identifier: MIT
+from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
-from typing import Generator
import requests
from cvat_sdk.api_client import ApiClient, Configuration
diff --git a/tests/python/shared/utils/helpers.py b/tests/python/shared/utils/helpers.py
index 14015f4b2ad3..4855796a0a86 100644
--- a/tests/python/shared/utils/helpers.py
+++ b/tests/python/shared/utils/helpers.py
@@ -3,9 +3,10 @@
# SPDX-License-Identifier: MIT
import subprocess
+from collections.abc import Generator
from contextlib import closing
from io import BytesIO
-from typing import Generator, List, Optional, Tuple
+from typing import Optional
import av
import av.video.reformatter
@@ -27,10 +28,10 @@ def generate_image_file(filename="image.png", size=(100, 50), color=(0, 0, 0)):
def generate_image_files(
count: int,
*,
- prefixes: Optional[List[str]] = None,
- filenames: Optional[List[str]] = None,
- sizes: Optional[List[Tuple[int, int]]] = None,
-) -> List[BytesIO]:
+ prefixes: Optional[list[str]] = None,
+ filenames: Optional[list[str]] = None,
+ sizes: Optional[list[tuple[int, int]]] = None,
+) -> list[BytesIO]:
assert not (prefixes and filenames), "prefixes cannot be used together with filenames"
assert not prefixes or len(prefixes) == count
assert not filenames or len(filenames) == count
diff --git a/tests/python/shared/utils/resource_import_export.py b/tests/python/shared/utils/resource_import_export.py
index 37983dbd1478..c8b4fd7ca93b 100644
--- a/tests/python/shared/utils/resource_import_export.py
+++ b/tests/python/shared/utils/resource_import_export.py
@@ -1,10 +1,10 @@
import functools
import json
-from abc import ABC, abstractstaticmethod
+from abc import ABC, abstractmethod
from contextlib import ExitStack
from http import HTTPStatus
from time import sleep
-from typing import Any, Dict, Optional, TypeVar
+from typing import Any, Optional, TypeVar
import pytest
@@ -17,7 +17,7 @@
IMPORT_FORMAT = "CVAT 1.1"
-def _make_custom_resource_params(resource: str, obj: str, cloud_storage_id: int) -> Dict[str, Any]:
+def _make_custom_resource_params(resource: str, obj: str, cloud_storage_id: int) -> dict[str, Any]:
return {
"filename": FILENAME_TEMPLATE.format(obj, resource),
"location": "cloud_storage",
@@ -25,7 +25,7 @@ def _make_custom_resource_params(resource: str, obj: str, cloud_storage_id: int)
}
-def _make_default_resource_params(resource: str, obj: str) -> Dict[str, Any]:
+def _make_default_resource_params(resource: str, obj: str) -> dict[str, Any]:
return {
"filename": FILENAME_TEMPLATE.format(obj, resource),
}
@@ -33,7 +33,7 @@ def _make_default_resource_params(resource: str, obj: str) -> Dict[str, Any]:
def _make_export_resource_params(
resource: str, is_default: bool = True, **kwargs
-) -> Dict[str, Any]:
+) -> dict[str, Any]:
func = _make_default_resource_params if is_default else _make_custom_resource_params
params = func(resource, **kwargs)
if resource != "backup":
@@ -43,7 +43,7 @@ def _make_export_resource_params(
def _make_import_resource_params(
resource: str, is_default: bool = True, **kwargs
-) -> Dict[str, Any]:
+) -> dict[str, Any]:
func = _make_default_resource_params if is_default else _make_custom_resource_params
params = func(resource, **kwargs)
if resource != "backup":
@@ -52,7 +52,8 @@ def _make_import_resource_params(
class _CloudStorageResourceTest(ABC):
- @abstractstaticmethod
+ @staticmethod
+ @abstractmethod
def _make_client():
pass
@@ -64,7 +65,7 @@ def setup(self, admin_user: str):
with self.exit_stack:
yield
- def _ensure_file_created(self, func: T, storage: Dict[str, Any]) -> T:
+ def _ensure_file_created(self, func: T, storage: dict[str, Any]) -> T:
@functools.wraps(func)
def wrapper(*args, **kwargs):
filename = kwargs["filename"]
@@ -219,7 +220,7 @@ def _import_dataset_from_cloud_storage(
response = get_method(user, url, action="import_status", rq_id=rq_id)
status = response.status_code
- def _import_resource(self, cloud_storage: Dict[str, Any], resource_type: str, *args, **kwargs):
+ def _import_resource(self, cloud_storage: dict[str, Any], resource_type: str, *args, **kwargs):
methods = {
"annotations": self._import_annotations_from_cloud_storage,
"dataset": self._import_dataset_from_cloud_storage,
@@ -234,7 +235,7 @@ def _import_resource(self, cloud_storage: Dict[str, Any], resource_type: str, *a
return methods[resource_type](*args, **kwargs)
- def _export_resource(self, cloud_storage: Dict[str, Any], *args, **kwargs):
+ def _export_resource(self, cloud_storage: dict[str, Any], *args, **kwargs):
org_id = cloud_storage["organization"]
if org_id:
kwargs.setdefault("org_id", org_id)
diff --git a/utils/__init__.py b/utils/__init__.py
index d0af4b967942..6370694b5ea2 100644
--- a/utils/__init__.py
+++ b/utils/__init__.py
@@ -1,4 +1,3 @@
# Copyright (C) 2022 Intel Corporation
#
# SPDX-License-Identifier: MIT
-
diff --git a/utils/dicom_converter/script.py b/utils/dicom_converter/script.py
index 23a1e7526e3a..3fe7ef0be6dd 100644
--- a/utils/dicom_converter/script.py
+++ b/utils/dicom_converter/script.py
@@ -16,10 +16,20 @@
# Script configuration
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
-parser = argparse.ArgumentParser(description='The script is used to convert some kinds of DICOM (.dcm) files to regular image files (.png)')
-parser.add_argument('input', type=str, help='A root directory with medical data files in DICOM format. The script finds all these files based on their extension')
-parser.add_argument('output', type=str, help='Where to save converted files. The script repeats internal directories structure of the input root directory')
+logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
+parser = argparse.ArgumentParser(
+ description="The script is used to convert some kinds of DICOM (.dcm) files to regular image files (.png)"
+)
+parser.add_argument(
+ "input",
+ type=str,
+ help="A root directory with medical data files in DICOM format. The script finds all these files based on their extension",
+)
+parser.add_argument(
+ "output",
+ type=str,
+ help="Where to save converted files. The script repeats internal directories structure of the input root directory",
+)
args = parser.parse_args()
@@ -32,11 +42,11 @@ def __init__(self, filename):
self._max_value = ds.pixel_array.max()
self._depth = ds.BitsStored
- logging.debug('File: {}'.format(filename))
- logging.debug('Photometric interpretation: {}'.format(self._photometric_interpretation))
- logging.debug('Min value: {}'.format(self._min_value))
- logging.debug('Max value: {}'.format(self._max_value))
- logging.debug('Depth: {}'.format(self._depth))
+ logging.debug("File: {}".format(filename))
+ logging.debug("Photometric interpretation: {}".format(self._photometric_interpretation))
+ logging.debug("Min value: {}".format(self._min_value))
+ logging.debug("Max value: {}".format(self._max_value))
+ logging.debug("Depth: {}".format(self._depth))
try:
self._length = ds["NumberOfFrames"].value
@@ -53,38 +63,40 @@ def __iter__(self):
for pixel_array in self._pixel_array:
# Normalization to an output range 0..255, 0..65535
pixel_array = pixel_array - self._min_value
- pixel_array = pixel_array.astype(int) * (2 ** self._depth - 1)
+ pixel_array = pixel_array.astype(int) * (2**self._depth - 1)
pixel_array = pixel_array // (self._max_value - self._min_value)
# In some cases we need to convert colors additionally
- if 'YBR' in self._photometric_interpretation:
- pixel_array = convert_color_space(pixel_array, self._photometric_interpretation, 'RGB')
+ if "YBR" in self._photometric_interpretation:
+ pixel_array = convert_color_space(
+ pixel_array, self._photometric_interpretation, "RGB"
+ )
if self._depth == 8:
image = Image.fromarray(pixel_array.astype(np.uint8))
elif self._depth == 16:
image = Image.fromarray(pixel_array.astype(np.uint16))
else:
- raise Exception('Not supported depth {}'.format(self._depth))
+ raise Exception("Not supported depth {}".format(self._depth))
yield image
def main(root_dir, output_root_dir):
- dicom_files = glob(os.path.join(root_dir, '**', '*.dcm'), recursive = True)
+ dicom_files = glob(os.path.join(root_dir, "**", "*.dcm"), recursive=True)
if not len(dicom_files):
- logging.info('DICOM files are not found under the specified path')
+ logging.info("DICOM files are not found under the specified path")
else:
- logging.info('Number of found DICOM files: ' + str(len(dicom_files)))
+ logging.info("Number of found DICOM files: " + str(len(dicom_files)))
pbar = tqdm(dicom_files)
for input_filename in pbar:
- pbar.set_description('Conversion: ' + input_filename)
+ pbar.set_description("Conversion: " + input_filename)
input_basename = os.path.basename(input_filename)
output_subpath = os.path.relpath(os.path.dirname(input_filename), root_dir)
output_path = os.path.join(output_root_dir, output_subpath)
- output_basename = '{}.png'.format(os.path.splitext(input_basename)[0])
+ output_basename = "{}.png".format(os.path.splitext(input_basename)[0])
output_filename = os.path.join(output_path, output_basename)
if not os.path.exists(output_path):
@@ -98,16 +110,19 @@ def main(root_dir, output_root_dir):
image.save(output_filename)
else:
filename_index = str(i).zfill(len(str(length)))
- list_output_filename = '{}_{}.png'.format(os.path.splitext(output_filename)[0], filename_index)
+ list_output_filename = "{}_{}.png".format(
+ os.path.splitext(output_filename)[0], filename_index
+ )
image.save(list_output_filename)
except Exception as ex:
- logging.error('Error while processing ' + input_filename)
+ logging.error("Error while processing " + input_filename)
logging.error(ex)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
input_root_path = os.path.abspath(args.input.rstrip(os.sep))
output_root_path = os.path.abspath(args.output.rstrip(os.sep))
- logging.info('From: {}'.format(input_root_path))
- logging.info('To: {}'.format(output_root_path))
+ logging.info("From: {}".format(input_root_path))
+ logging.info("To: {}".format(output_root_path))
main(input_root_path, output_root_path)