Handle Ego4D terminal voice endpoints

This commit is contained in:
Xin Wang
2026-09-15 13:27:01 +08:00
parent 86aa124a46
commit b3dc02b293
2 changed files with 48 additions and 5 deletions
+29 -5
View File
@@ -28,6 +28,7 @@ class CandidateConfig:
relation_window_seconds: float = 3.0
same_speaker_pause_max_seconds: float = 2.0
other_handover_max_seconds: float = 1.5
max_terminal_overrun_seconds: float = 0.1
@dataclass(frozen=True)
@@ -296,14 +297,24 @@ def derive_clip_candidates(
clip: dict[str, Any], *, split: str, config: CandidateConfig
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
segments, wearer_id = load_clip_segments(clip)
clip_end = _number(clip.get("clip_end_sec", max((row.end for row in segments), default=0.0)), field="clip_end_sec")
outside = [row for row in segments if row.end > clip_end + 1e-6]
if outside:
first = outside[0]
declared_clip_end = _number(
clip.get("clip_end_sec", max((row.end for row in segments), default=0.0)),
field="clip_end_sec",
)
maximum_segment_end = max((row.end for row in segments), default=declared_clip_end)
terminal_overrun = max(0.0, maximum_segment_end - declared_clip_end)
if terminal_overrun > config.max_terminal_overrun_seconds + 1e-9:
first = next(row for row in segments if row.end > declared_clip_end)
raise ValueError(
f"{clip.get('clip_uid')}: voice segment {first.uid} ends at {first.end:.6f}s "
f"after clip_end_sec={clip_end:.6f}; refusing to clamp or drop it"
f"after clip_end_sec={declared_clip_end:.6f}; terminal overrun "
f"{terminal_overrun:.6f}s exceeds the audited limit "
f"{config.max_terminal_overrun_seconds:.6f}s; refusing to clamp or drop it"
)
# Ego4D v1 has quantized terminal voice endpoints a few milliseconds past
# the 30-fps video endpoint (for example 300.008 vs 299.966667 seconds).
# Preserve that official endpoint and make the bounded exception explicit.
clip_end = max(declared_clip_end, maximum_segment_end)
candidates: list[dict[str, Any]] = []
wearer_segments = [row for row in segments if row.person_id == wearer_id]
@@ -409,6 +420,14 @@ def derive_clip_candidates(
))
control_sequence += 1
candidates.sort(key=lambda row: (float(row["event_time"]), str(row["uid"])))
for row in candidates:
row["temporal_evidence"].update({
"declared_clip_end_seconds": declared_clip_end,
"maximum_voice_segment_end_seconds": maximum_segment_end,
"terminal_voice_overrun_seconds": terminal_overrun,
"max_allowed_terminal_overrun_seconds": config.max_terminal_overrun_seconds,
"terminal_voice_endpoint_preserved_not_clamped": True,
})
return candidates, [_timeline_row(row) for row in segments]
@@ -462,6 +481,10 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--relation-window-seconds", type=float, default=3.0)
parser.add_argument("--same-speaker-pause-max-seconds", type=float, default=2.0)
parser.add_argument("--other-handover-max-seconds", type=float, default=1.5)
parser.add_argument(
"--max-terminal-overrun-seconds", type=float, default=0.1,
help="Allow and audit a bounded official voice endpoint past clip_end_sec (default: %(default)s).",
)
parser.add_argument("--overwrite", action="store_true")
return parser.parse_args()
@@ -471,6 +494,7 @@ def main() -> None:
numeric = [
args.context_seconds, args.relation_window_seconds,
args.same_speaker_pause_max_seconds, args.other_handover_max_seconds,
args.max_terminal_overrun_seconds,
]
if any(value < 0 for value in numeric):
raise ValueError("all time windows must be non-negative")
+19
View File
@@ -114,6 +114,25 @@ class Ego4DAVCandidateTests(unittest.TestCase):
with self.assertRaisesRegex(ValueError, "exactly one camera wearer"):
load_clip_segments(clip)
def test_bounded_official_terminal_endpoint_is_preserved_and_audited(self) -> None:
clip = fixture_clip()
clip["clip_end_sec"] = 8.76
rows, _ = derive_clip_candidates(clip, split="val", config=CandidateConfig())
offset = next(
row for row in rows
if row["candidate_source"] == "WEARER_OFFSET" and row["event_time"] == 8.0
)
self.assertAlmostEqual(
offset["temporal_evidence"]["terminal_voice_overrun_seconds"], 0.04
)
def test_large_terminal_endpoint_overrun_is_rejected(self) -> None:
clip = fixture_clip()
clip["clip_end_sec"] = 11.95
clip["camera_wearer"]["voice_segments"].append(segment("wearer", 11.8, 12.2))
with self.assertRaisesRegex(ValueError, "exceeds the audited limit"):
derive_clip_candidates(clip, split="val", config=CandidateConfig())
if __name__ == "__main__":
unittest.main()