diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml
index f3ec1d48262..a74f70c5437 100644
--- a/.github/workflows/black.yml
+++ b/.github/workflows/black.yml
@@ -5,38 +5,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - id: files
- uses: tj-actions/changed-files@v41.0.0
- with:
- files: |
- cvat-sdk/**/*.py
- cvat-cli/**/*.py
- tests/python/**/*.py
- cvat/apps/quality_control/**/*.py
- cvat/apps/analytics_report/**/*.py
- dir_names: true
- name: Run checks
- env:
- PR_FILES_AM: ${{ steps.files.outputs.added_modified }}
- PR_FILES_RENAMED: ${{ steps.files.outputs.renamed }}
run: |
- # If different modules use different Black configs,
- # we need to run Black for each python component group separately.
- # Otherwise, they all will use the same config.
+ pipx install $(grep "^black" ./cvat-cli/requirements/development.txt)
- UPDATED_DIRS="${{steps.files.outputs.all_changed_files}}"
+ echo "Black version: $(black --version)"
- if [[ ! -z $UPDATED_DIRS ]]; then
- pipx install $(egrep "black.*" ./cvat-cli/requirements/development.txt)
-
- echo "Black version: "$(black --version)
- echo "The dirs will be checked: $UPDATED_DIRS"
- EXIT_CODE=0
- for DIR in $UPDATED_DIRS; do
- black --check --diff $DIR || EXIT_CODE=$(($? | $EXIT_CODE)) || true
- done
- exit $EXIT_CODE
- else
- echo "No files with the \"py\" extension found"
- fi
+ black --check --diff .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b9a4fd9584..42d2893080b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+
+## \[2.21.3\] - 2024-10-31
+
+### Changed
+
+- CLI no longer prints the stack trace in case of HTTP errors
+ ()
+
+### Removed
+
+- Dropped support for Python 3.8 since its EOL was on 2024-10-07
+ ()
+
+### Fixed
+
+- Requests page crush with `Cannot read property 'target' of undefined` error
+ ()
+
+- Tags in ground truth job were displayed as `tag (GT)`
+ ()
+
+- Tags in ground truth job couldn't be deleted via `x` button
+ ()
+
+- Exception 'Canvas is busy' when change frame during drag/resize a track
+ ()
+
+- A shape gets shifted if auto save triggered during dragging
+ ()
+
## \[2.21.2\] - 2024-10-24
diff --git a/changelog.d/20241018_142148_klakhov_hide_mask.md b/changelog.d/20241018_142148_klakhov_hide_mask.md
new file mode 100644
index 00000000000..4c79bfdacfa
--- /dev/null
+++ b/changelog.d/20241018_142148_klakhov_hide_mask.md
@@ -0,0 +1,3 @@
+### Added
+
+- Feature to hide a mask during editing ()
diff --git a/changelog.d/20241022_121638_klakhov_fix_request_status_crush.md b/changelog.d/20241022_121638_klakhov_fix_request_status_crush.md
deleted file mode 100644
index 082b59a70d4..00000000000
--- a/changelog.d/20241022_121638_klakhov_fix_request_status_crush.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Fixed
-
-- Requests page crush with `Cannot read property 'target' of undefined` error
- ()
diff --git a/changelog.d/20241023_120236_klakhov_improve_gt_tags.md b/changelog.d/20241023_120236_klakhov_improve_gt_tags.md
deleted file mode 100644
index 79c1e724a35..00000000000
--- a/changelog.d/20241023_120236_klakhov_improve_gt_tags.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### Fixed
-
-- Tags in ground truth job were displayed as `tag (GT)`
- ()
-
-- Tags in ground truth job couldn't be deleted via `x` button
- ()
diff --git a/changelog.d/20241028_140908_sekachev.bs.md b/changelog.d/20241028_140908_sekachev.bs.md
deleted file mode 100644
index 507346e3d05..00000000000
--- a/changelog.d/20241028_140908_sekachev.bs.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Fixed
-
-- Exception 'Canvas is busy' when change frame during drag/resize a track
- ()
diff --git a/changelog.d/20241028_140945_sekachev.bs.md b/changelog.d/20241028_140945_sekachev.bs.md
deleted file mode 100644
index fd401e390a1..00000000000
--- a/changelog.d/20241028_140945_sekachev.bs.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Fixed
-
-- A shape gets shifted if auto save triggered during dragging
- ()
diff --git a/changelog.d/20241029_105216_maria_do_not_export_honeypots_when_exporting_project.md b/changelog.d/20241029_105216_maria_do_not_export_honeypots_when_exporting_project.md
new file mode 100644
index 00000000000..e6d8950a55b
--- /dev/null
+++ b/changelog.d/20241029_105216_maria_do_not_export_honeypots_when_exporting_project.md
@@ -0,0 +1,4 @@
+### Fixed
+
+- Exporting projects with tasks containing honeypots. Honeypots are no longer exported.
+ ()
diff --git a/changelog.d/20241029_113229_maria_drop_python_3_8_support.md b/changelog.d/20241029_113229_maria_drop_python_3_8_support.md
deleted file mode 100644
index ca57734aa1d..00000000000
--- a/changelog.d/20241029_113229_maria_drop_python_3_8_support.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Removed
-
-- Dropped support for Python 3.8 since its EOL was on 2024-10-07
- ()
diff --git a/changelog.d/20241029_120317_dmitrii.lavrukhin_remove_business.md b/changelog.d/20241029_120317_dmitrii.lavrukhin_remove_business.md
new file mode 100644
index 00000000000..654fc263dbb
--- /dev/null
+++ b/changelog.d/20241029_120317_dmitrii.lavrukhin_remove_business.md
@@ -0,0 +1,4 @@
+### Removed
+
+- Removed unused business group
+ ()
diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json
index 2b24ff47e34..c89e7506854 100644
--- a/cvat-canvas/package.json
+++ b/cvat-canvas/package.json
@@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
- "version": "2.20.9",
+ "version": "2.20.10",
"type": "module",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts
index 2c7a1f08d20..0ad62484c14 100644
--- a/cvat-canvas/src/typescript/canvasModel.ts
+++ b/cvat-canvas/src/typescript/canvasModel.ts
@@ -96,6 +96,7 @@ export interface Configuration {
controlPointsSize?: number;
outlinedBorders?: string | false;
resetZoom?: boolean;
+ hideEditedObject?: boolean;
}
export interface BrushTool {
@@ -416,6 +417,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
textPosition: consts.DEFAULT_SHAPE_TEXT_POSITION,
textContent: consts.DEFAULT_SHAPE_TEXT_CONTENT,
undefinedAttrValue: consts.DEFAULT_UNDEFINED_ATTR_VALUE,
+ hideEditedObject: false,
},
imageBitmap: false,
image: null,
@@ -981,6 +983,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.CSSImageFilter = configuration.CSSImageFilter;
}
+ if (typeof configuration.hideEditedObject === 'boolean') {
+ this.data.configuration.hideEditedObject = configuration.hideEditedObject;
+ }
+
this.notify(UpdateReasons.CONFIG_UPDATED);
}
diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts
index b7e9cbb9013..77b674dec05 100644
--- a/cvat-canvas/src/typescript/drawHandler.ts
+++ b/cvat-canvas/src/typescript/drawHandler.ts
@@ -5,7 +5,7 @@
import * as SVG from 'svg.js';
import 'svg.draw.js';
-import './svg.patch';
+import { CIRCLE_STROKE } from './svg.patch';
import { AutoborderHandler } from './autoborderHandler';
import {
@@ -104,6 +104,7 @@ export class DrawHandlerImpl implements DrawHandler {
private controlPointsSize: number;
private selectedShapeOpacity: number;
private outlinedBorders: string;
+ private isHidden: boolean;
// we should use any instead of SVG.Shape because svg plugins cannot change declared interface
// so, methods like draw() just undefined for SVG.Shape, but nevertheless they exist
@@ -1276,6 +1277,7 @@ export class DrawHandlerImpl implements DrawHandler {
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.outlinedBorders = configuration.outlinedBorders || 'black';
this.autobordersEnabled = false;
+ this.isHidden = false;
this.startTimestamp = Date.now();
this.onDrawDoneDefault = onDrawDone;
this.canvas = canvas;
@@ -1301,10 +1303,28 @@ export class DrawHandlerImpl implements DrawHandler {
});
}
+ private strokePoint(point: SVG.Element): void {
+ point.attr('stroke', this.isHidden ? 'none' : CIRCLE_STROKE);
+ point.fill({ opacity: this.isHidden ? 0 : 1 });
+ }
+
+ private updateHidden(value: boolean) {
+ this.isHidden = value;
+
+ if (value) {
+ this.canvas.attr('pointer-events', 'none');
+ } else {
+ this.canvas.attr('pointer-events', 'all');
+ }
+ }
+
public configurate(configuration: Configuration): void {
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.outlinedBorders = configuration.outlinedBorders || 'black';
+ if (this.isHidden !== configuration.hideEditedObject) {
+ this.updateHidden(configuration.hideEditedObject);
+ }
const isFillableRect = this.drawData &&
this.drawData.shapeType === 'rectangle' &&
@@ -1315,15 +1335,26 @@ export class DrawHandlerImpl implements DrawHandler {
const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon';
if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) {
- this.drawInstance.fill({ opacity: configuration.selectedShapeOpacity });
+ this.drawInstance.fill({
+ opacity: configuration.hideEditedObject ? 0 : configuration.selectedShapeOpacity,
+ });
+ }
+
+ if (this.drawInstance && (isFilalblePolygon)) {
+ const paintHandler = this.drawInstance.remember('_paintHandler');
+ if (paintHandler) {
+ for (const point of (paintHandler as any).set.members) {
+ this.strokePoint(point);
+ }
+ }
}
if (this.drawInstance && this.drawInstance.attr('stroke')) {
- this.drawInstance.attr('stroke', this.outlinedBorders);
+ this.drawInstance.attr('stroke', configuration.hideEditedObject ? 'none' : this.outlinedBorders);
}
if (this.pointsGroup && this.pointsGroup.attr('stroke')) {
- this.pointsGroup.attr('stroke', this.outlinedBorders);
+ this.pointsGroup.attr('stroke', configuration.hideEditedObject ? 'none' : this.outlinedBorders);
}
this.autobordersEnabled = configuration.autoborders;
@@ -1369,6 +1400,7 @@ export class DrawHandlerImpl implements DrawHandler {
const paintHandler = this.drawInstance.remember('_paintHandler');
for (const point of (paintHandler as any).set.members) {
+ this.strokePoint(point);
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
point.attr('r', `${this.controlPointsSize / geometry.scale}`);
}
diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts
index 567eea29c7d..84ecb1684ad 100644
--- a/cvat-canvas/src/typescript/editHandler.ts
+++ b/cvat-canvas/src/typescript/editHandler.ts
@@ -472,7 +472,7 @@ export class EditHandlerImpl implements EditHandler {
const paintHandler = this.editLine.remember('_paintHandler');
- for (const point of (paintHandler as any).set.members) {
+ for (const point of paintHandler.set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
point.attr('r', `${this.controlPointsSize / geometry.scale}`);
}
diff --git a/cvat-canvas/src/typescript/masksHandler.ts b/cvat-canvas/src/typescript/masksHandler.ts
index cdaa4d86d2f..ca6e5e469a6 100644
--- a/cvat-canvas/src/typescript/masksHandler.ts
+++ b/cvat-canvas/src/typescript/masksHandler.ts
@@ -6,7 +6,7 @@ import { fabric } from 'fabric';
import debounce from 'lodash/debounce';
import {
- DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy,
+ DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy, Position,
} from './canvasModel';
import consts from './consts';
import { DrawHandler } from './drawHandler';
@@ -61,10 +61,11 @@ export class MasksHandlerImpl implements MasksHandler {
private editData: MasksEditData | null;
private colorBy: ColorBy;
- private latestMousePos: { x: number; y: number; };
+ private latestMousePos: Position;
private startTimestamp: number;
private geometry: Geometry;
private drawingOpacity: number;
+ private isHidden: boolean;
private keepDrawnPolygon(): void {
const canvasWrapper = this.canvas.getElement().parentElement;
@@ -217,12 +218,29 @@ export class MasksHandlerImpl implements MasksHandler {
private imageDataFromCanvas(wrappingBBox: WrappingBBox): Uint8ClampedArray {
const imageData = this.canvas.toCanvasElement()
.getContext('2d').getImageData(
- wrappingBBox.left, wrappingBBox.top,
- wrappingBBox.right - wrappingBBox.left + 1, wrappingBBox.bottom - wrappingBBox.top + 1,
+ wrappingBBox.left,
+ wrappingBBox.top,
+ wrappingBBox.right - wrappingBBox.left + 1,
+ wrappingBBox.bottom - wrappingBBox.top + 1,
).data;
return imageData;
}
+ private updateHidden(value: boolean) {
+ this.isHidden = value;
+
+ // Need to update style of upper canvas explicitly because update of default cursor is not applied immediately
+ // https://github.com/fabricjs/fabric.js/issues/1456
+ const newOpacity = value ? '0' : '';
+ const newCursor = value ? 'inherit' : 'none';
+ this.canvas.getElement().parentElement.style.opacity = newOpacity;
+ const upperCanvas = this.canvas.getElement().parentElement.querySelector('.upper-canvas') as HTMLElement;
+ if (upperCanvas) {
+ upperCanvas.style.cursor = newCursor;
+ }
+ this.canvas.defaultCursor = newCursor;
+ }
+
private updateBrushTools(brushTool?: BrushTool, opts: Partial = {}): void {
if (this.isPolygonDrawing) {
// tool was switched from polygon to brush for example
@@ -350,6 +368,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.editData = null;
this.drawingOpacity = 0.5;
this.brushMarker = null;
+ this.isHidden = false;
this.colorBy = ColorBy.LABEL;
this.onDrawDone = onDrawDone;
this.onDrawRepeat = onDrawRepeat;
@@ -452,7 +471,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.canvas.renderAll();
}
- if (isMouseDown && !isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) {
+ if (isMouseDown && !this.isHidden && !isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) {
const color = fabric.Color.fromHex(tool.color);
color.setAlpha(tool.type === 'eraser' ? 1 : 0.5);
@@ -530,6 +549,10 @@ export class MasksHandlerImpl implements MasksHandler {
public configurate(configuration: Configuration): void {
this.colorBy = configuration.colorBy;
+
+ if (this.isHidden !== configuration.hideEditedObject) {
+ this.updateHidden(configuration.hideEditedObject);
+ }
}
public transform(geometry: Geometry): void {
@@ -563,7 +586,10 @@ export class MasksHandlerImpl implements MasksHandler {
const color = fabric.Color.fromHex(this.getStateColor(drawData.initialState)).getSource();
const [left, top, right, bottom] = points.slice(-4);
const imageBitmap = expandChannels(color[0], color[1], color[2], points);
- imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1,
+ imageDataToDataURL(
+ imageBitmap,
+ right - left + 1,
+ bottom - top + 1,
(dataURL: string) => new Promise((resolve) => {
fabric.Image.fromURL(dataURL, (image: fabric.Image) => {
try {
@@ -654,7 +680,10 @@ export class MasksHandlerImpl implements MasksHandler {
const color = fabric.Color.fromHex(this.getStateColor(editData.state)).getSource();
const [left, top, right, bottom] = points.slice(-4);
const imageBitmap = expandChannels(color[0], color[1], color[2], points);
- imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1,
+ imageDataToDataURL(
+ imageBitmap,
+ right - left + 1,
+ bottom - top + 1,
(dataURL: string) => new Promise((resolve) => {
fabric.Image.fromURL(dataURL, (image: fabric.Image) => {
try {
diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts
index 40af155a956..7b728b27433 100644
--- a/cvat-canvas/src/typescript/svg.patch.ts
+++ b/cvat-canvas/src/typescript/svg.patch.ts
@@ -86,6 +86,7 @@ SVG.Element.prototype.draw.extend(
}),
);
+export const CIRCLE_STROKE = '#000';
// Fix method drawCircles
function drawCircles(): void {
const array = this.el.array().valueOf();
@@ -109,6 +110,7 @@ function drawCircles(): void {
.circle(5)
.stroke({
width: 1,
+ color: CIRCLE_STROKE,
})
.fill('#ccc')
.center(p.x, p.y),
diff --git a/cvat-cli/src/cvat_cli/__main__.py b/cvat-cli/src/cvat_cli/__main__.py
index 2448587245f..b18c8d8bb75 100755
--- a/cvat-cli/src/cvat_cli/__main__.py
+++ b/cvat-cli/src/cvat_cli/__main__.py
@@ -9,6 +9,7 @@
from types import SimpleNamespace
from typing import List
+import urllib3.exceptions
from cvat_sdk import exceptions
from cvat_sdk.core.client import Client, Config
@@ -70,7 +71,7 @@ def main(args: List[str] = None):
try:
cli = CLI(client=client, credentials=parsed_args.auth)
actions[parsed_args.action](cli, **vars(action_args))
- except exceptions.ApiException as e:
+ except (exceptions.ApiException, urllib3.exceptions.HTTPError) as e:
logger.critical(e)
return 1
diff --git a/cvat-core/src/server-response-types.ts b/cvat-core/src/server-response-types.ts
index 58b950c670b..53438ed65d2 100644
--- a/cvat-core/src/server-response-types.ts
+++ b/cvat-core/src/server-response-types.ts
@@ -47,7 +47,7 @@ export interface SerializedUser {
first_name: string;
last_name: string;
email?: string;
- groups?: ('user' | 'business' | 'admin')[];
+ groups?: ('user' | 'admin')[];
is_staff?: boolean;
is_superuser?: boolean;
is_active?: boolean;
diff --git a/cvat-core/src/user.ts b/cvat-core/src/user.ts
index 6d7366151fb..ef28f3633f0 100644
--- a/cvat-core/src/user.ts
+++ b/cvat-core/src/user.ts
@@ -11,7 +11,7 @@ export default class User {
public readonly email: string;
public readonly firstName: string;
public readonly lastName: string;
- public readonly groups: ('user' | 'business' | 'admin')[];
+ public readonly groups: ('user' | 'admin')[];
public readonly lastLogin: string;
public readonly dateJoined: string;
public readonly isStaff: boolean;
diff --git a/cvat-sdk/cvat_sdk/core/client.py b/cvat-sdk/cvat_sdk/core/client.py
index add7ccb5f3d..0ae0b88ecad 100644
--- a/cvat-sdk/cvat_sdk/core/client.py
+++ b/cvat-sdk/cvat_sdk/core/client.py
@@ -10,7 +10,7 @@
from contextlib import contextmanager, suppress
from pathlib import Path
from time import sleep
-from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, TypeVar
+from typing import Any, Dict, Generator, Optional, Sequence, Tuple, TypeVar
import attrs
import packaging.specifiers as specifiers
@@ -121,7 +121,7 @@ def organization_slug(self, org_slug: Optional[str]):
self.api_client.default_headers[self._ORG_SLUG_HEADER] = org_slug
@contextmanager
- def organization_context(self, slug: str) -> Iterator[None]:
+ def organization_context(self, slug: str) -> Generator[None, None, None]:
prev_slug = self.organization_slug
self.organization_slug = slug
try:
diff --git a/cvat-sdk/cvat_sdk/core/progress.py b/cvat-sdk/cvat_sdk/core/progress.py
index 7fd2d13a2cd..fd844de722a 100644
--- a/cvat-sdk/cvat_sdk/core/progress.py
+++ b/cvat-sdk/cvat_sdk/core/progress.py
@@ -6,7 +6,7 @@
from __future__ import annotations
import contextlib
-from typing import ContextManager, Iterable, Optional, TypeVar
+from typing import Generator, Iterable, Optional, TypeVar
T = TypeVar("T")
@@ -26,7 +26,7 @@ class ProgressReporter:
"""
@contextlib.contextmanager
- def task(self, **kwargs) -> ContextManager[None]:
+ def task(self, **kwargs) -> Generator[None, None, None]:
"""
Returns a context manager that represents a long-running task
for which progress can be reported.
diff --git a/cvat-sdk/cvat_sdk/core/utils.py b/cvat-sdk/cvat_sdk/core/utils.py
index 0706a2eec61..1ef434e3ad5 100644
--- a/cvat-sdk/cvat_sdk/core/utils.py
+++ b/cvat-sdk/cvat_sdk/core/utils.py
@@ -13,7 +13,7 @@
BinaryIO,
ContextManager,
Dict,
- Iterator,
+ Generator,
Literal,
Sequence,
TextIO,
@@ -43,7 +43,7 @@ def atomic_writer(
@contextlib.contextmanager
def atomic_writer(
path: Union[os.PathLike, str], mode: Literal["w", "wb"], encoding: str = "UTF-8"
-) -> Iterator[IO]:
+) -> Generator[IO, None, None]:
"""
Returns a context manager that, when entered, returns a handle to a temporary
file opened with the specified `mode` and `encoding`. If the context manager
diff --git a/cvat-ui/package.json b/cvat-ui/package.json
index 2c43904a3fb..fe2e65f809b 100644
--- a/cvat-ui/package.json
+++ b/cvat-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
- "version": "1.66.2",
+ "version": "1.66.3",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts
index 670ace099e5..68df5e9eb73 100644
--- a/cvat-ui/src/actions/annotation-actions.ts
+++ b/cvat-ui/src/actions/annotation-actions.ts
@@ -126,6 +126,8 @@ export enum AnnotationActionTypes {
COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE',
COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS',
ACTIVATE_OBJECT = 'ACTIVATE_OBJECT',
+ UPDATE_EDITED_STATE = 'UPDATE_EDITED_STATE',
+ HIDE_ACTIVE_OBJECT = 'HIDE_ACTIVE_OBJECT',
REMOVE_OBJECT = 'REMOVE_OBJECT',
REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS',
REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED',
@@ -1320,7 +1322,7 @@ export function searchAnnotationsAsync(
};
}
-const ShapeTypeToControl: Record = {
+export const ShapeTypeToControl: Record = {
[ShapeType.RECTANGLE]: ActiveControl.DRAW_RECTANGLE,
[ShapeType.POLYLINE]: ActiveControl.DRAW_POLYLINE,
[ShapeType.POLYGON]: ActiveControl.DRAW_POLYGON,
@@ -1608,3 +1610,50 @@ export function restoreFrameAsync(frame: number): ThunkAction {
}
};
}
+
+export function changeHideActiveObjectAsync(hide: boolean): ThunkAction {
+ return async (dispatch: ThunkDispatch, getState): Promise => {
+ const state = getState();
+ const { instance: canvas } = state.annotation.canvas;
+ if (canvas) {
+ (canvas as Canvas).configure({
+ hideEditedObject: hide,
+ });
+
+ const { objectState } = state.annotation.editing;
+ if (objectState) {
+ objectState.hidden = hide;
+ await dispatch(updateAnnotationsAsync([objectState]));
+ }
+
+ dispatch({
+ type: AnnotationActionTypes.HIDE_ACTIVE_OBJECT,
+ payload: {
+ hide,
+ },
+ });
+ }
+ };
+}
+
+export function updateEditedStateAsync(objectState: ObjectState | null): ThunkAction {
+ return async (dispatch: ThunkDispatch, getState): Promise => {
+ let newActiveObjectHidden = false;
+ if (objectState) {
+ newActiveObjectHidden = objectState.hidden;
+ }
+
+ dispatch({
+ type: AnnotationActionTypes.UPDATE_EDITED_STATE,
+ payload: {
+ objectState,
+ },
+ });
+
+ const state = getState();
+ const { activeObjectHidden } = state.annotation.canvas;
+ if (activeObjectHidden !== newActiveObjectHidden) {
+ dispatch(changeHideActiveObjectAsync(newActiveObjectHidden));
+ }
+ };
+}
diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx
index 6c140438c20..b6a43ce20cf 100644
--- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx
+++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx
@@ -6,9 +6,9 @@ import './brush-toolbox-styles.scss';
import React, { useCallback, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
-import { useDispatch, useSelector } from 'react-redux';
+import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Button from 'antd/lib/button';
-import Icon, { VerticalAlignBottomOutlined } from '@ant-design/icons';
+import Icon, { EyeInvisibleFilled, EyeOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons';
import InputNumber from 'antd/lib/input-number';
import Select from 'antd/lib/select';
import notification from 'antd/lib/notification';
@@ -23,7 +23,7 @@ import {
import CVATTooltip from 'components/common/cvat-tooltip';
import { CombinedState, ObjectType, ShapeType } from 'reducers';
import LabelSelector from 'components/label-selector/label-selector';
-import { rememberObject, updateCanvasBrushTools } from 'actions/annotation-actions';
+import { changeHideActiveObjectAsync, rememberObject, updateCanvasBrushTools } from 'actions/annotation-actions';
import { ShortcutScope } from 'utils/enums';
import GlobalHotKeys from 'utils/mousetrap-react';
import { subKeyMap } from 'utils/component-subkeymap';
@@ -71,12 +71,17 @@ registerComponentShortcuts(componentShortcuts);
const MIN_BRUSH_SIZE = 1;
function BrushTools(): React.ReactPortal | null {
const dispatch = useDispatch();
- const defaultLabelID = useSelector((state: CombinedState) => state.annotation.drawing.activeLabelID);
- const config = useSelector((state: CombinedState) => state.annotation.canvas.brushTools);
- const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance);
- const labels = useSelector((state: CombinedState) => state.annotation.job.labels);
- const { keyMap, normalizedKeyMap } = useSelector((state: CombinedState) => state.shortcuts);
- const { visible } = config;
+ const {
+ defaultLabelID, visible, canvasInstance, labels, activeObjectHidden, keyMap, normalizedKeyMap,
+ } = useSelector((state: CombinedState) => ({
+ defaultLabelID: state.annotation.drawing.activeLabelID,
+ visible: state.annotation.canvas.brushTools.visible,
+ canvasInstance: state.annotation.canvas.instance,
+ labels: state.annotation.job.labels,
+ activeObjectHidden: state.annotation.canvas.activeObjectHidden,
+ keyMap: state.shortcuts.keyMap,
+ normalizedKeyMap: state.shortcuts.normalizedKeyMap,
+ }), shallowEqual);
const [editableState, setEditableState] = useState(null);
const [currentTool, setCurrentTool] = useState<'brush' | 'eraser' | 'polygon-plus' | 'polygon-minus'>('brush');
@@ -103,6 +108,10 @@ function BrushTools(): React.ReactPortal | null {
}
}, [setCurrentTool, blockedTools['polygon-minus']]);
+ const hideMask = useCallback((hide: boolean) => {
+ dispatch(changeHideActiveObjectAsync(hide));
+ }, []);
+
const handlers: Record void> = {
ACTIVATE_BRUSH_TOOL_STANDARD_CONTROLS: setBrushTool,
ACTIVATE_ERASER_TOOL_STANDARD_CONTROLS: setEraserTool,
@@ -365,6 +374,14 @@ function BrushTools(): React.ReactPortal | null {
icon={}
onClick={() => setRemoveUnderlyingPixels(!removeUnderlyingPixels)}
/>
+
+ : }
+ onClick={() => hideMask(!activeObjectHidden)}
+ />
+
{ !editableState && !!applicableLabels.length && (
{
private onCanvasShapeDrawn = (event: any): void => {
const {
jobInstance, activeLabelID, activeObjectType, frame, updateActiveControl, onCreateAnnotations,
+ onUpdateEditedObject, activeObjectHidden, workspace,
} = this.props;
if (!event.detail.continue) {
@@ -649,6 +659,7 @@ class CanvasWrapperComponent extends React.PureComponent {
state.rotation = state.rotation || 0;
state.occluded = state.occluded || false;
state.outside = state.outside || false;
+ state.hidden = state.hidden || (activeObjectHidden && workspace !== Workspace.SINGLE_SHAPE);
if (state.shapeType === ShapeType.SKELETON && Array.isArray(state.elements)) {
state.elements.forEach((element: Record) => {
element.objectType = state.objectType;
@@ -669,6 +680,7 @@ class CanvasWrapperComponent extends React.PureComponent {
const objectState = new cvat.classes.ObjectState(state);
onCreateAnnotations([objectState]);
+ onUpdateEditedObject(null);
};
private onCanvasObjectsMerged = (event: any): void => {
@@ -829,13 +841,16 @@ class CanvasWrapperComponent extends React.PureComponent {
}
};
- private onCanvasEditStart = (): void => {
- const { updateActiveControl } = this.props;
+ private onCanvasEditStart = (event: any): void => {
+ const { updateActiveControl, onUpdateEditedObject } = this.props;
updateActiveControl(ActiveControl.EDIT);
+ onUpdateEditedObject(event.detail.state);
};
private onCanvasEditDone = (event: any): void => {
- const { activeControl, onUpdateAnnotations, updateActiveControl } = this.props;
+ const {
+ activeControl, onUpdateAnnotations, updateActiveControl, onUpdateEditedObject,
+ } = this.props;
const { state, points, rotation } = event.detail;
if (state.rotation !== rotation) {
state.rotation = rotation;
@@ -848,6 +863,7 @@ class CanvasWrapperComponent extends React.PureComponent {
updateActiveControl(ActiveControl.CURSOR);
}
onUpdateAnnotations([state]);
+ onUpdateEditedObject(null);
};
private onCanvasSliceDone = (event: any): void => {
@@ -887,8 +903,9 @@ class CanvasWrapperComponent extends React.PureComponent {
};
private onCanvasCancel = (): void => {
- const { onResetCanvas } = this.props;
+ const { onResetCanvas, onUpdateEditedObject } = this.props;
onResetCanvas();
+ onUpdateEditedObject(null);
};
private onCanvasFindObject = async (e: any): Promise => {
diff --git a/cvat-ui/src/components/annotation-page/single-shape-workspace/single-shape-sidebar/single-shape-sidebar.tsx b/cvat-ui/src/components/annotation-page/single-shape-workspace/single-shape-sidebar/single-shape-sidebar.tsx
index 0a587c812b8..7f0aa28b31e 100644
--- a/cvat-ui/src/components/annotation-page/single-shape-workspace/single-shape-sidebar/single-shape-sidebar.tsx
+++ b/cvat-ui/src/components/annotation-page/single-shape-workspace/single-shape-sidebar/single-shape-sidebar.tsx
@@ -18,13 +18,18 @@ import Alert from 'antd/lib/alert';
import Button from 'antd/lib/button';
import message from 'antd/lib/message';
-import { CombinedState, NavigationType, ObjectType } from 'reducers';
+import {
+ ActiveControl, CombinedState, NavigationType, ObjectType,
+} from 'reducers';
import { Canvas, CanvasMode } from 'cvat-canvas-wrapper';
-import { Job, Label, LabelType } from 'cvat-core-wrapper';
+import {
+ Job, Label, LabelType, ShapeType,
+} from 'cvat-core-wrapper';
import { ActionUnion, createAction } from 'utils/redux';
import {
rememberObject, changeFrameAsync, setNavigationType,
removeObjectAsync, finishCurrentJobAsync,
+ changeHideActiveObjectAsync, updateActiveControl, ShapeTypeToControl,
} from 'actions/annotation-actions';
import LabelSelector from 'components/label-selector/label-selector';
import GlobalHotKeys from 'utils/mousetrap-react';
@@ -185,6 +190,12 @@ const componentShortcuts = {
sequences: ['del', 'shift+del'],
scope: ShortcutScope.SINGLE_SHAPE_ANNOTATION_WORKSPACE,
},
+ HIDE_MASK_SINGLE_SHAPE: {
+ name: 'Hide mask',
+ description: 'Hide currently edited mask',
+ sequences: ['h'],
+ scope: ShortcutScope.SINGLE_SHAPE_ANNOTATION_WORKSPACE,
+ },
};
registerComponentShortcuts(componentShortcuts);
@@ -203,6 +214,9 @@ function SingleShapeSidebar(): JSX.Element {
navigationType,
annotations,
activatedStateID,
+ editedState,
+ activeControl,
+ activeObjectHidden,
} = useSelector((_state: CombinedState) => ({
isCanvasReady: _state.annotation.canvas.ready,
jobInstance: _state.annotation.job.instance as Job,
@@ -214,6 +228,9 @@ function SingleShapeSidebar(): JSX.Element {
navigationType: _state.annotation.player.navigationType,
annotations: _state.annotation.annotations.states,
activatedStateID: _state.annotation.annotations.activatedStateID,
+ editedState: _state.annotation.editing.objectState,
+ activeControl: _state.annotation.canvas.activeControl,
+ activeObjectHidden: _state.annotation.canvas.activeObjectHidden,
}), shallowEqual);
const [state, dispatch] = useReducer(reducer, {
@@ -234,6 +251,9 @@ function SingleShapeSidebar(): JSX.Element {
canvasInitializerRef.current = (): void => {
const canvas = store.getState().annotation.canvas.instance as Canvas;
if (isCanvasReady && canvas.mode() !== CanvasMode.DRAW && state.label && state.labelType !== LabelType.ANY) {
+ appDispatch(updateActiveControl(
+ ShapeTypeToControl[state.labelType],
+ ));
// we remember active object type and active label
// to assign these values in default drawdone event listener
appDispatch(rememberObject({
@@ -388,6 +408,13 @@ function SingleShapeSidebar(): JSX.Element {
appDispatch(removeObjectAsync(objectStateToRemove, event?.shiftKey || false));
}
},
+ HIDE_MASK_SINGLE_SHAPE: (event: KeyboardEvent | undefined) => {
+ event?.preventDefault();
+ if (editedState?.shapeType === ShapeType.MASK || activeControl === ActiveControl.DRAW_MASK) {
+ const hide = editedState ? !editedState.hidden : !activeObjectHidden;
+ appDispatch(changeHideActiveObjectAsync(hide));
+ }
+ },
};
if (!state.labels.length) {
@@ -484,7 +511,7 @@ function SingleShapeSidebar(): JSX.Element {
Press
{` ${
- normalizedKeyMap.SWITCH_DRAW_MODE_SINGLE_SHAPE_CONTROLS
+ normalizedKeyMap.SWITCH_DRAW_MODE_SINGLE_SHAPE
} `}
to finish drawing process
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 61f6c1064c6..9e274804729 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 7f120cc981d..16ccdc08bff 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/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts
index 847a0b84d93..b7fd37d2880 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,
@@ -94,6 +95,9 @@ const defaultState: AnnotationState = {
activeLabelID: null,
activeObjectType: ObjectType.SHAPE,
},
+ editing: {
+ objectState: null,
+ },
annotations: {
activatedStateID: null,
activatedElementID: null,
@@ -633,6 +637,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 +1004,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 +1016,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 6c297cd5f4a..8d333c07bf0 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';
@@ -694,6 +694,10 @@ export enum NavigationType {
EMPTY = 'empty',
}
+export interface EditingState {
+ objectState: ObjectState | null;
+}
+
export interface AnnotationState {
activities: {
loads: {
@@ -719,6 +723,7 @@ export interface AnnotationState {
instance: Canvas | Canvas3d | null;
ready: boolean;
activeControl: ActiveControl;
+ activeObjectHidden: boolean;
};
job: {
openTime: null | number;
@@ -768,6 +773,7 @@ export interface AnnotationState {
activeObjectType: ObjectType;
activeInitialState?: any;
};
+ editing: EditingState;
annotations: {
activatedStateID: number | null;
activatedElementID: number | null;
diff --git a/cvat/__init__.py b/cvat/__init__.py
index d72cb8e0099..88392366440 100644
--- a/cvat/__init__.py
+++ b/cvat/__init__.py
@@ -4,6 +4,6 @@
from cvat.utils.version import get_version
-VERSION = (2, 22, 0, 'alpha', 0)
+VERSION = (2, 22, 0, "alpha", 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 706d6e701db..87910192779 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 35d4b902a53..1c70520a709 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 4651fd39845..03ef389599e 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 3350a4180ef..059a45f6df2 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/media_extractors.py b/cvat/apps/engine/media_extractors.py
index a64637359ff..c923083b18b 100644
--- a/cvat/apps/engine/media_extractors.py
+++ b/cvat/apps/engine/media_extractors.py
@@ -539,7 +539,9 @@ def extract(self):
class _AvVideoReading:
@contextmanager
- def read_av_container(self, source: Union[str, io.BytesIO]) -> av.container.InputContainer:
+ def read_av_container(
+ self, source: Union[str, io.BytesIO]
+ ) -> Generator[av.container.InputContainer, None, None]:
if isinstance(source, io.BytesIO):
source.seek(0) # required for re-reading
diff --git a/cvat/apps/engine/rules/annotationguides.rego b/cvat/apps/engine/rules/annotationguides.rego
index dd512af6d79..6429eecb23a 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 3e278a35a7d..04f8e0e4536 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 019a5ebcecc..9384d829b09 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 803dab16c01..d8a487cbcdb 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 8068f7d6fdf..7980a08d1bc 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 a5029637768..1d4344da7fe 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 8e40ddc43c8..bdaabb12013 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 bfe3b47a0d4..6833826a076 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 99d126d2b44..f020cf4ac97 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 4cf56274167..1dbfcc1167f 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 63460df540b..4a4941e0fd1 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 f36c8a7dfa0..a13a1897c66 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 0a35d83880e..53213eb39d2 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 ca799f953cd..e36f8c8ec7b 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 6657f21d299..d4a7259893f 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 8e9b57a814d..c2b4195191a 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 61da5c8520d..30925fcee18 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 595cbaae4ee..a609492868f 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 63469228e11..34cb0f4866d 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 07e01461ab7..e6ed6b6c030 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 0152ec721ba..58ec43763b2 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 da9d54d79e2..dee2d4a6896 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 00000000000..2bf1a56b406
--- /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 00000000000..bd6d6576ecf
--- /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/rules/utils.rego b/cvat/apps/iam/rules/utils.rego
index 4bd64f0ae10..148b2ec1a2a 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 2829860c093..7b3b6c82897 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 5a669c5f49f..94f694988a3 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 57c74cf2c52..794ef8cefab 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 c50cf9302c6..d25aa7fe275 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 b43a9c1b111..f40653f63e2 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 a581e0716e5..7ff4ea28047 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 7e40f092607..95d566e4b93 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 3a51f76128e..2e15ba4a863 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 c23f3039ff1..09752e4b700 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 24643feab70..6d0a8c29c19 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 c3ba86abb75..bf7edec5071 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 b86548142da..c74a4a7c992 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 a6c111bfef4..d2a8a6fb653 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 45ddbc95f47..0a3885bb00a 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/rules/conflicts.rego b/cvat/apps/quality_control/rules/conflicts.rego
index f8e570b5882..88349112820 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 e9dd28b3ec3..98626a5f0ca 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 1fc587159ee..0b2f6b149e7 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/webhooks/rules/tests/generators/webhooks_test.gen.rego.py b/cvat/apps/webhooks/rules/tests/generators/webhooks_test.gen.rego.py
index c367a42cc98..66417f3d096 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 a74a88c6a96..85d577a21ee 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 44ddd0d8713..2fbe40a8d4c 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 cd8c1ac7422..a12bcaaaedd 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 d368a1ef262..8a3e187b74b 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/settings/base.py b/cvat/settings/base.py
index f439000f9d2..404628fa555 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 144ed619f76..08257a14b81 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 caf2e859a53..72c93eaeaf8 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 b2ed89a5d55..2cb1b7498b3 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 b4d01baf3c3..bc6ef40ae0e 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 ecc79eea705..8b1b53a1038 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 d417bcd669f..e837842efaf 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 6cdaf313f96..ed8d08a40f4 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/pyproject.toml b/pyproject.toml
index 6d077245157..528bdc579fc 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 82b7499baf8..5ae76e64a7f 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 25af0b0e8f8..2eca3a94133 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 c1b6be282f2..c57824d13fa 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 08182f80a22..bb72a99af89 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/process_sdk_docs.py b/site/process_sdk_docs.py
index 3b194124841..03324aea691 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/tests/cypress.config.js b/tests/cypress.config.js
index da1f20d8dee..17f157c59c8 100644
--- a/tests/cypress.config.js
+++ b/tests/cypress.config.js
@@ -1,3 +1,7 @@
+// Copyright (C) 2024 CVAT.ai Corporation
+//
+// SPDX-License-Identifier: MIT
+
const { defineConfig } = require('cypress');
const baseConfig = require('./cypress.base.config');
diff --git a/tests/cypress/e2e/actions_objects/regression_tests.js b/tests/cypress/e2e/actions_objects/regression_tests.js
index 8cf00b90e0c..7bf11c7b0d7 100644
--- a/tests/cypress/e2e/actions_objects/regression_tests.js
+++ b/tests/cypress/e2e/actions_objects/regression_tests.js
@@ -9,9 +9,9 @@ context('Regression tests', () => {
let jobID = null;
const taskPayload = {
- name: 'Test annotations actions',
+ name: 'Regression tests',
labels: [{
- name: 'label 1',
+ name: 'car',
attributes: [],
type: 'any',
}],
@@ -29,12 +29,9 @@ context('Regression tests', () => {
};
const rectanglePayload = {
- frame: 99,
- objectType: 'shape',
shapeType: 'rectangle',
- points: [250, 64, 491, 228],
occluded: false,
- labelName: 'label 1',
+ labelName: taskPayload.labels[0].name,
};
before(() => {
@@ -45,41 +42,65 @@ context('Regression tests', () => {
taskID = response.taskID;
[jobID] = response.jobIDs;
- cy.headlessCreateObjects([rectanglePayload], jobID);
- cy.visit(`/tasks/${taskID}/jobs/${jobID}`);
+ cy.headlessCreateObjects([
+ {
+ ...rectanglePayload, frame: 99, points: [250, 64, 491, 228], objectType: 'shape',
+ },
+ {
+ ...rectanglePayload, frame: 0, points: [10, 10, 30, 30], objectType: 'track',
+ },
+ ], jobID);
});
});
- describe('Regression tests', () => {
+ describe('UI does not crash', () => {
+ beforeEach(() => {
+ cy.visit(`/tasks/${taskID}/jobs/${jobID}`);
+ cy.get('.cvat-canvas-container').should('not.exist');
+ cy.get('.cvat-canvas-container').should('exist').and('be.visible');
+ });
+
it('UI does not crash if to activate an object while frame fetching', () => {
- cy.reload();
cy.intercept('GET', '/api/jobs/**/data?**', (req) => {
req.continue((res) => {
- res.setDelay(1000);
+ res.setDelay(3000);
});
}).as('delayedRequest');
+
cy.get('.cvat-player-last-button').click();
- cy.get('#cvat_canvas_shape_1').trigger('mousemove');
- cy.get('#cvat_canvas_shape_1').should('not.have.class', 'cvat_canvas_shape_activated');
+ cy.get('#cvat-objects-sidebar-state-item-1').trigger('mousemove');
+ cy.get('#cvat-objects-sidebar-state-item-1').should('not.have.class', 'cvat-objects-sidebar-state-active-item');
cy.wait('@delayedRequest');
cy.get('#cvat_canvas_shape_1').trigger('mousemove');
cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated');
});
+
+ it('UI does not crash if to navigate during an element resizing (issue 1922)', { scrollBehavior: false }, () => {
+ cy.get('#cvat_canvas_shape_2').then(([el]) => {
+ const rect = el.getBoundingClientRect();
+
+ cy.get('body').trigger('mousemove', rect.x + rect.width / 2, rect.y + rect.height / 2);
+ cy.get('#cvat_canvas_shape_2').should('have.class', 'cvat_canvas_shape_activated');
+
+ cy.get('body').trigger('mousedown', rect.right, rect.bottom, { button: 0 });
+ cy.get('body').trigger('mousemove', rect.right + 100, rect.bottom + 100);
+
+ cy.get('body').type('f'); // go to next frame
+ cy.get('body').trigger('mouseup');
+
+ // Page with the error is missing
+ cy.get('.cvat-global-boundary').should('not.exist');
+ cy.checkFrameNum(0);
+ });
+ });
});
after(() => {
+ if (taskID !== null) {
+ cy.headlessDeleteTask(taskID);
+ }
cy.logout();
- cy.getAuthKey().then((response) => {
- const authKey = response.body.key;
- cy.request({
- method: 'DELETE',
- url: `/api/tasks/${taskID}`,
- headers: {
- Authorization: `Token ${authKey}`,
- },
- });
- });
});
});
diff --git a/tests/cypress/e2e/features/masks_basics.js b/tests/cypress/e2e/features/masks_basics.js
index b39d6ea769d..3e119e97f03 100644
--- a/tests/cypress/e2e/features/masks_basics.js
+++ b/tests/cypress/e2e/features/masks_basics.js
@@ -156,6 +156,12 @@ context('Manipulations with masks', { scrollBehavior: false }, () => {
cy.interactAnnotationObjectMenu('#cvat-objects-sidebar-state-item-1', 'Edit');
cy.drawMask(editingActions);
+
+ // Check issue fixed in https://github.com/cvat-ai/cvat/pull/8598
+ // Frames navigation should not work during editing
+ cy.get('.cvat-player-next-button').click();
+ cy.checkFrameNum(0);
+
cy.finishMaskDrawing();
});
diff --git a/tests/cypress/e2e/features/requests_page.js b/tests/cypress/e2e/features/requests_page.js
index 3cdf187a982..8622092b89d 100644
--- a/tests/cypress/e2e/features/requests_page.js
+++ b/tests/cypress/e2e/features/requests_page.js
@@ -357,5 +357,24 @@ context('Requests page', () => {
});
});
});
+
+ it('Export task. Request for status fails, UI is not crushing', () => {
+ cy.intercept('GET', '/api/requests/**', {
+ statusCode: 500,
+ body: 'Network error',
+ });
+
+ cy.exportTask({
+ type: 'annotations',
+ format: exportFormat,
+ archiveCustomName: annotationsArchiveNameLocal,
+ });
+
+ cy.contains('Could not export dataset').should('be.visible');
+ cy.closeNotification('.ant-notification-notice-error');
+
+ cy.contains('.cvat-header-button', 'Requests').should('be.visible').click();
+ cy.get('.cvat-requests-page').should('be.visible');
+ });
});
});
diff --git a/tests/cypress/e2e/issues_prs/issue_1922_error_canvas_is_busy_at_resize_element.js b/tests/cypress/e2e/issues_prs/issue_1922_error_canvas_is_busy_at_resize_element.js
deleted file mode 100644
index 53b5606f457..00000000000
--- a/tests/cypress/e2e/issues_prs/issue_1922_error_canvas_is_busy_at_resize_element.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2020-2022 Intel Corporation
-// Copyright (C) 2023 CVAT.ai Corporation
-//
-// SPDX-License-Identifier: MIT
-
-///
-
-import { taskName, labelName } from '../../support/const';
-
-context('Check error canvas is busy at resize element', () => {
- const issueId = '1922';
- const createRectangleShape2Points = {
- points: 'By 2 Points',
- type: 'Shape',
- labelName,
- firstX: 100,
- firstY: 100,
- secondX: 300,
- secondY: 300,
- };
-
- before(() => {
- cy.openTaskJob(taskName);
- });
-
- describe(`Testing issue "${issueId}"`, () => {
- it('Create an object in first frame', () => {
- cy.createRectangle(createRectangleShape2Points);
- });
-
- it('Go to next frame and create an object in second frame', () => {
- cy.get('.cvat-player-next-button').click();
- cy.createRectangle(createRectangleShape2Points);
- });
-
- it('Switching mode of button on "back with a filter"', () => {
- cy.get('.cvat-player-previous-button').rightclick();
- cy.get('.cvat-player-previous-filtered-inlined-button').click();
- });
-
- it('Resize element on second frame and go to previous frame at resizing element', () => {
- const { secondX, secondY } = createRectangleShape2Points;
- cy.get('.cvat-canvas-container').trigger('mousemove', secondX - 10, secondY - 10); // activate second shape
- cy.get('.cvat-canvas-container').trigger('mousedown', secondX, secondY, { button: 0 });
- cy.get('.cvat-canvas-container').trigger('mousemove', secondX + 100, secondY + 100);
- cy.get('body').type('d'); // go to previous frame
- cy.get('body').trigger('mouseup');
- });
-
- it('Page with the error is missing', () => {
- cy.get('.cvat-global-boundary').should('not.exist');
- });
- });
-});
diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js
index 76f7ffe2640..d3988c2e56a 100644
--- a/tests/cypress/support/commands.js
+++ b/tests/cypress/support/commands.js
@@ -1272,7 +1272,7 @@ Cypress.Commands.add('exportTask', ({
cy.get('.cvat-cloud-storage-select-provider').click();
}
}
- cy.contains('button', 'OK').click();
+ cy.contains('.cvat-modal-export-task button', 'OK').click();
cy.get('.cvat-notification-notice-export-task-start').should('be.visible');
cy.closeNotification('.cvat-notification-notice-export-task-start');
});
@@ -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/cypress_canvas3d.config.js b/tests/cypress_canvas3d.config.js
index e1cd5ede69f..f542fe78bde 100644
--- a/tests/cypress_canvas3d.config.js
+++ b/tests/cypress_canvas3d.config.js
@@ -1,3 +1,7 @@
+// Copyright (C) 2024 CVAT.ai Corporation
+//
+// SPDX-License-Identifier: MIT
+
const { defineConfig } = require('cypress');
const baseConfig = require('./cypress.base.config');
diff --git a/tests/python/cli/self-signed.crt b/tests/python/cli/self-signed.crt
new file mode 100644
index 00000000000..815373bf286
--- /dev/null
+++ b/tests/python/cli/self-signed.crt
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPDCB76ADAgECAhQksQwFGcyVwF0+gIOPMPBB+/NjNTAFBgMrZXAwFDESMBAG
+A1UEAwwJbG9jYWxob3N0MB4XDTI0MTAyODEyMTkyNFoXDTI0MTAyOTEyMTkyNFow
+FDESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAzGOv96vkrHr0GPcWL7vN
+8mgR4XMg9ItNpJ2nbMmjYCKjUzBRMB0GA1UdDgQWBBR6Hn0aG/ZGAJjY9HIUK7El
+84qAgzAfBgNVHSMEGDAWgBR6Hn0aG/ZGAJjY9HIUK7El84qAgzAPBgNVHRMBAf8E
+BTADAQH/MAUGAytlcANBAMj2zWdIa8oOiEtUWFMv+KYf1kyP1lUnlcC2xUpOj8d3
+kRYtlRX4E7F5zzzgKgNpbanRAg72qnqPiFAFCGVAhgY=
+-----END CERTIFICATE-----
diff --git a/tests/python/cli/self-signed.key b/tests/python/cli/self-signed.key
new file mode 100644
index 00000000000..f81d8519bca
--- /dev/null
+++ b/tests/python/cli/self-signed.key
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIKe5zj/UrVJ/LySjKm9BBVHXziqFIwJ6w+HuTHnldCLo
+-----END PRIVATE KEY-----
diff --git a/tests/python/cli/test_cli.py b/tests/python/cli/test_cli.py
index 364c7011e7c..d6b19cfe0a3 100644
--- a/tests/python/cli/test_cli.py
+++ b/tests/python/cli/test_cli.py
@@ -10,7 +10,6 @@
import packaging.version as pv
import pytest
-from cvat_cli.cli import CLI
from cvat_sdk import Client, make_client
from cvat_sdk.api_client import exceptions, models
from cvat_sdk.core.proxies.tasks import ResourceType, Task
@@ -20,7 +19,7 @@
from shared.utils.config import BASE_URL, USER_PASS
from shared.utils.helpers import generate_image_file
-from .util import generate_images, run_cli
+from .util import generate_images, https_reverse_proxy, run_cli
class TestCLI:
@@ -243,23 +242,26 @@ def mocked_version(_):
assert "Server version '0' is not compatible with SDK version" in caplog.text
@pytest.mark.parametrize("verify", [True, False])
- def test_can_control_ssl_verification_with_arg(self, monkeypatch, verify: bool):
- # TODO: Very hacky implementation, improve it, if possible
- class MyException(Exception):
- pass
-
- normal_init = CLI.__init__
-
- def my_init(self, *args, **kwargs):
- normal_init(self, *args, **kwargs)
- raise MyException(self.client.api_client.configuration.verify_ssl)
-
- monkeypatch.setattr(CLI, "__init__", my_init)
-
- with pytest.raises(MyException) as capture:
- self.run_cli(*(["--insecure"] if not verify else []), "ls")
-
- assert capture.value.args[0] == verify
+ def test_can_control_ssl_verification_with_arg(self, verify: bool):
+ with https_reverse_proxy() as proxy_url:
+ if verify:
+ insecure_args = []
+ else:
+ insecure_args = ["--insecure"]
+
+ run_cli(
+ self,
+ f"--auth={self.user}:{self.password}",
+ f"--server-host={proxy_url}",
+ *insecure_args,
+ "ls",
+ expected_code=1 if verify else 0,
+ )
+ stdout = self.stdout.getvalue()
+
+ if not verify:
+ for line in stdout.splitlines():
+ int(line)
def test_can_control_organization_context(self):
org = "cli-test-org"
diff --git a/tests/python/cli/util.py b/tests/python/cli/util.py
index 034d5d073ac..ff1173fa4a8 100644
--- a/tests/python/cli/util.py
+++ b/tests/python/cli/util.py
@@ -3,10 +3,17 @@
# SPDX-License-Identifier: MIT
+import contextlib
+import http.server
+import ssl
+import threading
import unittest
from pathlib import Path
-from typing import Any, List, Union
+from typing import Any, Dict, Generator, List, Union
+import requests
+
+from shared.utils.config import BASE_URL
from shared.utils.helpers import generate_image_file
@@ -29,3 +36,50 @@ def generate_images(dst_dir: Path, count: int) -> List[Path]:
filename.write_bytes(generate_image_file(filename.name).getvalue())
filenames.append(filename)
return filenames
+
+
+@contextlib.contextmanager
+def https_reverse_proxy() -> Generator[str, None, None]:
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
+ cert_dir = Path(__file__).parent
+ ssl_context.load_cert_chain(cert_dir / "self-signed.crt", cert_dir / "self-signed.key")
+
+ with http.server.HTTPServer(("localhost", 0), _ProxyHttpRequestHandler) as proxy_server:
+ proxy_server.socket = ssl_context.wrap_socket(
+ proxy_server.socket,
+ server_side=True,
+ )
+ server_thread = threading.Thread(target=proxy_server.serve_forever)
+ server_thread.start()
+ try:
+ yield f"https://localhost:{proxy_server.server_port}"
+ finally:
+ proxy_server.shutdown()
+ server_thread.join()
+
+
+class _ProxyHttpRequestHandler(http.server.BaseHTTPRequestHandler):
+ def do_GET(self):
+ response = requests.get(**self._shared_request_args())
+ self._translate_response(response)
+
+ def do_POST(self):
+ body_length = int(self.headers["Content-Length"])
+
+ 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]:
+ headers = {k.lower(): v for k, v in self.headers.items()}
+ del headers["host"]
+
+ return {"url": BASE_URL + self.path, "headers": headers, "timeout": 60, "stream": True}
+
+ def _translate_response(self, response: requests.Response) -> None:
+ self.send_response(response.status_code)
+ for key, value in response.headers.items():
+ self.send_header(key, value)
+ self.end_headers()
+ # Need to use raw here to prevent requests from handling Content-Encoding.
+ self.wfile.write(response.raw.read())
diff --git a/tests/python/rest_api/test_analytics.py b/tests/python/rest_api/test_analytics.py
index f14cdd206f8..68671889a21 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_cloud_storages.py b/tests/python/rest_api/test_cloud_storages.py
index 9fc1739b9e0..ce2db93cab5 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 c6c043f2e44..51fd2041c00 100644
--- a/tests/python/rest_api/test_issues.py
+++ b/tests/python/rest_api/test_issues.py
@@ -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),
diff --git a/tests/python/rest_api/test_jobs.py b/tests/python/rest_api/test_jobs.py
index 6d5626fcda9..5bb545497a8 100644
--- a/tests/python/rest_api/test_jobs.py
+++ b/tests/python/rest_api/test_jobs.py
@@ -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),
@@ -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_memberships.py b/tests/python/rest_api/test_memberships.py
index e03cac2e277..dbb2863260f 100644
--- a/tests/python/rest_api/test_memberships.py
+++ b/tests/python/rest_api/test_memberships.py
@@ -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)
diff --git a/tests/python/rest_api/test_organizations.py b/tests/python/rest_api/test_organizations.py
index 5daee9e5353..50834b1fff8 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 b0c8a3b247c..73e3c02ad28 100644
--- a/tests/python/rest_api/test_projects.py
+++ b/tests/python/rest_api/test_projects.py
@@ -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)
@@ -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)
@@ -1038,6 +1045,61 @@ 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:
diff --git a/tests/python/rest_api/test_requests.py b/tests/python/rest_api/test_requests.py
index f06e97ae7fb..1d076bf7f58 100644
--- a/tests/python/rest_api/test_requests.py
+++ b/tests/python/rest_api/test_requests.py
@@ -29,6 +29,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):
diff --git a/tests/python/rest_api/test_tasks.py b/tests/python/rest_api/test_tasks.py
index c57dec13f63..6f5bd04de7e 100644
--- a/tests/python/rest_api/test_tasks.py
+++ b/tests/python/rest_api/test_tasks.py
@@ -141,7 +141,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 +349,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 +509,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),
diff --git a/tests/python/rest_api/test_webhooks.py b/tests/python/rest_api/test_webhooks.py
index 3c528bc78c1..778eda8430e 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/shared/assets/cloudstorages.json b/tests/python/shared/assets/cloudstorages.json
index 4cda853ec93..8d8d92009aa 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 f1f7457eae7..4681af9bd7d 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 7bb20c4507c..43b972c79e4 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",
@@ -19010,7 +19002,7 @@
"user"
],
"object_id": "10",
- "object_repr": "business1",
+ "object_repr": "user7",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19028,7 +19020,7 @@
"user"
],
"object_id": "10",
- "object_repr": "business1",
+ "object_repr": "user7",
"action_flag": 2,
"change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\"]}}]"
}
@@ -19046,7 +19038,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 +19056,7 @@
"user"
],
"object_id": "11",
- "object_repr": "business2",
+ "object_repr": "user8",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19082,7 +19074,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 +19092,7 @@
"user"
],
"object_id": "12",
- "object_repr": "business3",
+ "object_repr": "user9",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19118,7 +19110,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 +19128,7 @@
"user"
],
"object_id": "13",
- "object_repr": "business4",
+ "object_repr": "user10",
"action_flag": 1,
"change_message": "[{\"added\": {}}]"
}
@@ -19154,7 +19146,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 6b0f2452820..9a58f4bbee0 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 9aff3cf4b63..e719b30ac11 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 9ae6bcc8d95..3c0be8035d2 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 ad26620a27e..8106c5b8b6a 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 f7c0c25b464..19d345ee4e3 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/tasks.json b/tests/python/shared/assets/tasks.json
index 5a28176ef5e..cf2d63da785 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 d3a420b297a..9c4dce1e4fd 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 da5b0f6837d..b6a90828ee3 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/utils/__init__.py b/utils/__init__.py
index d0af4b96794..6370694b5ea 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 23a1e7526e3..3fe7ef0be6d 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)