312 lines
10 KiB
Python
312 lines
10 KiB
Python
# Copyright (C) CVAT.ai Corporation
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import io
|
|
from logging import Logger
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import pytest
|
|
from cvat_sdk import Client, models
|
|
from cvat_sdk.api_client import exceptions
|
|
from cvat_sdk.core.proxies.projects import Project
|
|
from cvat_sdk.core.proxies.tasks import Task
|
|
from cvat_sdk.core.proxies.types import Location
|
|
from cvat_sdk.core.utils import filter_dict
|
|
from PIL import Image
|
|
from pytest_cases import fixture_ref, parametrize
|
|
|
|
from shared.fixtures.data import CloudStorageAssets
|
|
from shared.utils.config import IMPORT_EXPORT_BUCKET_ID
|
|
|
|
from .common import TestDatasetExport
|
|
from .util import make_pbar
|
|
|
|
|
|
class TestProjectUsecases(TestDatasetExport):
|
|
@pytest.fixture(autouse=True)
|
|
def setup(
|
|
self,
|
|
tmp_path: Path,
|
|
fxt_login: tuple[Client, str],
|
|
fxt_logger: tuple[Logger, io.StringIO],
|
|
fxt_stdout: io.StringIO,
|
|
restore_redis_ondisk_per_function,
|
|
):
|
|
self.tmp_path = tmp_path
|
|
logger, self.logger_stream = fxt_logger
|
|
self.stdout = fxt_stdout
|
|
self.client, self.user = fxt_login
|
|
self.client.logger = logger
|
|
|
|
api_client = self.client.api_client
|
|
for k in api_client.configuration.logger:
|
|
api_client.configuration.logger[k] = logger
|
|
|
|
@pytest.fixture
|
|
def fxt_task_with_shapes(self, fxt_new_task: Task):
|
|
labels = fxt_new_task.get_labels()
|
|
fxt_new_task.set_annotations(
|
|
models.LabeledDataRequest(
|
|
shapes=[
|
|
models.LabeledShapeRequest(
|
|
frame=0,
|
|
label_id=labels[0].id,
|
|
type="rectangle",
|
|
points=[1, 1, 2, 2],
|
|
),
|
|
],
|
|
)
|
|
)
|
|
|
|
return fxt_new_task
|
|
|
|
@pytest.fixture
|
|
def fxt_new_project(self):
|
|
project = self.client.projects.create(
|
|
spec={
|
|
"name": "test_project",
|
|
"labels": [{"name": "car"}, {"name": "person"}],
|
|
},
|
|
)
|
|
|
|
return project
|
|
|
|
@pytest.fixture
|
|
def fxt_new_project_with_target_storage(self):
|
|
project = self.client.projects.create(
|
|
spec={
|
|
"name": "test_project",
|
|
"labels": [{"name": "car"}, {"name": "person"}],
|
|
"target_storage": {
|
|
"location": Location.CLOUD_STORAGE,
|
|
"cloud_storage_id": IMPORT_EXPORT_BUCKET_ID,
|
|
},
|
|
},
|
|
)
|
|
|
|
return project
|
|
|
|
@pytest.fixture
|
|
def fxt_empty_project(self):
|
|
return self.client.projects.create(spec={"name": "test_project"})
|
|
|
|
@pytest.fixture
|
|
def fxt_project_with_shapes(self, fxt_task_with_shapes: Task):
|
|
project = self.client.projects.create(
|
|
spec=models.ProjectWriteRequest(
|
|
name="test_project",
|
|
labels=[
|
|
models.PatchedLabelRequest(
|
|
**filter_dict(label.to_dict(), drop=["id", "has_parent"])
|
|
)
|
|
for label in fxt_task_with_shapes.get_labels()
|
|
],
|
|
)
|
|
)
|
|
fxt_task_with_shapes.update(models.PatchedTaskWriteRequest(project_id=project.id))
|
|
project.fetch()
|
|
return project
|
|
|
|
@pytest.fixture
|
|
def fxt_backup_file(self, fxt_project_with_shapes: Project):
|
|
backup_path = self.tmp_path / "backup.zip"
|
|
|
|
fxt_project_with_shapes.download_backup(str(backup_path))
|
|
|
|
yield backup_path
|
|
|
|
def test_can_create_empty_project(self):
|
|
project = self.client.projects.create(spec=models.ProjectWriteRequest(name="test project"))
|
|
|
|
assert project.id != 0
|
|
assert project.name == "test project"
|
|
|
|
def test_can_create_project_with_attribute_with_blank_default(self):
|
|
project = self.client.projects.create(
|
|
spec=models.ProjectWriteRequest(
|
|
name="test project",
|
|
labels=[
|
|
models.PatchedLabelRequest(
|
|
name="text",
|
|
attributes=[
|
|
models.AttributeRequest(
|
|
name="text",
|
|
mutable=True,
|
|
input_type=models.InputTypeEnum("text"),
|
|
values=[],
|
|
default_value="",
|
|
)
|
|
],
|
|
)
|
|
],
|
|
)
|
|
)
|
|
|
|
labels = project.get_labels()
|
|
assert labels[0].attributes[0].default_value == ""
|
|
|
|
def test_can_create_project_from_dataset(self, fxt_coco_dataset: Path):
|
|
pbar_out = io.StringIO()
|
|
pbar = make_pbar(file=pbar_out)
|
|
|
|
project = self.client.projects.create_from_dataset(
|
|
spec=models.ProjectWriteRequest(name="project with data"),
|
|
dataset_path=fxt_coco_dataset,
|
|
dataset_format="COCO 1.0",
|
|
pbar=pbar,
|
|
)
|
|
|
|
assert project.get_tasks()[0].size == 1
|
|
assert "100%" in pbar_out.getvalue().strip("\r").split("\r")[-1]
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
@pytest.mark.parametrize("convert", [True, False])
|
|
def test_can_create_project_from_dataset_with_polygons_to_masks_param(
|
|
self, fxt_camvid_dataset: Path, convert: bool
|
|
):
|
|
pbar_out = io.StringIO()
|
|
pbar = make_pbar(file=pbar_out)
|
|
project = self.client.projects.create_from_dataset(
|
|
spec=models.ProjectWriteRequest(name="project with data"),
|
|
dataset_path=fxt_camvid_dataset,
|
|
dataset_format="CamVid 1.0",
|
|
conv_mask_to_poly=convert,
|
|
pbar=pbar,
|
|
)
|
|
|
|
assert project.get_tasks()[0].size == 1
|
|
assert "100%" in pbar_out.getvalue().strip("\r").split("\r")[-1]
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
task = project.get_tasks()[0]
|
|
imported_annotations = task.get_annotations()
|
|
assert all(
|
|
[s.type.value == "polygon" if convert else "mask" for s in imported_annotations.shapes]
|
|
)
|
|
|
|
def test_can_retrieve_project(self, fxt_new_project: Project):
|
|
project_id = fxt_new_project.id
|
|
|
|
project = self.client.projects.retrieve(project_id)
|
|
|
|
assert project.id == project_id
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_list_projects(self, fxt_new_project: Project):
|
|
project_id = fxt_new_project.id
|
|
|
|
projects = self.client.projects.list()
|
|
|
|
assert any(p.id == project_id for p in projects)
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_update_project(self, fxt_new_project: Project):
|
|
fxt_new_project.update(models.PatchedProjectWriteRequest(name="foo"))
|
|
|
|
retrieved_project = self.client.projects.retrieve(fxt_new_project.id)
|
|
assert retrieved_project.name == "foo"
|
|
assert fxt_new_project.name == retrieved_project.name
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_delete_project(self, fxt_new_project: Project):
|
|
fxt_new_project.remove()
|
|
|
|
with pytest.raises(exceptions.NotFoundException):
|
|
fxt_new_project.fetch()
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_get_tasks(self, fxt_project_with_shapes: Project):
|
|
tasks = fxt_project_with_shapes.get_tasks()
|
|
|
|
assert len(tasks) == 1
|
|
assert tasks[0].project_id == fxt_project_with_shapes.id
|
|
|
|
def test_can_get_labels(self, fxt_project_with_shapes: Project):
|
|
expected_labels = {"car", "person"}
|
|
|
|
received_labels = fxt_project_with_shapes.get_labels()
|
|
|
|
assert {obj.name for obj in received_labels} == expected_labels
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_download_backup(self, fxt_project_with_shapes: Project):
|
|
pbar_out = io.StringIO()
|
|
pbar = make_pbar(file=pbar_out)
|
|
backup_path = self.tmp_path / "backup.zip"
|
|
|
|
fxt_project_with_shapes.download_backup(str(backup_path), pbar=pbar)
|
|
|
|
assert backup_path.stat().st_size > 0
|
|
assert "100%" in pbar_out.getvalue().strip("\r").split("\r")[-1]
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
def test_can_create_from_backup(self, fxt_backup_file: Path):
|
|
pbar_out = io.StringIO()
|
|
pbar = make_pbar(file=pbar_out)
|
|
|
|
restored_project = self.client.projects.create_from_backup(fxt_backup_file, pbar=pbar)
|
|
|
|
assert restored_project.get_tasks()[0].size == 1
|
|
assert "100%" in pbar_out.getvalue().strip("\r").split("\r")[-1]
|
|
assert self.stdout.getvalue() == ""
|
|
|
|
@pytest.mark.parametrize("format_name", ("CVAT for images 1.1",))
|
|
@pytest.mark.parametrize("include_images", (True, False))
|
|
@parametrize(
|
|
"project, location",
|
|
[
|
|
(fixture_ref("fxt_new_project"), None),
|
|
(fixture_ref("fxt_new_project"), Location.LOCAL),
|
|
(
|
|
pytest.param(
|
|
fixture_ref("fxt_new_project"),
|
|
Location.CLOUD_STORAGE,
|
|
marks=pytest.mark.with_external_services,
|
|
)
|
|
),
|
|
(
|
|
pytest.param(
|
|
fixture_ref("fxt_new_project_with_target_storage"),
|
|
None,
|
|
marks=pytest.mark.with_external_services,
|
|
)
|
|
),
|
|
(fixture_ref("fxt_new_project_with_target_storage"), Location.LOCAL),
|
|
(
|
|
pytest.param(
|
|
fixture_ref("fxt_new_project_with_target_storage"),
|
|
Location.CLOUD_STORAGE,
|
|
marks=pytest.mark.with_external_services,
|
|
)
|
|
),
|
|
],
|
|
)
|
|
def test_can_export_dataset(
|
|
self,
|
|
format_name: str,
|
|
include_images: bool,
|
|
project: Project,
|
|
location: Optional[Location],
|
|
request: pytest.FixtureRequest,
|
|
cloud_storages: CloudStorageAssets,
|
|
):
|
|
file_path = self.tmp_path / f"project_{project.id}-{format_name.lower()}.zip"
|
|
self._test_can_export_dataset(
|
|
project,
|
|
format_name=format_name,
|
|
file_path=file_path,
|
|
include_images=include_images,
|
|
location=location,
|
|
request=request,
|
|
cloud_storages=cloud_storages,
|
|
)
|
|
|
|
def test_can_download_preview(self, fxt_project_with_shapes: Project):
|
|
frame_encoded = fxt_project_with_shapes.get_preview()
|
|
(width, height) = Image.open(frame_encoded).size
|
|
|
|
assert width > 0 and height > 0
|
|
assert self.stdout.getvalue() == ""
|