diff --git a/lightly_studio/src/lightly_studio/core/file_outcome_report.py b/lightly_studio/src/lightly_studio/core/file_outcome_report.py index 84512a5458..89e46369e9 100644 --- a/lightly_studio/src/lightly_studio/core/file_outcome_report.py +++ b/lightly_studio/src/lightly_studio/core/file_outcome_report.py @@ -37,7 +37,7 @@ import contextlib import logging -from collections.abc import Iterator +from collections.abc import Iterator, Mapping from dataclasses import dataclass, field from enum import Enum from os import PathLike @@ -94,9 +94,13 @@ class FileOutcomeReport: Attributes: max_examples_per_outcome: Maximum number of example paths kept per outcome. + label_overrides: Per-outcome display-label overrides used only by + `log_summary()`; any outcome absent from the dict falls back to + `outcome.value`. Defaults to empty (add-images behavior). """ max_examples_per_outcome: int = DEFAULT_MAX_EXAMPLES_PER_OUTCOME + label_overrides: Mapping[FileOutcome, str] = field(default_factory=dict) _counts: dict[FileOutcome, int] = field( default_factory=lambda: dict.fromkeys(FileOutcome, 0), init=False, @@ -173,9 +177,17 @@ def raise_if_all_failed(self) -> None: def log_summary(self) -> None: """Log a single end-of-run summary of counts and example paths.""" - counts = ", ".join(f"{outcome.value}={self._counts[outcome]}" for outcome in FileOutcome) - logger.info(f"File outcomes: {counts}.") + counts = ", ".join( + f"{self._label(outcome)}={self._counts[outcome]}" for outcome in FileOutcome + ) + logger.info(f"File processing result: {counts}.") for outcome in FileOutcome: + if outcome == FileOutcome.ADDED: + continue examples = self._example_paths[outcome] if examples: - logger.info(f"Example {outcome.value} paths: {', '.join(examples)}.") + logger.info(f"Examples '{self._label(outcome)}': {', '.join(examples)}.") + + def _label(self, outcome: FileOutcome) -> str: + """Return the display label for `outcome`, honoring `label_overrides`.""" + return self.label_overrides.get(outcome, outcome.value) diff --git a/lightly_studio/src/lightly_studio/dataset/image_embedding.py b/lightly_studio/src/lightly_studio/dataset/image_embedding.py index 71a51e767d..6f76fae889 100644 --- a/lightly_studio/src/lightly_studio/dataset/image_embedding.py +++ b/lightly_studio/src/lightly_studio/dataset/image_embedding.py @@ -93,7 +93,7 @@ def load_and_preprocess(filepath: str) -> torch.Tensor | None: ) kept_indices_set = set(result.kept_indices) - report = FileOutcomeReport() + report = FileOutcomeReport(label_overrides={FileOutcome.ADDED: "embedded"}) for index, filepath in enumerate(filepaths): outcome = FileOutcome.ADDED if index in kept_indices_set else FileOutcome.BROKEN report.record(path=filepath, outcome=outcome) diff --git a/lightly_studio/tests/core/image/test_image_dataset__labelformat.py b/lightly_studio/tests/core/image/test_image_dataset__labelformat.py index 992c885b54..770de00887 100644 --- a/lightly_studio/tests/core/image/test_image_dataset__labelformat.py +++ b/lightly_studio/tests/core/image/test_image_dataset__labelformat.py @@ -135,7 +135,7 @@ def test_from_labelformat__duplication( log_text = caplog.text assert "added=0, already_present=1" in log_text - assert "Example already_present paths:" in log_text + assert "Examples 'already_present':" in log_text assert "/fake/path/images/image.jpg" in log_text def test_from_labelformat__annotations_synced_images( diff --git a/lightly_studio/tests/core/image/test_image_dataset__path.py b/lightly_studio/tests/core/image/test_image_dataset__path.py index 2c8bec675a..2ce2480fdf 100644 --- a/lightly_studio/tests/core/image/test_image_dataset__path.py +++ b/lightly_studio/tests/core/image/test_image_dataset__path.py @@ -180,7 +180,7 @@ def test_dataset_add_images_from_path__duplication( log_text = caplog.text assert "added=2, already_present=4" in log_text - assert "Example already_present paths:" in log_text + assert "Examples 'already_present':" in log_text assert f"{images_path}" in log_text def test_dataset_add_images_from_path__dont_embed( diff --git a/lightly_studio/tests/core/test_file_outcome_report.py b/lightly_studio/tests/core/test_file_outcome_report.py index b79fb1f826..7a08106c7a 100644 --- a/lightly_studio/tests/core/test_file_outcome_report.py +++ b/lightly_studio/tests/core/test_file_outcome_report.py @@ -138,5 +138,44 @@ def test_log_summary(self, caplog: pytest.LogCaptureFixture) -> None: text = caplog.text assert "added=1" in text assert "missing=1" in text - assert "a.jpg" in text + # Examples are only shown for non-ADDED outcomes. assert "b.jpg" in text + assert "Examples 'missing'" in text + assert "a.jpg" not in text + assert "Examples 'added'" not in text + + def test_log_summary__label_overrides(self, caplog: pytest.LogCaptureFixture) -> None: + report = FileOutcomeReport(label_overrides={FileOutcome.ADDED: "embedded"}) + report.record(path="a.jpg", outcome=FileOutcome.ADDED) + report.record(path="b.jpg", outcome=FileOutcome.MISSING) + + with caplog.at_level(logging.INFO): + report.log_summary() + + text = caplog.text + # Overridden outcome uses the custom label. + assert "embedded=1" in text + assert "added=1" not in text + # Non-overridden outcomes still render their default labels. + assert "missing=1" in text + + def test_log_summary__label_overrides_are_general( + self, caplog: pytest.LogCaptureFixture + ) -> None: + report = FileOutcomeReport( + label_overrides={ + FileOutcome.ADDED: "embedded", + FileOutcome.MISSING: "not_found", + } + ) + report.record(path="a.jpg", outcome=FileOutcome.ADDED) + report.record(path="b.jpg", outcome=FileOutcome.MISSING) + + with caplog.at_level(logging.INFO): + report.log_summary() + + text = caplog.text + assert "embedded=1" in text + assert "not_found=1" in text + assert "Examples 'not_found'" in text + assert "missing=1" not in text