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:
2026-03-14 11:31:44 +08:00
parent ede9690318
commit d4e2a59ad2
4 changed files with 131 additions and 14 deletions
+82
View File
@@ -927,6 +927,88 @@ def test_frame_pacer_emission_count_24_to_15() -> None:
assert 60 <= emitted <= 65
def test_pipeline_pacing_skips_window_growth_until_emitted() -> None:
from opengait_studio.pipeline import ScoliosisPipeline
with (
mock.patch("opengait_studio.pipeline.YOLO") as mock_yolo,
mock.patch("opengait_studio.pipeline.create_source") as mock_source,
mock.patch("opengait_studio.pipeline.create_publisher") as mock_publisher,
mock.patch("opengait_studio.pipeline.ScoNetDemo") as mock_classifier,
mock.patch("opengait_studio.pipeline.select_person") as mock_select_person,
mock.patch("opengait_studio.pipeline.mask_to_silhouette") as mock_mask_to_sil,
):
mock_detector = mock.MagicMock()
mock_box = mock.MagicMock()
mock_box.xyxy = np.array([[100, 100, 200, 300]], dtype=np.float32)
mock_box.id = np.array([1], dtype=np.int64)
mock_mask = mock.MagicMock()
mock_mask.data = np.random.rand(1, 480, 640).astype(np.float32)
mock_result = mock.MagicMock()
mock_result.boxes = mock_box
mock_result.masks = mock_mask
mock_detector.track.return_value = [mock_result]
mock_yolo.return_value = mock_detector
mock_source.return_value = []
mock_publisher.return_value = mock.MagicMock()
mock_model = mock.MagicMock()
mock_model.predict.return_value = ("neutral", 0.7)
mock_classifier.return_value = mock_model
dummy_mask = np.random.randint(0, 256, (480, 640), dtype=np.uint8)
dummy_bbox_mask = (100, 100, 200, 300)
dummy_bbox_frame = (100, 100, 200, 300)
dummy_silhouette = np.random.rand(64, 44).astype(np.float32)
mock_select_person.return_value = (
dummy_mask,
dummy_bbox_mask,
dummy_bbox_frame,
1,
)
mock_mask_to_sil.return_value = dummy_silhouette
pipeline = ScoliosisPipeline(
source="dummy.mp4",
checkpoint="dummy.pt",
config=str(CONFIG_PATH) if CONFIG_PATH.exists() else "dummy.yaml",
device="cpu",
yolo_model="dummy.pt",
window=2,
stride=1,
nats_url=None,
nats_subject="test",
max_frames=None,
target_fps=15.0,
)
frame = np.zeros((480, 640, 3), dtype=np.uint8)
first = pipeline.process_frame(
frame,
{"frame_count": 0, "timestamp_ns": 1_000_000_000},
)
second = pipeline.process_frame(
frame,
{"frame_count": 1, "timestamp_ns": 1_033_000_000},
)
third = pipeline.process_frame(
frame,
{"frame_count": 2, "timestamp_ns": 1_067_000_000},
)
assert first is not None
assert second is not None
assert third is not None
assert first["segmentation_input"] is not None
assert second["segmentation_input"] is not None
assert third["segmentation_input"] is not None
assert first["segmentation_input"].shape[0] == 1
assert second["segmentation_input"].shape[0] == 1
assert second["label"] is None
assert third["segmentation_input"].shape[0] == 2
assert third["label"] == "neutral"
def test_frame_pacer_requires_positive_target_fps() -> None:
from opengait_studio.pipeline import _FramePacer