fix(demo): pace gait windows before buffering
Make the OpenGait-studio demo drop unpaced frames before they grow the silhouette window. Separate source-frame gap tracking from paced-frame stride tracking so runtime scheduling matches the documented demo-window-and-stride behavior. Add regressions for paced window growth and schedule-frame stride semantics.
This commit is contained in:
@@ -148,6 +148,7 @@ class ScoliosisPipeline:
|
||||
_visualizer: OpenCVVisualizer | None
|
||||
_last_viz_payload: _VizPayload | None
|
||||
_frame_pacer: _FramePacer | None
|
||||
_paced_frame_idx: int
|
||||
_visualizer_accepts_pose_data: bool | None
|
||||
_visualizer_signature_owner: object | None
|
||||
|
||||
@@ -209,6 +210,7 @@ class ScoliosisPipeline:
|
||||
self._visualizer = None
|
||||
self._last_viz_payload = None
|
||||
self._frame_pacer = _FramePacer(target_fps) if target_fps is not None else None
|
||||
self._paced_frame_idx = -1
|
||||
self._visualizer_accepts_pose_data = None
|
||||
self._visualizer_signature_owner = None
|
||||
|
||||
@@ -393,8 +395,6 @@ class ScoliosisPipeline:
|
||||
"confidence": None,
|
||||
"pose": pose_data,
|
||||
}
|
||||
self._window.push(silhouette, frame_idx=frame_idx, track_id=track_id)
|
||||
|
||||
if self._frame_pacer is not None and not self._frame_pacer.should_emit(
|
||||
timestamp_ns
|
||||
):
|
||||
@@ -409,6 +409,13 @@ class ScoliosisPipeline:
|
||||
"confidence": None,
|
||||
"pose": pose_data,
|
||||
}
|
||||
self._paced_frame_idx += 1
|
||||
self._window.push(
|
||||
silhouette,
|
||||
frame_idx=frame_idx,
|
||||
track_id=track_id,
|
||||
schedule_frame_idx=self._paced_frame_idx,
|
||||
)
|
||||
segmentation_input = self._window.buffered_silhouettes
|
||||
|
||||
if not self._window.should_classify():
|
||||
|
||||
@@ -67,7 +67,8 @@ class SilhouetteWindow:
|
||||
stride: int
|
||||
gap_threshold: int
|
||||
_buffer: deque[Float[ndarray, "64 44"]]
|
||||
_frame_indices: deque[int]
|
||||
_source_frame_indices: deque[int]
|
||||
_schedule_frame_indices: deque[int]
|
||||
_track_id: int | None
|
||||
_last_classified_frame: int
|
||||
_frame_count: int
|
||||
@@ -91,12 +92,20 @@ class SilhouetteWindow:
|
||||
|
||||
# Bounded storage via deque
|
||||
self._buffer = deque(maxlen=window_size)
|
||||
self._frame_indices = deque(maxlen=window_size)
|
||||
self._source_frame_indices = deque(maxlen=window_size)
|
||||
self._schedule_frame_indices = deque(maxlen=window_size)
|
||||
self._track_id = None
|
||||
self._last_classified_frame = -1
|
||||
self._frame_count = 0
|
||||
|
||||
def push(self, sil: np.ndarray, frame_idx: int, track_id: int) -> None:
|
||||
def push(
|
||||
self,
|
||||
sil: np.ndarray,
|
||||
frame_idx: int,
|
||||
track_id: int,
|
||||
*,
|
||||
schedule_frame_idx: int | None = None,
|
||||
) -> None:
|
||||
"""Push a new silhouette into the window.
|
||||
|
||||
Automatically resets buffer on track ID change or frame gap
|
||||
@@ -112,8 +121,8 @@ class SilhouetteWindow:
|
||||
self.reset()
|
||||
|
||||
# Check for frame gap
|
||||
if self._frame_indices:
|
||||
last_frame = self._frame_indices[-1]
|
||||
if self._source_frame_indices:
|
||||
last_frame = self._source_frame_indices[-1]
|
||||
gap = frame_idx - last_frame
|
||||
if gap > self.gap_threshold:
|
||||
self.reset()
|
||||
@@ -129,7 +138,10 @@ class SilhouetteWindow:
|
||||
)
|
||||
|
||||
self._buffer.append(sil_array)
|
||||
self._frame_indices.append(frame_idx)
|
||||
self._source_frame_indices.append(frame_idx)
|
||||
self._schedule_frame_indices.append(
|
||||
frame_idx if schedule_frame_idx is None else schedule_frame_idx
|
||||
)
|
||||
self._frame_count += 1
|
||||
|
||||
def is_ready(self) -> bool:
|
||||
@@ -152,7 +164,7 @@ class SilhouetteWindow:
|
||||
if self._last_classified_frame < 0:
|
||||
return True
|
||||
|
||||
current_frame = self._frame_indices[-1]
|
||||
current_frame = self._schedule_frame_indices[-1]
|
||||
frames_since = current_frame - self._last_classified_frame
|
||||
return frames_since >= self.stride
|
||||
|
||||
@@ -185,15 +197,16 @@ class SilhouetteWindow:
|
||||
def reset(self) -> None:
|
||||
"""Reset the window, clearing all buffers and counters."""
|
||||
self._buffer.clear()
|
||||
self._frame_indices.clear()
|
||||
self._source_frame_indices.clear()
|
||||
self._schedule_frame_indices.clear()
|
||||
self._track_id = None
|
||||
self._last_classified_frame = -1
|
||||
self._frame_count = 0
|
||||
|
||||
def mark_classified(self) -> None:
|
||||
"""Mark current frame as classified, updating stride tracking."""
|
||||
if self._frame_indices:
|
||||
self._last_classified_frame = self._frame_indices[-1]
|
||||
if self._schedule_frame_indices:
|
||||
self._last_classified_frame = self._schedule_frame_indices[-1]
|
||||
|
||||
@property
|
||||
def current_track_id(self) -> int | None:
|
||||
@@ -212,9 +225,9 @@ class SilhouetteWindow:
|
||||
|
||||
@property
|
||||
def window_start_frame(self) -> int:
|
||||
if not self._frame_indices:
|
||||
if not self._source_frame_indices:
|
||||
raise ValueError("Window is empty")
|
||||
return int(self._frame_indices[0])
|
||||
return int(self._source_frame_indices[0])
|
||||
|
||||
@property
|
||||
def buffered_silhouettes(self) -> Float[ndarray, "n 64 44"]:
|
||||
|
||||
Reference in New Issue
Block a user