From a9b763e8d2281a683ce9f91d2c0ae224bbcfe1a2 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Tue, 2 Dec 2025 19:09:34 +0900 Subject: [PATCH 1/3] fix: remove REC006 Signed-off-by: ktro2828 --- docs/schema/requirement.md | 1 - t4_devkit/sanity/record/__init__.py | 1 - t4_devkit/sanity/record/rec006.py | 25 ------------------------- tests/sanity/test_record_checkers.py | 19 ------------------- 4 files changed, 46 deletions(-) delete mode 100644 t4_devkit/sanity/record/rec006.py diff --git a/docs/schema/requirement.md b/docs/schema/requirement.md index c29ddfa..18ea8e3 100644 --- a/docs/schema/requirement.md +++ b/docs/schema/requirement.md @@ -23,7 +23,6 @@ | `REC003` | `sample-data-not-empty` | `ERROR` | `SampleData` record is not empty. | | `REC004` | `ego-pose-not-empty` | `ERROR` | `EgoPose` record is not empty. | | `REC005` | `calibrated-sensor-non-empty` | `ERROR` | `CalibratedSensor` record is not empty. | -| `REC006` | `instance-not-empty` | `ERROR` | `Instance` record is not empty. | ## Reference (`REF`) diff --git a/t4_devkit/sanity/record/__init__.py b/t4_devkit/sanity/record/__init__.py index a4980bd..61e0927 100644 --- a/t4_devkit/sanity/record/__init__.py +++ b/t4_devkit/sanity/record/__init__.py @@ -5,4 +5,3 @@ from .rec003 import * # noqa from .rec004 import * # noqa from .rec005 import * # noqa -from .rec006 import * # noqa diff --git a/t4_devkit/sanity/record/rec006.py b/t4_devkit/sanity/record/rec006.py deleted file mode 100644 index 0edee0c..0000000 --- a/t4_devkit/sanity/record/rec006.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from t4_devkit.schema import SchemaName - -from ..checker import RuleID, RuleName, Severity -from ..registry import CHECKERS -from ..result import Reason -from .base import RecordCountChecker - -__all__ = ["REC006"] - - -@CHECKERS.register() -class REC006(RecordCountChecker): - """A checker of REC006.""" - - id = RuleID("REC006") - name = RuleName("instance-not-empty") - severity = Severity.ERROR - description = "'Instance' record is not empty." - schema = SchemaName.INSTANCE - - def check_count(self, records: list[dict]) -> list[Reason] | None: - num_instance = len(records) - return [Reason("'Instance' record must not be empty")] if num_instance == 0 else None diff --git a/tests/sanity/test_record_checkers.py b/tests/sanity/test_record_checkers.py index 5c3710f..764102e 100644 --- a/tests/sanity/test_record_checkers.py +++ b/tests/sanity/test_record_checkers.py @@ -12,7 +12,6 @@ from t4_devkit.sanity.record.rec003 import REC003 from t4_devkit.sanity.record.rec004 import REC004 from t4_devkit.sanity.record.rec005 import REC005 -from t4_devkit.sanity.record.rec006 import REC006 # Base sample dataset root (contains all mandatory annotation json files with non-empty records) SAMPLE_ROOT = Path(__file__).parent.parent.joinpath("sample", "t4dataset") @@ -143,21 +142,3 @@ def test_rec005_fail_calibrated_sensor_empty(tmp_path: Path) -> None: report = checker(_context(root)) assert not report.is_passed(strict=True) assert report.reasons and report.reasons[0] == "'CalibratedSensor' record must not be empty" - - -# ---------------- REC006 (instance-not-empty) ---------------- - - -def test_rec006_pass_instance_not_empty() -> None: - checker = REC006() - report = checker(_context(SAMPLE_ROOT)) - assert report.is_passed(strict=True) - assert report.reasons is None - - -def test_rec006_fail_instance_empty(tmp_path: Path) -> None: - root = _make_mutated_dataset(tmp_path, {"instance.json": []}) - checker = REC006() - report = checker(_context(root)) - assert not report.is_passed(strict=True) - assert report.reasons and report.reasons[0] == "'Instance' record must not be empty" From 0e374ce369312cff25482efd7e1056de4f1cb991 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Wed, 3 Dec 2025 09:44:55 +0900 Subject: [PATCH 2/3] Revert "fix: remove REC006" This reverts commit a9b763e8d2281a683ce9f91d2c0ae224bbcfe1a2. Signed-off-by: ktro2828 --- docs/schema/requirement.md | 1 + t4_devkit/sanity/record/__init__.py | 1 + t4_devkit/sanity/record/rec006.py | 25 +++++++++++++++++++++++++ tests/sanity/test_record_checkers.py | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 t4_devkit/sanity/record/rec006.py diff --git a/docs/schema/requirement.md b/docs/schema/requirement.md index 18ea8e3..c29ddfa 100644 --- a/docs/schema/requirement.md +++ b/docs/schema/requirement.md @@ -23,6 +23,7 @@ | `REC003` | `sample-data-not-empty` | `ERROR` | `SampleData` record is not empty. | | `REC004` | `ego-pose-not-empty` | `ERROR` | `EgoPose` record is not empty. | | `REC005` | `calibrated-sensor-non-empty` | `ERROR` | `CalibratedSensor` record is not empty. | +| `REC006` | `instance-not-empty` | `ERROR` | `Instance` record is not empty. | ## Reference (`REF`) diff --git a/t4_devkit/sanity/record/__init__.py b/t4_devkit/sanity/record/__init__.py index 61e0927..a4980bd 100644 --- a/t4_devkit/sanity/record/__init__.py +++ b/t4_devkit/sanity/record/__init__.py @@ -5,3 +5,4 @@ from .rec003 import * # noqa from .rec004 import * # noqa from .rec005 import * # noqa +from .rec006 import * # noqa diff --git a/t4_devkit/sanity/record/rec006.py b/t4_devkit/sanity/record/rec006.py new file mode 100644 index 0000000..0edee0c --- /dev/null +++ b/t4_devkit/sanity/record/rec006.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from t4_devkit.schema import SchemaName + +from ..checker import RuleID, RuleName, Severity +from ..registry import CHECKERS +from ..result import Reason +from .base import RecordCountChecker + +__all__ = ["REC006"] + + +@CHECKERS.register() +class REC006(RecordCountChecker): + """A checker of REC006.""" + + id = RuleID("REC006") + name = RuleName("instance-not-empty") + severity = Severity.ERROR + description = "'Instance' record is not empty." + schema = SchemaName.INSTANCE + + def check_count(self, records: list[dict]) -> list[Reason] | None: + num_instance = len(records) + return [Reason("'Instance' record must not be empty")] if num_instance == 0 else None diff --git a/tests/sanity/test_record_checkers.py b/tests/sanity/test_record_checkers.py index 764102e..5c3710f 100644 --- a/tests/sanity/test_record_checkers.py +++ b/tests/sanity/test_record_checkers.py @@ -12,6 +12,7 @@ from t4_devkit.sanity.record.rec003 import REC003 from t4_devkit.sanity.record.rec004 import REC004 from t4_devkit.sanity.record.rec005 import REC005 +from t4_devkit.sanity.record.rec006 import REC006 # Base sample dataset root (contains all mandatory annotation json files with non-empty records) SAMPLE_ROOT = Path(__file__).parent.parent.joinpath("sample", "t4dataset") @@ -142,3 +143,21 @@ def test_rec005_fail_calibrated_sensor_empty(tmp_path: Path) -> None: report = checker(_context(root)) assert not report.is_passed(strict=True) assert report.reasons and report.reasons[0] == "'CalibratedSensor' record must not be empty" + + +# ---------------- REC006 (instance-not-empty) ---------------- + + +def test_rec006_pass_instance_not_empty() -> None: + checker = REC006() + report = checker(_context(SAMPLE_ROOT)) + assert report.is_passed(strict=True) + assert report.reasons is None + + +def test_rec006_fail_instance_empty(tmp_path: Path) -> None: + root = _make_mutated_dataset(tmp_path, {"instance.json": []}) + checker = REC006() + report = checker(_context(root)) + assert not report.is_passed(strict=True) + assert report.reasons and report.reasons[0] == "'Instance' record must not be empty" From 6369902c89bea7fd70ab68bb3575c15812a1471d Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Wed, 3 Dec 2025 10:11:31 +0900 Subject: [PATCH 3/3] fix: update to run checking only if either sample_ann or object_ann is not empty Signed-off-by: ktro2828 --- docs/schema/requirement.md | 16 +++++------ t4_devkit/sanity/record/rec006.py | 40 +++++++++++++++++++++++++++- tests/sanity/test_record_checkers.py | 13 +++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/docs/schema/requirement.md b/docs/schema/requirement.md index c29ddfa..0717efe 100644 --- a/docs/schema/requirement.md +++ b/docs/schema/requirement.md @@ -16,14 +16,14 @@ ## Schema Record (`REC`) -| ID | Name | Severity | Description | -| -------- | ----------------------------- | -------- | --------------------------------------- | -| `REC001` | `scene-single` | `ERROR` | `Scene` record is a single. | -| `REC002` | `sample-not-empty` | `ERROR` | `Sample` record is not empty. | -| `REC003` | `sample-data-not-empty` | `ERROR` | `SampleData` record is not empty. | -| `REC004` | `ego-pose-not-empty` | `ERROR` | `EgoPose` record is not empty. | -| `REC005` | `calibrated-sensor-non-empty` | `ERROR` | `CalibratedSensor` record is not empty. | -| `REC006` | `instance-not-empty` | `ERROR` | `Instance` record is not empty. | +| ID | Name | Severity | Description | +| -------- | ----------------------------- | -------- | ---------------------------------------------------------------------------------------- | +| `REC001` | `scene-single` | `ERROR` | `Scene` record is a single. | +| `REC002` | `sample-not-empty` | `ERROR` | `Sample` record is not empty. | +| `REC003` | `sample-data-not-empty` | `ERROR` | `SampleData` record is not empty. | +| `REC004` | `ego-pose-not-empty` | `ERROR` | `EgoPose` record is not empty. | +| `REC005` | `calibrated-sensor-non-empty` | `ERROR` | `CalibratedSensor` record is not empty. | +| `REC006` | `instance-not-empty` | `ERROR` | `Instance` record is not empty if either 'SampleAnnotation' or 'ObjectAnn' is not empty. | ## Reference (`REF`) diff --git a/t4_devkit/sanity/record/rec006.py b/t4_devkit/sanity/record/rec006.py index 0edee0c..1ed9ecc 100644 --- a/t4_devkit/sanity/record/rec006.py +++ b/t4_devkit/sanity/record/rec006.py @@ -1,12 +1,20 @@ from __future__ import annotations +from typing import TYPE_CHECKING + +from returns.maybe import Maybe, Nothing, Some + from t4_devkit.schema import SchemaName from ..checker import RuleID, RuleName, Severity from ..registry import CHECKERS from ..result import Reason +from ..safety import load_json_safe from .base import RecordCountChecker +if TYPE_CHECKING: + from ..context import SanityContext + __all__ = ["REC006"] @@ -17,9 +25,39 @@ class REC006(RecordCountChecker): id = RuleID("REC006") name = RuleName("instance-not-empty") severity = Severity.ERROR - description = "'Instance' record is not empty." + description = ( + "'Instance' record is not empty if either 'SampleAnnotation' or 'ObjectAnn' is not empty." + ) schema = SchemaName.INSTANCE + def can_skip(self, context: SanityContext) -> Maybe[Reason]: + # return skip reason if instance.json does not exist + match super().can_skip(context): + case Some(x): + return Maybe.from_value(x) + + # instance.json should contain any records if either + # SampleAnnotation or ObjectAnn records exist + sample_ann_file = context.to_schema_file(SchemaName.SAMPLE_ANNOTATION) + object_ann_file = context.to_schema_file(SchemaName.OBJECT_ANN) + + match (sample_ann_file.value_or(None), object_ann_file.value_or(None)): + case (None, _) | (_, None): + return Maybe.from_value(Reason("Missing 'annotation' directory")) + case (s, o): + sample_ann_count = len(load_json_safe(s).unwrap()) if s.exists() else 0 + object_ann_count = len(load_json_safe(o).unwrap()) if o.exists() else 0 + return ( + Maybe.from_value( + Reason( + f"Both {SchemaName.SAMPLE_ANNOTATION} " + f"and {SchemaName.OBJECT_ANN} records are empty" + ) + ) + if sample_ann_count == 0 and object_ann_count == 0 + else Nothing + ) + def check_count(self, records: list[dict]) -> list[Reason] | None: num_instance = len(records) return [Reason("'Instance' record must not be empty")] if num_instance == 0 else None diff --git a/tests/sanity/test_record_checkers.py b/tests/sanity/test_record_checkers.py index 5c3710f..9ada4b8 100644 --- a/tests/sanity/test_record_checkers.py +++ b/tests/sanity/test_record_checkers.py @@ -13,6 +13,7 @@ from t4_devkit.sanity.record.rec004 import REC004 from t4_devkit.sanity.record.rec005 import REC005 from t4_devkit.sanity.record.rec006 import REC006 +from t4_devkit.schema.name import SchemaName # Base sample dataset root (contains all mandatory annotation json files with non-empty records) SAMPLE_ROOT = Path(__file__).parent.parent.joinpath("sample", "t4dataset") @@ -155,6 +156,18 @@ def test_rec006_pass_instance_not_empty() -> None: assert report.reasons is None +def test_rec006_skip_annotation_empty(tmp_path: Path) -> None: + root = _make_mutated_dataset(tmp_path, {"sample_annotation.json": [], "object_ann.json": []}) + checker = REC006() + report = checker(_context(root)) + assert report.is_passed(strict=True) + assert ( + report.reasons + and report.reasons[0] + == f"Both {SchemaName.SAMPLE_ANNOTATION} and {SchemaName.OBJECT_ANN} records are empty" + ) + + def test_rec006_fail_instance_empty(tmp_path: Path) -> None: root = _make_mutated_dataset(tmp_path, {"instance.json": []}) checker = REC006()