twanghcmut's picture
download
raw
5.18 kB
"""Reconcile clip-key parsing and track resampling against :class:`~fpgm.types.ClipTiming`.
Three clocks are in play: the ``"{start}:{end}"`` clip key (trajectory-fps frame
indices), the mp4 container's own frame indices (``mp4_fps``), and wall-clock
seconds. :class:`~fpgm.types.ClipTiming` exposes the conversions; this module builds
one from a clip key and uses it to resample a :class:`~fpgm.types.Track2D` (mp4-frame
indexed) onto the integer clip-frame axis that ``scene_flows`` shares.
"""
from __future__ import annotations
import numpy as np
from fpgm.types import ClipTiming, Track2D
def build_timing(clip_key: str, trajectory_fps: float, mp4_fps: float) -> ClipTiming:
"""Parse a ``"{start}:{end}"`` scene-flow clip group key into a :class:`ClipTiming`.
Args:
clip_key: e.g. ``"1204:1450"``, the h5 group name for one clip.
trajectory_fps: fps of the ``scene_flows``/``joint_positions`` clock.
mp4_fps: fps read from the mp4 container (not assumed equal to ``trajectory_fps``).
Raises:
ValueError: if ``clip_key`` is not of the form ``"{int}:{int}"``.
"""
parts = clip_key.split(":")
if len(parts) != 2:
raise ValueError(f"clip key {clip_key!r} is not of the form '{{start}}:{{end}}'")
try:
start, end = int(parts[0]), int(parts[1])
except ValueError as exc:
raise ValueError(f"clip key {clip_key!r} has non-integer bounds") from exc
if end <= start:
raise ValueError(f"clip key {clip_key!r} has end <= start")
return ClipTiming(
clip_start_frame=start, clip_end_frame=end, trajectory_fps=trajectory_fps, mp4_fps=mp4_fps
)
def resample_track_to_clip_frames(track2d: Track2D, timing: ClipTiming) -> Track2D:
"""Resample an mp4-frame-indexed :class:`Track2D` onto integer clip-frame indices.
``track2d.frames`` holds absolute mp4 frame indices; the output is re-indexed to
the ``[0, timing.n_frames)`` clip-local frame axis that ``scene_flows`` shares,
with ``uv`` **linearly** interpolated in time (not nearest-frame snapped, since
DROID's mp4 and trajectory clocks are rarely frame-aligned). Clip frames whose
corresponding continuous mp4-time position falls outside ``track2d.frames``'
covered range are marked ``visible=False`` rather than extrapolated -- an
extrapolated position outside the tracked window is fabricated data.
Note:
The returned ``Track2D.frames`` holds clip-local integer frame indices
(0-based, aligned with the ``scene_flows`` T axis), not mp4 frame indices --
this is what downstream depth queries and lifting index by.
Args:
track2d: Source track, uv valid at ``track2d.resolution``, indexed by
``track2d.frames`` (absolute mp4 frame indices).
timing: Clock reconciliation for the clip being resampled onto.
Returns:
A new :class:`Track2D` with ``frames = arange(timing.n_frames)``.
"""
n_out = timing.n_frames
clip_frames = np.arange(n_out, dtype=np.int64)
seconds = timing.clip_frame_to_seconds(clip_frames)
query_video_frame = seconds * timing.mp4_fps # (n_out,) float positions
src_frames = track2d.frames.astype(np.float64) # (T_src,)
n_src = src_frames.shape[0]
n_points = track2d.uv.shape[1]
if n_src < 2:
# A single source sample cannot be linearly interpolated; only an exact
# time match is meaningful, everything else is out of coverage.
out_uv = np.zeros((n_out, n_points, 2), dtype=np.float32)
out_visible = np.zeros((n_out, n_points), dtype=bool)
if n_src == 1:
exact = np.isclose(query_video_frame, src_frames[0])
out_uv[exact] = track2d.uv[0]
out_visible[exact] = track2d.visible[0]
return Track2D(
point_id=track2d.point_id,
frames=clip_frames.astype(np.int32),
uv=out_uv,
visible=out_visible,
resolution=track2d.resolution,
)
in_coverage = (query_video_frame >= src_frames[0]) & (query_video_frame <= src_frames[-1])
# Vectorised (over both output-time and query points at once) linear
# interpolation via searchsorted -- no Python loop over points.
right_idx = np.searchsorted(src_frames, query_video_frame, side="right")
idx1 = np.clip(right_idx, 1, n_src - 1)
idx0 = idx1 - 1
t0 = src_frames[idx0]
t1 = src_frames[idx1]
denom = t1 - t0
frac = np.where(denom > 0, (query_video_frame - t0) / np.where(denom > 0, denom, 1.0), 0.0)
frac = frac.reshape(n_out, 1, 1)
uv0 = track2d.uv[idx0] # (n_out, Q, 2)
uv1 = track2d.uv[idx1] # (n_out, Q, 2)
out_uv = (uv0 * (1.0 - frac) + uv1 * frac).astype(np.float32)
vis0 = track2d.visible[idx0] # (n_out, Q)
vis1 = track2d.visible[idx1] # (n_out, Q)
out_visible = vis0 & vis1 & in_coverage.reshape(n_out, 1)
out_uv = np.where(out_visible[..., None], out_uv, np.nan).astype(np.float32)
return Track2D(
point_id=track2d.point_id,
frames=clip_frames.astype(np.int32),
uv=out_uv,
visible=out_visible,
resolution=track2d.resolution,
)

Xet Storage Details

Size:
5.18 kB
·
Xet hash:
4935235f3945809c5484cb43ed1b77c0735a7543d7b6681599bace057f74ee6f

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.