Match EasyCom oracle labels to EgoSpeak sampling
This commit is contained in:
@@ -64,36 +64,49 @@ def participant_activity(
|
||||
*,
|
||||
num_steps: int,
|
||||
wearer_id: int,
|
||||
) -> tuple[dict[int, torch.BoolTensor], torch.BoolTensor, dict[str, int]]:
|
||||
"""Rasterize inclusive 20 Hz transcript spans into observed 5 Hz bins."""
|
||||
) -> tuple[dict[int, torch.BoolTensor], torch.BoolTensor, dict[str, Any]]:
|
||||
"""Sample transcript activity on EgoSpeak's 5 Hz right-edge grid.
|
||||
|
||||
EasyCom stores one-based ``Start_Frame``/``End_Frame`` markers. EgoSpeak's
|
||||
released targets sample source-frame markers 4, 8, 12, ... and use
|
||||
``Start_Frame <= marker < End_Frame``. ``load_utterances`` represents the
|
||||
corresponding interval as ``[Start_Frame - 1, End_Frame)`` on its
|
||||
zero-origin dense timeline, hence the ``+1`` below. This is deliberately
|
||||
point sampling, not an any-overlap reduction over a 200 ms bin.
|
||||
"""
|
||||
|
||||
activity: dict[int, torch.BoolTensor] = {}
|
||||
wearer = torch.zeros(num_steps, dtype=torch.bool)
|
||||
discarded_terminal_frames = 0
|
||||
terminal_frames_after_last_sample = 0
|
||||
grid_frames = num_steps * FRAMES_PER_TASK_STEP
|
||||
for row in utterances:
|
||||
dense_start = int(row.dense_start_frame)
|
||||
# Recover the source annotation marker from the zero-origin interval.
|
||||
dense_start = int(row.dense_start_frame) + 1
|
||||
dense_end = int(row.dense_end_frame_exclusive)
|
||||
if dense_end > grid_frames:
|
||||
overrun = dense_end - grid_frames
|
||||
if overrun > FRAMES_PER_TASK_STEP - 1:
|
||||
raise ValueError(
|
||||
f"Transcript exceeds the discarded terminal sub-step by "
|
||||
f"Transcript exceeds the final 5 Hz sample by "
|
||||
f"{overrun} frames: {row.uid}"
|
||||
)
|
||||
discarded_terminal_frames += max(
|
||||
0, dense_end - max(dense_start, grid_frames)
|
||||
)
|
||||
dense_end = grid_frames
|
||||
terminal_frames_after_last_sample += overrun
|
||||
if dense_start >= dense_end:
|
||||
continue
|
||||
first = dense_start // FRAMES_PER_TASK_STEP
|
||||
last = (dense_end - 1) // FRAMES_PER_TASK_STEP
|
||||
if first < 0 or last < first or last >= num_steps:
|
||||
|
||||
# Sample number k (one-origin) observes marker 4*k and maps to row k-1.
|
||||
# The exclusive stop follows directly from 4*k < dense_end.
|
||||
first = (dense_start + FRAMES_PER_TASK_STEP - 1) // FRAMES_PER_TASK_STEP - 1
|
||||
stop = (dense_end + FRAMES_PER_TASK_STEP - 1) // FRAMES_PER_TASK_STEP - 1
|
||||
first = max(0, first)
|
||||
stop = min(num_steps, stop)
|
||||
if first > num_steps or stop < 0:
|
||||
raise ValueError(
|
||||
f"Transcript interval leaves target grid: {row.uid}, "
|
||||
f"bins=[{first},{last}], target_steps={num_steps}"
|
||||
f"rows=[{first},{stop}), target_steps={num_steps}"
|
||||
)
|
||||
if first >= stop:
|
||||
continue
|
||||
destination = (
|
||||
wearer
|
||||
if int(row.participant_id) == wearer_id
|
||||
@@ -102,9 +115,12 @@ def participant_activity(
|
||||
torch.zeros(num_steps, dtype=torch.bool),
|
||||
)
|
||||
)
|
||||
destination[first : last + 1] = True
|
||||
destination[first:stop] = True
|
||||
return activity, wearer, {
|
||||
"discarded_terminal_transcript_frames": discarded_terminal_frames
|
||||
"target_sampling_policy": "egospeak-right-edge-start-inclusive-end-exclusive",
|
||||
"terminal_transcript_frames_after_last_5hz_sample": (
|
||||
terminal_frames_after_last_sample
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -323,7 +339,6 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--delay-seconds", type=float, default=1.0)
|
||||
parser.add_argument("--camera-wearer-id", type=int, default=CAMERA_WEARER_ID)
|
||||
parser.add_argument("--shuffle-seed", type=int, default=2027)
|
||||
parser.add_argument("--max-label-mismatch-rows", type=int, default=0)
|
||||
parser.add_argument("--expected-mode", default="open-stream-replay")
|
||||
parser.add_argument("--reuse-existing", action="store_true")
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
@@ -335,8 +350,6 @@ def main() -> None:
|
||||
args = parse_args()
|
||||
history_steps = exact_steps(args.history_seconds, "--history-seconds")
|
||||
delay_steps = exact_steps(args.delay_seconds, "--delay-seconds")
|
||||
if args.max_label_mismatch_rows < 0:
|
||||
raise ValueError("--max-label-mismatch-rows cannot be negative")
|
||||
sessions = [normalize_sample_name(value, "easycom") for value in args.sessions]
|
||||
unknown = sorted(set(sessions) - set(ALL_SESSIONS))
|
||||
if unknown:
|
||||
@@ -405,12 +418,13 @@ def main() -> None:
|
||||
| (all_other_activity != target_tensor[:, 2].bool())
|
||||
).sum()
|
||||
)
|
||||
if mismatch_union > args.max_label_mismatch_rows:
|
||||
if mismatch_union:
|
||||
raise ValueError(
|
||||
f"{session}: transcript/target activity differs on "
|
||||
f"{mismatch_union} rows (wearer={wearer_mismatch}, "
|
||||
f"others={other_mismatch}); inspect alignment or explicitly "
|
||||
"raise --max-label-mismatch-rows"
|
||||
f"others={other_mismatch}) under the EgoSpeak right-edge "
|
||||
"sampling rule; inspect transcript ordering and dense-timeline "
|
||||
"alignment. Oracle preparation never tolerates label mismatch."
|
||||
)
|
||||
|
||||
visible_ids, source_records = visible_people_by_row(
|
||||
@@ -501,7 +515,7 @@ def main() -> None:
|
||||
config = {
|
||||
"oracle_analysis": True,
|
||||
"uses_speech_labels": True,
|
||||
"causal_policy": "fully-observed-half-open-5hz-bins",
|
||||
"causal_policy": "egospeak-right-edge-current-state",
|
||||
"oracle_condition": condition,
|
||||
"oracle_scope": "currently-visible-nonwearer-participants",
|
||||
"history_steps": history_steps,
|
||||
@@ -531,10 +545,9 @@ def main() -> None:
|
||||
"audio_valid": audio_valid,
|
||||
"persons": {
|
||||
"features": features,
|
||||
# Bin k covers [k/5,(k+1)/5); its feature is available at
|
||||
# the matching state cutoff (k+1)/5.
|
||||
"frame_times": torch.arange(len(times), dtype=torch.float64)
|
||||
* STEP_SECONDS,
|
||||
# This oracle state is sampled exactly at the matching
|
||||
# reference cutoff and is available at that cutoff.
|
||||
"frame_times": times.clone(),
|
||||
"frame_offsets": frame_offsets,
|
||||
"records": records,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user