diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 06f48f0eae23..1c4d0081cd7c 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -672,8 +672,8 @@ def create(self, validated_data): ) segment = models.Segment.objects.create( - start_frame=task.data.start_frame, - stop_frame=task.data.stop_frame, + start_frame=0, + stop_frame=task.data.size, frames=frames, task=task, type=models.SegmentType.SPECIFIC_FRAMES, diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 520673d2bde3..e9d884e8b37e 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1915,7 +1915,7 @@ def metadata(self, request, pk): stop_frame = db_job.segment.stop_frame frame_step = db_data.get_frame_step() data_start_frame = db_data.start_frame + start_frame * frame_step - data_stop_frame = db_data.start_frame + stop_frame * frame_step + data_stop_frame = min(db_data.stop_frame, db_data.start_frame + stop_frame * frame_step) frame_set = db_job.segment.frame_set if request.method == 'PATCH': diff --git a/tests/python/rest_api/test_jobs.py b/tests/python/rest_api/test_jobs.py index 2df0ca5841aa..264722852704 100644 --- a/tests/python/rest_api/test_jobs.py +++ b/tests/python/rest_api/test_jobs.py @@ -21,8 +21,9 @@ from PIL import Image from shared.utils.config import make_api_client +from shared.utils.helpers import generate_image_files -from .utils import CollectionSimpleFilterTestBase, compare_annotations, export_dataset +from .utils import CollectionSimpleFilterTestBase, compare_annotations, create_task, export_dataset def get_job_staff(job, tasks, projects): @@ -577,7 +578,9 @@ def test_can_get_gt_job_meta(self, admin_user, tasks, task_mode): assert job_frame_ids == gt_job_meta.included_frames # The frames themselves are the same as in the whole range - # this is required in UI implementation + # this is required by the UI implementation + assert task_meta.start_frame == gt_job_meta.start_frame + assert task_meta.stop_frame == gt_job_meta.stop_frame if task_mode == "annotation": assert ( len(gt_job_meta.frames) @@ -588,6 +591,52 @@ def test_can_get_gt_job_meta(self, admin_user, tasks, task_mode): else: assert False + @pytest.mark.usefixtures("restore_db_per_function") + def test_can_get_gt_job_meta_with_complex_frame_setup(self, admin_user): + image_count = 50 + start_frame = 3 + stop_frame = image_count - 4 + frame_step = 5 + + images = generate_image_files(image_count) + + task_id, _ = create_task( + admin_user, + spec={ + "name": "test complex frame setup", + "labels": [{"name": "cat"}], + }, + data={ + "image_quality": 75, + "start_frame": start_frame, + "stop_frame": stop_frame, + "frame_filter": f"step={frame_step}", + "client_files": images, + "sorting_method": "predefined", + }, + ) + + task_frame_ids = range(start_frame, stop_frame, frame_step) + job_frame_ids = list(task_frame_ids[::3]) + gt_job = self._get_or_create_gt_job(admin_user, task_id, job_frame_ids) + + with make_api_client(admin_user) as api_client: + (gt_job_meta, _) = api_client.jobs_api.retrieve_data_meta(gt_job.id) + + # The size is adjusted by the frame step and included frames + assert len(job_frame_ids) == gt_job_meta.size + assert job_frame_ids == gt_job_meta.included_frames + + # The frames themselves are the same as in the whole range + # with placeholders in the frames outside the job. + # This is required by the UI implementation + assert start_frame == gt_job_meta.start_frame + assert max(task_frame_ids) == gt_job_meta.stop_frame + assert [frame_info["name"] for frame_info in gt_job_meta.frames] == [ + images[frame].name if frame in job_frame_ids else "placeholder.jpg" + for frame in task_frame_ids + ] + @pytest.mark.parametrize("task_mode", ["annotation", "interpolation"]) @pytest.mark.parametrize("quality", ["compressed", "original"]) def test_can_get_gt_job_chunk(self, admin_user, tasks, task_mode, quality): @@ -622,6 +671,9 @@ def test_can_get_gt_job_chunk(self, admin_user, tasks, task_mode, quality): ) included_frames = job_frame_ids + # The frame count is the same as in the whole range + # with placeholders in the frames outside the job. + # This is required by the UI implementation with zipfile.ZipFile(chunk_file) as chunk: assert set(chunk.namelist()) == set("{:06d}.jpeg".format(i) for i in frame_range)