Skip to content

Commit

Permalink
Merge pull request #8827 from cvat-ai/ov/regression_export_cvat_dataset
Browse files Browse the repository at this point in the history
Regression test for missing frames after exporting a CVAT dataset
  • Loading branch information
archibald1418 authored Dec 19, 2024
2 parents a9ea512 + 4998837 commit df230a4
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@
"python": "${command:python.interpreterPath}",
"module": "pytest",
"args": [
"--verbose",
"--no-cov", // vscode debugger might not work otherwise
"tests/python/rest_api/"
],
"cwd": "${workspaceFolder}",
Expand Down
2 changes: 2 additions & 0 deletions tests/python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Pillow==10.3.0
python-dateutil==2.8.2
pyyaml==6.0.0
numpy==2.0.0

# TODO: update pytest to 7.0.0 and pytest-timeout to 2.3.1 (better debug in vscode)
71 changes: 69 additions & 2 deletions tests/python/rest_api/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from itertools import chain, groupby, product
from math import ceil
from operator import itemgetter
from pathlib import Path
from pathlib import Path, PurePosixPath
from tempfile import NamedTemporaryFile, TemporaryDirectory
from time import sleep, time
from typing import Any, Callable, ClassVar, Optional, Union
Expand Down Expand Up @@ -66,6 +66,7 @@
from .utils import (
DATUMARO_FORMAT_FOR_DIMENSION,
CollectionSimpleFilterTestBase,
calc_end_frame,
compare_annotations,
create_task,
export_dataset,
Expand Down Expand Up @@ -3111,7 +3112,7 @@ def _compute_annotation_segment_params(self, task_spec: _TaskSpec) -> list[tuple
stop_frame = getattr(task_spec, "stop_frame", None) or (
start_frame + (task_spec.size - 1) * frame_step
)
end_frame = stop_frame - ((stop_frame - start_frame) % frame_step) + frame_step
end_frame = calc_end_frame(start_frame, stop_frame, frame_step)

validation_params = getattr(task_spec, "validation_params", None)
if validation_params and validation_params.mode.value == "gt_pool":
Expand Down Expand Up @@ -6349,3 +6350,69 @@ def check_element_outside_count(track_idx, element_idx, expected_count):
check_element_outside_count(1, 0, 1)
check_element_outside_count(1, 1, 2)
check_element_outside_count(1, 2, 2)


@pytest.mark.usefixtures("restore_db_per_class")
@pytest.mark.usefixtures("restore_redis_ondisk_per_function")
@pytest.mark.usefixtures("restore_redis_ondisk_after_class")
@pytest.mark.usefixtures("restore_redis_inmem_per_function")
class TestPatchExportFrames(TestTaskData):

@fixture(scope="class")
@parametrize("media_type", [_SourceDataType.images, _SourceDataType.video])
@parametrize("step", [5])
@parametrize("frame_count", [20])
@parametrize("start_frame", [None, 3])
def fxt_uploaded_media_task(
self,
request: pytest.FixtureRequest,
media_type: _SourceDataType,
step: int,
frame_count: int,
start_frame: Optional[int],
) -> Generator[tuple[_TaskSpec, Task, str], None, None]:
args = dict(request=request, frame_count=frame_count, step=step, start_frame=start_frame)

if media_type == _SourceDataType.images:
(spec, task_id) = next(self._uploaded_images_task_fxt_base(**args))
else:
(spec, task_id) = next(self._uploaded_video_task_fxt_base(**args))

with make_sdk_client(self._USERNAME) as client:
task = client.tasks.retrieve(task_id)

yield (spec, task, f"CVAT for {media_type} 1.1")

@pytest.mark.usefixtures("restore_redis_ondisk_per_function")
@parametrize("spec, task, format_name", [fixture_ref(fxt_uploaded_media_task)])
def test_export_with_non_default_frame_step(
self, tmp_path: Path, spec: _TaskSpec, task: Task, format_name: str
):

dataset_file = tmp_path / "dataset.zip"
task.export_dataset(format_name, dataset_file, include_images=True)

def get_img_index(zinfo: zipfile.ZipInfo) -> int:
name = PurePosixPath(zinfo.filename)
if name.suffix.lower() not in (".png", ".jpg", ".jpeg"):
return -1
return int(name.stem.rsplit("_", maxsplit=1)[-1])

# get frames and sort them
with zipfile.ZipFile(dataset_file) as dataset:
frames = np.array(
[png_idx for png_idx in map(get_img_index, dataset.filelist) if png_idx != -1]
)
frames.sort()

task_meta = task.get_meta()
(src_start_frame, src_stop_frame, src_frame_step) = (
task_meta["start_frame"],
task_meta["stop_frame"],
spec.frame_step,
)
src_end_frame = calc_end_frame(src_start_frame, src_stop_frame, src_frame_step)
assert len(frames) == spec.size == task_meta["size"], "Some frames were lost"
assert np.all(
frames == np.arange(src_start_frame, src_end_frame, src_frame_step)
), "Some frames are wrong"
4 changes: 4 additions & 0 deletions tests/python/rest_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,3 +601,7 @@ def _exclude_cb(obj, path):

def parse_frame_step(frame_filter: str) -> int:
return int((frame_filter or "step=1").split("=")[1])


def calc_end_frame(start_frame: int, stop_frame: int, frame_step: int) -> int:
return stop_frame - ((stop_frame - start_frame) % frame_step) + frame_step

0 comments on commit df230a4

Please sign in to comment.