Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/schema/requirement.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down
40 changes: 39 additions & 1 deletion t4_devkit/sanity/record/rec006.py
Original file line number Diff line number Diff line change
@@ -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"]


Expand All @@ -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
13 changes: 13 additions & 0 deletions tests/sanity/test_record_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down
Loading