From 2e3306e619d89c7e06a0913713950d64b0bb135b Mon Sep 17 00:00:00 2001 From: rikunosuke Date: Mon, 22 Dec 2025 14:21:43 +0900 Subject: [PATCH] feat: Allow tasks with zero height/width in COCO and Pascal VOC converters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove early return check for zero dimensions in to_coco and to_pascalvoc functions. These formats don't use division by dimensions (unlike YOLO), so zero values are safe and should be included in the output. Add tests to verify zero dimension handling for both converters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- fastlabel/converters.py | 6 -- tests/test_converters.py | 201 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 tests/test_converters.py diff --git a/fastlabel/converters.py b/fastlabel/converters.py index 0c4ab81..3cec06e 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -34,9 +34,6 @@ def to_coco( annotation_id = 0 image_index = 0 for task in tasks: - if task["height"] == 0 or task["width"] == 0: - continue - if is_video_project_type(project_type): image_file_names = _export_image_files_for_video_task( task, str((Path(output_dir) / "images").resolve()) @@ -726,9 +723,6 @@ def _truncate(n, decimals=0) -> float: def to_pascalvoc(project_type: str, tasks: list, output_dir: str) -> list: pascalvoc = [] for task in tasks: - if task["height"] == 0 or task["width"] == 0: - continue - if is_video_project_type(project_type): image_file_names = _export_image_files_for_video_task( task, str((Path(output_dir) / "images").resolve()) diff --git a/tests/test_converters.py b/tests/test_converters.py new file mode 100644 index 0000000..06804d9 --- /dev/null +++ b/tests/test_converters.py @@ -0,0 +1,201 @@ +from fastlabel import converters + + +class TestToCoco: + """Tests for to_coco converter function.""" + + def test_to_coco_with_zero_height_task(self, tmp_path): + """Test that tasks with height=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 0, + "width": 100, + "annotations": [ + { + "type": "bbox", + "value": "cat", + "points": [10, 10, 50, 50], + "color": "#FF0000", + } + ], + }, + { + "name": "image2.jpg", + "height": 100, + "width": 100, + "annotations": [ + { + "type": "bbox", + "value": "cat", + "points": [10, 10, 50, 50], + "color": "#FF0000", + } + ], + }, + ] + + result = converters.to_coco( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result["images"]) == 2 + assert result["images"][0]["height"] == 0 + assert result["images"][1]["height"] == 100 + + def test_to_coco_with_zero_width_task(self, tmp_path): + """Test that tasks with width=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 100, + "width": 0, + "annotations": [ + { + "type": "bbox", + "value": "dog", + "points": [10, 10, 50, 50], + "color": "#00FF00", + } + ], + }, + ] + + result = converters.to_coco( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result["images"]) == 1 + assert result["images"][0]["width"] == 0 + + def test_to_coco_with_zero_dimensions_task(self, tmp_path): + """Test that tasks with both height=0 and width=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 0, + "width": 0, + "annotations": [ + { + "type": "bbox", + "value": "bird", + "points": [10, 10, 50, 50], + "color": "#0000FF", + } + ], + }, + ] + + result = converters.to_coco( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result["images"]) == 1 + assert result["images"][0]["height"] == 0 + assert result["images"][0]["width"] == 0 + + +class TestToPascalVoc: + """Tests for to_pascalvoc converter function.""" + + def test_to_pascalvoc_with_zero_height_task(self, tmp_path): + """Test that tasks with height=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 0, + "width": 100, + "annotations": [ + { + "type": "bbox", + "value": "cat", + "points": [10, 10, 50, 50], + "attributes": [], + } + ], + }, + { + "name": "image2.jpg", + "height": 100, + "width": 100, + "annotations": [ + { + "type": "bbox", + "value": "cat", + "points": [10, 10, 50, 50], + "attributes": [], + } + ], + }, + ] + + result = converters.to_pascalvoc( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result) == 2 + assert result[0]["annotation"]["size"]["height"] == 0 + assert result[1]["annotation"]["size"]["height"] == 100 + + def test_to_pascalvoc_with_zero_width_task(self, tmp_path): + """Test that tasks with width=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 100, + "width": 0, + "annotations": [ + { + "type": "bbox", + "value": "dog", + "points": [10, 10, 50, 50], + "attributes": [], + } + ], + }, + ] + + result = converters.to_pascalvoc( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result) == 1 + assert result[0]["annotation"]["size"]["width"] == 0 + + def test_to_pascalvoc_with_zero_dimensions_task(self, tmp_path): + """Test that tasks with both height=0 and width=0 are included in output.""" + tasks = [ + { + "name": "image1.jpg", + "height": 0, + "width": 0, + "annotations": [ + { + "type": "bbox", + "value": "bird", + "points": [10, 10, 50, 50], + "attributes": [], + } + ], + }, + ] + + result = converters.to_pascalvoc( + project_type="image_bbox", + tasks=tasks, + output_dir=str(tmp_path), + ) + + assert len(result) == 1 + assert result[0]["annotation"]["size"]["height"] == 0 + assert result[0]["annotation"]["size"]["width"] == 0