From 84590581cea1fecddf7ebf52bb1383d51ddcd528 Mon Sep 17 00:00:00 2001 From: JonasWurst Date: Tue, 16 Jun 2026 07:26:02 +0200 Subject: [PATCH 1/5] Kitti OD export plugin --- README.md | 19 ++ plugins.toml | 8 + plugins/kitti_export/LICENSE | 201 ++++++++++++++++++ plugins/kitti_export/Makefile | 13 ++ plugins/kitti_export/pyproject.toml | 20 ++ .../lightly_plugins_kitti_export/__init__.py | 1 + .../lightly_plugins_kitti_export/operator.py | 183 ++++++++++++++++ 7 files changed, 445 insertions(+) create mode 100644 plugins/kitti_export/LICENSE create mode 100644 plugins/kitti_export/Makefile create mode 100644 plugins/kitti_export/pyproject.toml create mode 100644 plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py create mode 100644 plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py diff --git a/README.md b/README.md index 40c2368..c01a447 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,25 @@ Each plugin entry below includes the exact copy-paste install command. After ins +- [KITTI export](plugins/kitti_export/) + Exports KITTI object-detection label files. + +
+ Details + + The plugin writes KITTI `.txt` label files for the current filtered image + view. Nested image folder structure is preserved in label filenames when + exporting images from multiple folders. + + - Scope: images in the current view + - Input: output folder + - Output: KITTI object-detection label files + - Maintainer: Lightly + - Install: + `pip install git+https://github.com/lightly-ai/lightly-studio-plugins.git#subdirectory=plugins/kitti_export/` + +
+ ## Contributing Plugins 1. Create a new directory under `plugins/`: diff --git a/plugins.toml b/plugins.toml index a773371..baf5f62 100644 --- a/plugins.toml +++ b/plugins.toml @@ -21,3 +21,11 @@ description = "Runs LightlyTrain object detection inference on a single image or source = "local:plugins/lightly_train_object_detection_inference" maintainer = "lightly" tags = ["auto-labeling", "object-detection", "inference"] + +[[plugins]] +id = "lightly_plugins_kitti_export" +name = "KITTI export" +description = "Exports KITTI object-detection label files" +source = "local:plugins/kitti_export" +maintainer = "lightly" +tags = ["export", "object-detection", "kitti"] diff --git a/plugins/kitti_export/LICENSE b/plugins/kitti_export/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/plugins/kitti_export/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/plugins/kitti_export/Makefile b/plugins/kitti_export/Makefile new file mode 100644 index 0000000..394c786 --- /dev/null +++ b/plugins/kitti_export/Makefile @@ -0,0 +1,13 @@ +.PHONY: install format type-check + +install: + uv venv + uv pip install -r ../../dev-requirements.txt + uv pip install -e . + +format: + uv run ruff format . + uv run ruff check --fix . + +type-check: + uv run mypy --config-file ../../mypy.ini . diff --git a/plugins/kitti_export/pyproject.toml b/plugins/kitti_export/pyproject.toml new file mode 100644 index 0000000..41e2982 --- /dev/null +++ b/plugins/kitti_export/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "lightly_plugins_kitti_export" +version = "0.1.0" +description = "KITTI object detection export plugin for Lightly Studio" +requires-python = ">=3.10" +dependencies = [ + "lightly_studio>=1.0.0", + "labelformat", + "sqlmodel", +] + +[project.entry-points."lightly_studio.plugins"] +kitti_export = "lightly_plugins_kitti_export.operator:ExportKittiOperator" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/lightly_plugins_kitti_export"] diff --git a/plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py b/plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py @@ -0,0 +1 @@ + diff --git a/plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py b/plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py new file mode 100644 index 0000000..a9ea3d5 --- /dev/null +++ b/plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py @@ -0,0 +1,183 @@ +"""Plugin for exporting filtered image samples to KITTI object-detection format.""" + +from __future__ import annotations + +import os +from collections.abc import Iterable +from dataclasses import dataclass +from pathlib import Path +from typing import Any +from uuid import UUID + +from labelformat.formats import KittiObjectDetectionOutput +from labelformat.model.image import Image +from labelformat.model.object_detection import ImageObjectDetection +from sqlmodel import Session + +from lightly_studio.core.image.image_sample import ImageSample +from lightly_studio.export.lightly_studio_label_input import ( + LightlyStudioObjectDetectionInput, +) +from lightly_studio.models.collection import CollectionTable +from lightly_studio.plugins.base_operator import BaseOperator, OperatorResult +from lightly_studio.plugins.operator_context import ( + AnyFilter, + ExecutionContext, + OperatorScope, +) +from lightly_studio.plugins.operator_registry import operator_registry +from lightly_studio.plugins.parameter import BaseParameter, StringParameter +from lightly_studio.resolvers import image_resolver +from lightly_studio.resolvers.image_filter import ImageFilter +from lightly_studio.resolvers.sample_resolver.sample_filter import SampleFilter + + +class KittiObjectDetectionInput(LightlyStudioObjectDetectionInput): + """Object-detection input with KITTI-compatible relative label filenames.""" + + def __init__( + self, + session: Session, + dataset_id: UUID, + samples: Iterable[ImageSample], + images_root: Path | None = None, + ) -> None: + """Initialize the input. + + Args: + session: The database session. + dataset_id: The dataset ID for label retrieval. + samples: The samples to export. + images_root: Common root path used to preserve nested image folders. + """ + self._images_root = images_root + super().__init__(session=session, dataset_id=dataset_id, samples=samples) + + def get_images(self) -> list[Image]: + """Return images with filenames relative to the KITTI output folder.""" + return [ + Image( + id=image.id, + filename=_get_kitti_filename( + image_filename=image.filename, + images_root=self._images_root, + ), + width=image.width, + height=image.height, + ) + for image in super().get_images() + ] + + def get_labels(self) -> list[ImageObjectDetection]: + """Return labels with filenames relative to the KITTI output folder.""" + return [ + ImageObjectDetection( + image=Image( + id=label.image.id, + filename=_get_kitti_filename( + image_filename=label.image.filename, + images_root=self._images_root, + ), + width=label.image.width, + height=label.image.height, + ), + objects=label.objects, + ) + for label in super().get_labels() + ] + + +@dataclass +class ExportKittiOperator(BaseOperator): + """Export the current image scope to KITTI object-detection format.""" + + name: str = "Export to KITTI" + description: str = "Export filtered image samples to KITTI object-detection format." + + @property + def parameters(self) -> list[BaseParameter]: + """Return the operator parameters.""" + return [ + StringParameter( + name="output_folder", + description="Destination folder for the KITTI .txt files.", + required=True, + default="kitti_export", + ) + ] + + @property + def supported_scopes(self) -> list[OperatorScope]: + """Return the scopes where this operator is available.""" + return [OperatorScope.IMAGE] + + def execute( + self, + *, + session: Session, + context: ExecutionContext, + parameters: dict[str, Any], + ) -> OperatorResult: + """Export filtered image samples to KITTI object-detection labels.""" + collection = session.get(CollectionTable, context.collection_id) + if collection is None: + return OperatorResult(success=False, message="Collection does not exist.") + + result = image_resolver.get_all_by_collection_id( + session=session, + collection_id=context.collection_id, + filters=_get_image_filter(context_filter=context.context_filter), + ) + samples = [ImageSample(inner=image_table) for image_table in result.samples] + + output_folder = Path(parameters["output_folder"]).absolute() + output_folder.mkdir(parents=True, exist_ok=True) + + label_input = KittiObjectDetectionInput( + session=session, + dataset_id=collection.dataset_id, + samples=samples, + images_root=_get_common_image_root(samples=samples), + ) + KittiObjectDetectionOutput(output_folder=output_folder).save( + label_input=label_input + ) + + return OperatorResult( + success=True, + message=f"Exported {len(samples)} samples to KITTI format: {output_folder}", + ) + + +def _get_image_filter(*, context_filter: AnyFilter | None) -> ImageFilter | None: + if isinstance(context_filter, SampleFilter): + return ImageFilter(sample_filter=context_filter) + if isinstance(context_filter, ImageFilter): + return context_filter + return None + + +def _get_common_image_root(*, samples: list[ImageSample]) -> Path | None: + if not samples: + return None + image_parent_paths = [ + str(Path(sample.file_path_abs).resolve(strict=False).parent) + for sample in samples + ] + try: + return Path(os.path.commonpath(image_parent_paths)) + except ValueError: + return None + + +def _get_kitti_filename(*, image_filename: str, images_root: Path | None) -> str: + image_path = Path(image_filename).resolve(strict=False) + if images_root is None: + return image_path.name + try: + return image_path.relative_to(images_root).as_posix() + except ValueError: + return image_path.name + + +operator_registry.register(operator=ExportKittiOperator()) From 0d954856e993711837ecf8dadbad6dfc9cc00b23 Mon Sep 17 00:00:00 2001 From: JonasWurst Date: Tue, 16 Jun 2026 07:34:35 +0200 Subject: [PATCH 2/5] rename --- README.md | 4 ++-- plugins.toml | 6 +++--- .../LICENSE | 0 .../Makefile | 0 .../pyproject.toml | 8 ++++---- .../__init__.py | 0 .../operator.py | 0 7 files changed, 9 insertions(+), 9 deletions(-) rename plugins/{kitti_export => kitti_export_object_detection}/LICENSE (100%) rename plugins/{kitti_export => kitti_export_object_detection}/Makefile (100%) rename plugins/{kitti_export => kitti_export_object_detection}/pyproject.toml (57%) rename plugins/{kitti_export/src/lightly_plugins_kitti_export => kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection}/__init__.py (100%) rename plugins/{kitti_export/src/lightly_plugins_kitti_export => kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection}/operator.py (100%) diff --git a/README.md b/README.md index c01a447..646cbd0 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Each plugin entry below includes the exact copy-paste install command. After ins -- [KITTI export](plugins/kitti_export/) +- [KITTI object detection export](plugins/kitti_export_object_detection/) Exports KITTI object-detection label files.
@@ -118,7 +118,7 @@ Each plugin entry below includes the exact copy-paste install command. After ins - Output: KITTI object-detection label files - Maintainer: Lightly - Install: - `pip install git+https://github.com/lightly-ai/lightly-studio-plugins.git#subdirectory=plugins/kitti_export/` + `pip install git+https://github.com/lightly-ai/lightly-studio-plugins.git#subdirectory=plugins/kitti_export_object_detection/`
diff --git a/plugins.toml b/plugins.toml index baf5f62..d39bd7b 100644 --- a/plugins.toml +++ b/plugins.toml @@ -23,9 +23,9 @@ maintainer = "lightly" tags = ["auto-labeling", "object-detection", "inference"] [[plugins]] -id = "lightly_plugins_kitti_export" -name = "KITTI export" +id = "lightly_plugins_kitti_export_object_detection" +name = "KITTI object detection export" description = "Exports KITTI object-detection label files" -source = "local:plugins/kitti_export" +source = "local:plugins/kitti_export_object_detection" maintainer = "lightly" tags = ["export", "object-detection", "kitti"] diff --git a/plugins/kitti_export/LICENSE b/plugins/kitti_export_object_detection/LICENSE similarity index 100% rename from plugins/kitti_export/LICENSE rename to plugins/kitti_export_object_detection/LICENSE diff --git a/plugins/kitti_export/Makefile b/plugins/kitti_export_object_detection/Makefile similarity index 100% rename from plugins/kitti_export/Makefile rename to plugins/kitti_export_object_detection/Makefile diff --git a/plugins/kitti_export/pyproject.toml b/plugins/kitti_export_object_detection/pyproject.toml similarity index 57% rename from plugins/kitti_export/pyproject.toml rename to plugins/kitti_export_object_detection/pyproject.toml index 41e2982..67cf4e8 100644 --- a/plugins/kitti_export/pyproject.toml +++ b/plugins/kitti_export_object_detection/pyproject.toml @@ -1,8 +1,8 @@ [project] -name = "lightly_plugins_kitti_export" +name = "lightly_plugins_kitti_export_object_detection" version = "0.1.0" description = "KITTI object detection export plugin for Lightly Studio" -requires-python = ">=3.10" +requires-python = ">=3.9" dependencies = [ "lightly_studio>=1.0.0", "labelformat", @@ -10,11 +10,11 @@ dependencies = [ ] [project.entry-points."lightly_studio.plugins"] -kitti_export = "lightly_plugins_kitti_export.operator:ExportKittiOperator" +kitti_export_object_detection = "lightly_plugins_kitti_export_object_detection.operator:ExportKittiOperator" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/lightly_plugins_kitti_export"] +packages = ["src/lightly_plugins_kitti_export_object_detection"] diff --git a/plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/__init__.py similarity index 100% rename from plugins/kitti_export/src/lightly_plugins_kitti_export/__init__.py rename to plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/__init__.py diff --git a/plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py similarity index 100% rename from plugins/kitti_export/src/lightly_plugins_kitti_export/operator.py rename to plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py From baca84e39108b6aab6f6fe6b910cfa131e98a63f Mon Sep 17 00:00:00 2001 From: JonasWurst Date: Tue, 16 Jun 2026 07:42:10 +0200 Subject: [PATCH 3/5] Remove register --- .../lightly_plugins_kitti_export_object_detection/operator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py index a9ea3d5..d01c2ea 100644 --- a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py +++ b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py @@ -178,6 +178,3 @@ def _get_kitti_filename(*, image_filename: str, images_root: Path | None) -> str return image_path.relative_to(images_root).as_posix() except ValueError: return image_path.name - - -operator_registry.register(operator=ExportKittiOperator()) From d5ce930a33b543b4e24a8344c5aef5a03ea71fa2 Mon Sep 17 00:00:00 2001 From: JonasWurst Date: Thu, 18 Jun 2026 13:07:57 +0200 Subject: [PATCH 4/5] Substitute " " with "_" in label names --- .../operator.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py index d01c2ea..b06da96 100644 --- a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py +++ b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py @@ -4,7 +4,7 @@ import os from collections.abc import Iterable -from dataclasses import dataclass +from dataclasses import dataclass, replace from pathlib import Path from typing import Any from uuid import UUID @@ -81,7 +81,9 @@ def get_labels(self) -> list[ImageObjectDetection]: width=label.image.width, height=label.image.height, ), - objects=label.objects, + objects=[ + _normalize_kitti_object_class_name(obj) for obj in label.objects + ], ) for label in super().get_labels() ] @@ -178,3 +180,13 @@ def _get_kitti_filename(*, image_filename: str, images_root: Path | None) -> str return image_path.relative_to(images_root).as_posix() except ValueError: return image_path.name + + +def _normalize_kitti_object_class_name(obj: Any) -> Any: + """Replace spaces in the exported KITTI class token.""" + if " " not in obj.category.name: + return obj + return replace( + obj, + category=replace(obj.category, name=obj.category.name.replace(" ", "_")), + ) From ea4439b3b11e18b6992ab2c4f7698763db1563ae Mon Sep 17 00:00:00 2001 From: JonasWurst Date: Fri, 19 Jun 2026 10:30:54 +0200 Subject: [PATCH 5/5] Remove normalization --- .../operator.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py index b06da96..d01c2ea 100644 --- a/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py +++ b/plugins/kitti_export_object_detection/src/lightly_plugins_kitti_export_object_detection/operator.py @@ -4,7 +4,7 @@ import os from collections.abc import Iterable -from dataclasses import dataclass, replace +from dataclasses import dataclass from pathlib import Path from typing import Any from uuid import UUID @@ -81,9 +81,7 @@ def get_labels(self) -> list[ImageObjectDetection]: width=label.image.width, height=label.image.height, ), - objects=[ - _normalize_kitti_object_class_name(obj) for obj in label.objects - ], + objects=label.objects, ) for label in super().get_labels() ] @@ -180,13 +178,3 @@ def _get_kitti_filename(*, image_filename: str, images_root: Path | None) -> str return image_path.relative_to(images_root).as_posix() except ValueError: return image_path.name - - -def _normalize_kitti_object_class_name(obj: Any) -> Any: - """Replace spaces in the exported KITTI class token.""" - if " " not in obj.category.name: - return obj - return replace( - obj, - category=replace(obj.category, name=obj.category.name.replace(" ", "_")), - )