From a36d818f6ff00595a1f58c92d52dbf7d1a91cdd1 Mon Sep 17 00:00:00 2001 From: Horatiu Almasan Date: Fri, 5 Jun 2026 11:40:36 +0300 Subject: [PATCH 1/5] Fix confidance values not loaded --- src/labelformat/formats/coco.py | 10 + .../model/instance_segmentation.py | 7 + tests/unit/formats/test_coco.py | 237 ++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 tests/unit/formats/test_coco.py diff --git a/src/labelformat/formats/coco.py b/src/labelformat/formats/coco.py index fdb9df6..1ac817b 100644 --- a/src/labelformat/formats/coco.py +++ b/src/labelformat/formats/coco.py @@ -90,6 +90,9 @@ def get_labels(self) -> Iterable[ImageObjectDetection]: bbox=[float(x) for x in ann["bbox"]], format=BoundingBoxFormat.XYWH, ), + confidence=( + float(ann["score"]) if "score" in ann else None + ), ) ) yield ImageObjectDetection( @@ -133,6 +136,9 @@ def get_labels(self) -> Iterable[ImageInstanceSegmentation]: SingleInstanceSegmentation( category=category_id_to_category[ann["category_id"]], segmentation=segmentation, + confidence=( + float(ann["score"]) if "score" in ann else None + ), ) ) yield ImageInstanceSegmentation( @@ -173,6 +179,8 @@ def save(self, label_input: ObjectDetectionInput) -> None: float(v) for v in obj.box.to_format(BoundingBoxFormat.XYWH) ], } + if obj.confidence is not None: + annotation["score"] = obj.confidence data["annotations"].append(annotation) self.output_file.parent.mkdir(parents=True, exist_ok=True) @@ -201,6 +209,8 @@ def save(self, label_input: InstanceSegmentationInput) -> None: "iscrowd": is_crowd, "segmentation": segmentation, } + if obj.confidence is not None: + annotation["score"] = obj.confidence data["annotations"].append(annotation) self.output_file.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/labelformat/model/instance_segmentation.py b/src/labelformat/model/instance_segmentation.py index 39a5946..2c07792 100644 --- a/src/labelformat/model/instance_segmentation.py +++ b/src/labelformat/model/instance_segmentation.py @@ -15,6 +15,13 @@ class SingleInstanceSegmentation: category: Category segmentation: MultiPolygon | BinaryMaskSegmentation + confidence: float | None = None + + def __post_init__(self) -> None: + if self.confidence is not None and not (0 <= self.confidence <= 1): + raise ValueError( + f"Confidence must be between 0 and 1, but got: {self.confidence}" + ) @dataclass(frozen=True) diff --git a/tests/unit/formats/test_coco.py b/tests/unit/formats/test_coco.py new file mode 100644 index 0000000..c2717f1 --- /dev/null +++ b/tests/unit/formats/test_coco.py @@ -0,0 +1,237 @@ +import json +from pathlib import Path + +import pytest + +from argparse import ArgumentParser +from typing import Iterable + +from labelformat.formats.coco import ( + COCOInstanceSegmentationInput, + COCOInstanceSegmentationOutput, + COCOObjectDetectionInput, + COCOObjectDetectionOutput, +) +from labelformat.model.bounding_box import BoundingBox +from labelformat.model.category import Category +from labelformat.model.image import Image +from labelformat.model.instance_segmentation import ( + ImageInstanceSegmentation, + InstanceSegmentationInput, + SingleInstanceSegmentation, +) +from labelformat.model.multipolygon import MultiPolygon +from labelformat.model.object_detection import ( + ImageObjectDetection, + SingleObjectDetection, +) + +from ... import simple_object_detection_label_input + + +def _create_coco_file(tmp_path: Path, with_score: bool) -> Path: + annotations = [ + { + "image_id": 0, + "category_id": 1, + "bbox": [10.0, 20.0, 20.0, 20.0], + }, + { + "image_id": 0, + "category_id": 0, + "bbox": [50.0, 60.0, 20.0, 20.0], + }, + ] + if with_score: + annotations[0]["score"] = 0.4 + annotations[1]["score"] = 0.8 + data = { + "images": [ + {"id": 0, "file_name": "image.jpg", "width": 100, "height": 200}, + ], + "categories": [ + {"id": 0, "name": "cat"}, + {"id": 1, "name": "dog"}, + {"id": 2, "name": "cow"}, + ], + "annotations": annotations, + } + coco_file = tmp_path / "train.json" + coco_file.write_text(json.dumps(data)) + return coco_file + + +class TestCOCOObjectDetectionInput: + @pytest.mark.parametrize("with_score", [True, False]) + def test_get_labels(self, tmp_path: Path, with_score: bool) -> None: + coco_file = _create_coco_file(tmp_path=tmp_path, with_score=with_score) + label_input = COCOObjectDetectionInput(input_file=coco_file) + labels = list(label_input.get_labels()) + assert labels == [ + ImageObjectDetection( + image=Image(id=0, filename="image.jpg", width=100, height=200), + objects=[ + SingleObjectDetection( + category=Category(id=1, name="dog"), + box=BoundingBox(xmin=10.0, ymin=20.0, xmax=30.0, ymax=40.0), + confidence=0.4 if with_score else None, + ), + SingleObjectDetection( + category=Category(id=0, name="cat"), + box=BoundingBox(xmin=50.0, ymin=60.0, xmax=70.0, ymax=80.0), + confidence=0.8 if with_score else None, + ), + ], + ), + ] + + +class TestCOCOObjectDetectionOutput: + @pytest.mark.parametrize("with_confidence", [True, False]) + def test_save(self, tmp_path: Path, with_confidence: bool) -> None: + output_file = tmp_path / "train.json" + COCOObjectDetectionOutput(output_file=output_file).save( + label_input=simple_object_detection_label_input.get_input( + with_confidence=with_confidence + ) + ) + + output_json = json.loads(output_file.read_text()) + expected_annotations = [ + { + "image_id": 0, + "category_id": 1, + "bbox": [10.0, 20.0, 20.0, 20.0], + }, + { + "image_id": 0, + "category_id": 0, + "bbox": [50.0, 60.0, 20.0, 20.0], + }, + ] + if with_confidence: + expected_annotations[0]["score"] = 0.4 + expected_annotations[1]["score"] = 0.8 + assert output_json["annotations"] == expected_annotations + + +def _create_coco_instance_segmentation_file( + tmp_path: Path, with_score: bool +) -> Path: + annotations = [ + { + "image_id": 0, + "category_id": 1, + "iscrowd": 0, + "bbox": [10.0, 10.0, 10.0, 10.0], + "segmentation": [[10.0, 10.0, 10.0, 20.0, 20.0, 20.0, 20.0, 10.0]], + }, + ] + if with_score: + annotations[0]["score"] = 0.4 + data = { + "images": [ + {"id": 0, "file_name": "image.jpg", "width": 100, "height": 200}, + ], + "categories": [ + {"id": 0, "name": "cat"}, + {"id": 1, "name": "dog"}, + {"id": 2, "name": "cow"}, + ], + "annotations": annotations, + } + coco_file = tmp_path / "train.json" + coco_file.write_text(json.dumps(data)) + return coco_file + + +class TestCOCOInstanceSegmentationInput: + @pytest.mark.parametrize("with_score", [True, False]) + def test_get_labels(self, tmp_path: Path, with_score: bool) -> None: + coco_file = _create_coco_instance_segmentation_file( + tmp_path=tmp_path, with_score=with_score + ) + label_input = COCOInstanceSegmentationInput(input_file=coco_file) + labels = list(label_input.get_labels()) + assert labels == [ + ImageInstanceSegmentation( + image=Image(id=0, filename="image.jpg", width=100, height=200), + objects=[ + SingleInstanceSegmentation( + category=Category(id=1, name="dog"), + segmentation=MultiPolygon( + polygons=[ + [ + (10.0, 10.0), + (10.0, 20.0), + (20.0, 20.0), + (20.0, 10.0), + ], + ], + ), + confidence=0.4 if with_score else None, + ), + ], + ), + ] + + +class _SimpleInstanceSegmentationInput(InstanceSegmentationInput): + def __init__(self, with_confidence: bool) -> None: + self._with_confidence = with_confidence + + @staticmethod + def add_cli_arguments(parser: ArgumentParser) -> None: + pass + + def get_categories(self) -> Iterable[Category]: + return [ + Category(id=0, name="cat"), + Category(id=1, name="dog"), + Category(id=2, name="cow"), + ] + + def get_images(self) -> Iterable[Image]: + return [Image(id=0, filename="image.jpg", width=100, height=200)] + + def get_labels(self) -> Iterable[ImageInstanceSegmentation]: + return [ + ImageInstanceSegmentation( + image=Image(id=0, filename="image.jpg", width=100, height=200), + objects=[ + SingleInstanceSegmentation( + category=Category(id=1, name="dog"), + segmentation=MultiPolygon( + polygons=[ + [ + (10.0, 10.0), + (10.0, 20.0), + (20.0, 20.0), + (20.0, 10.0), + ], + ], + ), + confidence=0.4 if self._with_confidence else None, + ), + ], + ) + ] + + +class TestCOCOInstanceSegmentationOutput: + @pytest.mark.parametrize("with_confidence", [True, False]) + def test_save(self, tmp_path: Path, with_confidence: bool) -> None: + output_file = tmp_path / "train.json" + COCOInstanceSegmentationOutput(output_file=output_file).save( + label_input=_SimpleInstanceSegmentationInput( + with_confidence=with_confidence + ) + ) + + output_json = json.loads(output_file.read_text()) + annotations = output_json["annotations"] + assert len(annotations) == 1 + if with_confidence: + assert annotations[0]["score"] == 0.4 + else: + assert "score" not in annotations[0] From 9cd75dd7aff75aa4b9e572af8f6dd1279b7d2f9e Mon Sep 17 00:00:00 2001 From: Horatiu Almasan Date: Fri, 5 Jun 2026 11:50:46 +0300 Subject: [PATCH 2/5] update --- src/labelformat/formats/coco.py | 8 ++------ tests/unit/formats/test_coco.py | 9 +++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/labelformat/formats/coco.py b/src/labelformat/formats/coco.py index 1ac817b..46c7a49 100644 --- a/src/labelformat/formats/coco.py +++ b/src/labelformat/formats/coco.py @@ -90,9 +90,7 @@ def get_labels(self) -> Iterable[ImageObjectDetection]: bbox=[float(x) for x in ann["bbox"]], format=BoundingBoxFormat.XYWH, ), - confidence=( - float(ann["score"]) if "score" in ann else None - ), + confidence=(float(ann["score"]) if "score" in ann else None), ) ) yield ImageObjectDetection( @@ -136,9 +134,7 @@ def get_labels(self) -> Iterable[ImageInstanceSegmentation]: SingleInstanceSegmentation( category=category_id_to_category[ann["category_id"]], segmentation=segmentation, - confidence=( - float(ann["score"]) if "score" in ann else None - ), + confidence=(float(ann["score"]) if "score" in ann else None), ) ) yield ImageInstanceSegmentation( diff --git a/tests/unit/formats/test_coco.py b/tests/unit/formats/test_coco.py index c2717f1..ad1a6c0 100644 --- a/tests/unit/formats/test_coco.py +++ b/tests/unit/formats/test_coco.py @@ -1,11 +1,10 @@ import json +from argparse import ArgumentParser from pathlib import Path +from typing import Iterable import pytest -from argparse import ArgumentParser -from typing import Iterable - from labelformat.formats.coco import ( COCOInstanceSegmentationInput, COCOInstanceSegmentationOutput, @@ -115,9 +114,7 @@ def test_save(self, tmp_path: Path, with_confidence: bool) -> None: assert output_json["annotations"] == expected_annotations -def _create_coco_instance_segmentation_file( - tmp_path: Path, with_score: bool -) -> Path: +def _create_coco_instance_segmentation_file(tmp_path: Path, with_score: bool) -> Path: annotations = [ { "image_id": 0, From 92a30ece64843976716a0ca42f4df2e753bd7160 Mon Sep 17 00:00:00 2001 From: Horatiu Almasan Date: Fri, 5 Jun 2026 12:01:54 +0300 Subject: [PATCH 3/5] update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bc1f88..3bc9496 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "labelformat" -version = "0.1.15" +version = "0.1.16" authors = [{ name = "Lightly.ai" }] description = "A tool for converting computer vision label formats." readme = "README.md" From 479e1fe07cc92758a8e1dee0f056681d1d55a5dd Mon Sep 17 00:00:00 2001 From: Horatiu Almasan Date: Fri, 5 Jun 2026 12:13:39 +0300 Subject: [PATCH 4/5] update lock file --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 1d0a508..1b84305 100644 --- a/uv.lock +++ b/uv.lock @@ -2706,7 +2706,7 @@ wheels = [ [[package]] name = "labelformat" -version = "0.1.15" +version = "0.1.16" source = { editable = "." } dependencies = [ { name = "fsspec", version = "2024.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, From 1fa350661ba8fb8f8672cb16f1946a0b7d631fb8 Mon Sep 17 00:00:00 2001 From: Horatiu Almasan Date: Fri, 5 Jun 2026 12:25:56 +0300 Subject: [PATCH 5/5] remove COCOInstanceSegmentationInput confidence --- src/labelformat/formats/coco.py | 3 - .../model/instance_segmentation.py | 7 - tests/unit/formats/test_coco.py | 135 +----------------- 3 files changed, 1 insertion(+), 144 deletions(-) diff --git a/src/labelformat/formats/coco.py b/src/labelformat/formats/coco.py index 46c7a49..fbde286 100644 --- a/src/labelformat/formats/coco.py +++ b/src/labelformat/formats/coco.py @@ -134,7 +134,6 @@ def get_labels(self) -> Iterable[ImageInstanceSegmentation]: SingleInstanceSegmentation( category=category_id_to_category[ann["category_id"]], segmentation=segmentation, - confidence=(float(ann["score"]) if "score" in ann else None), ) ) yield ImageInstanceSegmentation( @@ -205,8 +204,6 @@ def save(self, label_input: InstanceSegmentationInput) -> None: "iscrowd": is_crowd, "segmentation": segmentation, } - if obj.confidence is not None: - annotation["score"] = obj.confidence data["annotations"].append(annotation) self.output_file.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/labelformat/model/instance_segmentation.py b/src/labelformat/model/instance_segmentation.py index 2c07792..39a5946 100644 --- a/src/labelformat/model/instance_segmentation.py +++ b/src/labelformat/model/instance_segmentation.py @@ -15,13 +15,6 @@ class SingleInstanceSegmentation: category: Category segmentation: MultiPolygon | BinaryMaskSegmentation - confidence: float | None = None - - def __post_init__(self) -> None: - if self.confidence is not None and not (0 <= self.confidence <= 1): - raise ValueError( - f"Confidence must be between 0 and 1, but got: {self.confidence}" - ) @dataclass(frozen=True) diff --git a/tests/unit/formats/test_coco.py b/tests/unit/formats/test_coco.py index ad1a6c0..c0805c6 100644 --- a/tests/unit/formats/test_coco.py +++ b/tests/unit/formats/test_coco.py @@ -1,25 +1,12 @@ import json -from argparse import ArgumentParser from pathlib import Path -from typing import Iterable import pytest -from labelformat.formats.coco import ( - COCOInstanceSegmentationInput, - COCOInstanceSegmentationOutput, - COCOObjectDetectionInput, - COCOObjectDetectionOutput, -) +from labelformat.formats.coco import COCOObjectDetectionInput, COCOObjectDetectionOutput from labelformat.model.bounding_box import BoundingBox from labelformat.model.category import Category from labelformat.model.image import Image -from labelformat.model.instance_segmentation import ( - ImageInstanceSegmentation, - InstanceSegmentationInput, - SingleInstanceSegmentation, -) -from labelformat.model.multipolygon import MultiPolygon from labelformat.model.object_detection import ( ImageObjectDetection, SingleObjectDetection, @@ -112,123 +99,3 @@ def test_save(self, tmp_path: Path, with_confidence: bool) -> None: expected_annotations[0]["score"] = 0.4 expected_annotations[1]["score"] = 0.8 assert output_json["annotations"] == expected_annotations - - -def _create_coco_instance_segmentation_file(tmp_path: Path, with_score: bool) -> Path: - annotations = [ - { - "image_id": 0, - "category_id": 1, - "iscrowd": 0, - "bbox": [10.0, 10.0, 10.0, 10.0], - "segmentation": [[10.0, 10.0, 10.0, 20.0, 20.0, 20.0, 20.0, 10.0]], - }, - ] - if with_score: - annotations[0]["score"] = 0.4 - data = { - "images": [ - {"id": 0, "file_name": "image.jpg", "width": 100, "height": 200}, - ], - "categories": [ - {"id": 0, "name": "cat"}, - {"id": 1, "name": "dog"}, - {"id": 2, "name": "cow"}, - ], - "annotations": annotations, - } - coco_file = tmp_path / "train.json" - coco_file.write_text(json.dumps(data)) - return coco_file - - -class TestCOCOInstanceSegmentationInput: - @pytest.mark.parametrize("with_score", [True, False]) - def test_get_labels(self, tmp_path: Path, with_score: bool) -> None: - coco_file = _create_coco_instance_segmentation_file( - tmp_path=tmp_path, with_score=with_score - ) - label_input = COCOInstanceSegmentationInput(input_file=coco_file) - labels = list(label_input.get_labels()) - assert labels == [ - ImageInstanceSegmentation( - image=Image(id=0, filename="image.jpg", width=100, height=200), - objects=[ - SingleInstanceSegmentation( - category=Category(id=1, name="dog"), - segmentation=MultiPolygon( - polygons=[ - [ - (10.0, 10.0), - (10.0, 20.0), - (20.0, 20.0), - (20.0, 10.0), - ], - ], - ), - confidence=0.4 if with_score else None, - ), - ], - ), - ] - - -class _SimpleInstanceSegmentationInput(InstanceSegmentationInput): - def __init__(self, with_confidence: bool) -> None: - self._with_confidence = with_confidence - - @staticmethod - def add_cli_arguments(parser: ArgumentParser) -> None: - pass - - def get_categories(self) -> Iterable[Category]: - return [ - Category(id=0, name="cat"), - Category(id=1, name="dog"), - Category(id=2, name="cow"), - ] - - def get_images(self) -> Iterable[Image]: - return [Image(id=0, filename="image.jpg", width=100, height=200)] - - def get_labels(self) -> Iterable[ImageInstanceSegmentation]: - return [ - ImageInstanceSegmentation( - image=Image(id=0, filename="image.jpg", width=100, height=200), - objects=[ - SingleInstanceSegmentation( - category=Category(id=1, name="dog"), - segmentation=MultiPolygon( - polygons=[ - [ - (10.0, 10.0), - (10.0, 20.0), - (20.0, 20.0), - (20.0, 10.0), - ], - ], - ), - confidence=0.4 if self._with_confidence else None, - ), - ], - ) - ] - - -class TestCOCOInstanceSegmentationOutput: - @pytest.mark.parametrize("with_confidence", [True, False]) - def test_save(self, tmp_path: Path, with_confidence: bool) -> None: - output_file = tmp_path / "train.json" - COCOInstanceSegmentationOutput(output_file=output_file).save( - label_input=_SimpleInstanceSegmentationInput( - with_confidence=with_confidence - ) - ) - - output_json = json.loads(output_file.read_text()) - annotations = output_json["annotations"] - assert len(annotations) == 1 - if with_confidence: - assert annotations[0]["score"] == 0.4 - else: - assert "score" not in annotations[0]