perf: throttle tracker to every 2nd frame, sup interval 10, 720p output option

- detect_interval=2 on both pipelines; stabilizer 10-frame hold bridges
  skipped frames (GPU inference cut ~half during active batch)
- sup_det_interval 5 -> 10 (still <= stabilizer hold / synthetic max_age)
- preview_every_n default 2 -> 5
- AnnotatedVideoWriter: codec auto-chain gstreamer_nvenc -> avc1 -> mp4v,
  max_height downscale (even dims, INTER_AREA); backend logged
- output_max_height plumbed Job -> web checkbox (720p) -> CLI --output-height
- README: 720p option, CLI flag
- 153 tests pass (+13)
This commit is contained in:
jetson committed 2026-09-29 15:58:55 +07:00
1 parent ad41bf01e1
commit 07289a419a
10 files changed
+345 -19

No files matched your search

+125
View File
@@ -235,3 +235,128 @@ def test_run_job_merged_propagates_box_counts(tmp_path, monkeypatch):
assert r.box_loading_count == 6
assert r.box_unloading_count == 2
assert r.box_net_count == 4
# ── output_max_height plumbing ──────────────────────────────────────────
def test_job_output_max_height_default_none():
"""Job.output_max_height defaults to None."""
job = Job(
job_id="test-oh",
video_path="/tmp/test.mp4",
model_configs=[],
output_dir="/tmp/output",
)
assert job.output_max_height is None
def test_queue_add_job_output_max_height():
"""add_job passes output_max_height through to the Job."""
q = JobQueue(output_dir="/tmp/output")
job = q.add_job(video_path="/tmp/test.mp4", model_configs=[],
output_max_height=720)
assert job.output_max_height == 720
def test_queue_add_job_output_max_height_default():
"""add_job without output_max_height keeps None."""
q = JobQueue(output_dir="/tmp/output")
job = q.add_job(video_path="/tmp/test.mp4", model_configs=[])
assert job.output_max_height is None
def _fake_single_job(tmp_path, output_max_height, monkeypatch):
import src.job as job_mod
from src.model_registry import ModelConfig
from src.pipeline import PipelineResult
seen = {}
def fake_run_pipeline(**kwargs):
seen.update(kwargs)
return PipelineResult(
output_path=str(tmp_path / "out.mp4"),
frame_count=10,
loading_count=1,
unloading_count=0,
batch_count=1,
duration_seconds=1.0,
model_name="a.pt",
class_filter=None,
)
monkeypatch.setattr(job_mod, "run_pipeline", fake_run_pipeline)
q = JobQueue(output_dir=str(tmp_path))
job = Job(
job_id="job-oh-single",
video_path="/tmp/test.mp4",
model_configs=[
ModelConfig(filename="a.pt", path=str(tmp_path / "a.pt"), stem="a",
known_classes=["sack", "truck"])
],
output_dir=str(tmp_path / "job-oh-single"),
output_max_height=output_max_height,
)
q._jobs[job.job_id] = job
q._run_job(job.job_id)
return job, seen
def test_run_job_single_passes_output_max_height(tmp_path, monkeypatch):
"""_run_job single branch forwards output_max_height=720 to run_pipeline."""
job, seen = _fake_single_job(tmp_path, 720, monkeypatch)
assert job.status == JobStatus.COMPLETED
assert seen.get("output_max_height") == 720
def test_run_job_single_output_max_height_none(tmp_path, monkeypatch):
"""_run_job single branch forwards output_max_height=None."""
job, seen = _fake_single_job(tmp_path, None, monkeypatch)
assert job.status == JobStatus.COMPLETED
assert seen.get("output_max_height") is None
def test_run_job_merged_passes_output_max_height(tmp_path, monkeypatch):
"""_run_job merged branch forwards output_max_height to run_merged_pipeline."""
import src.job as job_mod
from src.job import Job, JobQueue, JobStatus
from src.model_registry import ModelConfig
from src.pipeline import MergedPipelineResult
seen = {}
def fake_run_merged(**kwargs):
seen.update(kwargs)
return MergedPipelineResult(
output_path=str(tmp_path / "merged.mp4"),
frame_count=10,
loading_count=3,
unloading_count=1,
batch_count=1,
duration_seconds=1.0,
model_names=["a.pt", "b.pt"],
class_filter=None,
)
monkeypatch.setattr(job_mod, "run_merged_pipeline", fake_run_merged)
q = JobQueue(output_dir=str(tmp_path))
job = Job(
job_id="job-oh-merged",
video_path="/tmp/test.mp4",
model_configs=[
ModelConfig(filename="a.pt", path=str(tmp_path / "a.pt"), stem="a",
known_classes=["sack", "truck"]),
ModelConfig(filename="b.pt", path=str(tmp_path / "b.pt"), stem="b",
known_classes=["sack"]),
],
output_dir=str(tmp_path / "job-oh-merged"),
output_max_height=720,
)
q._jobs[job.job_id] = job
q._run_job(job.job_id)
assert job.status == JobStatus.COMPLETED
assert seen.get("output_max_height") == 720
+18
View File
@@ -327,3 +327,21 @@ def test_run_merged_pipeline_propagates_box_counts(tmp_path, monkeypatch):
assert result.box_loading_count == 4
assert result.box_unloading_count == 1
assert result.box_net_count == 3
# ── Perf/output tuning params (signature contracts) ────────────────────
@pytest.mark.parametrize(
"fn_name", ["run_pipeline", "run_merged_pipeline"]
)
def test_pipeline_perf_params_signature(fn_name):
"""Both runners expose detect_interval=2, output_max_height=None, preview_every_n=5."""
import inspect
from src import pipeline as pipe_mod
sig = inspect.signature(getattr(pipe_mod, fn_name))
assert sig.parameters["detect_interval"].default == 2
assert sig.parameters["output_max_height"].default is None
assert sig.parameters["preview_every_n"].default == 5
+69
View File
@@ -6,6 +6,18 @@ import pytest
from src.video_writer import AnnotatedVideoWriter
def probe(path):
cap = cv2.VideoCapture(str(path))
assert cap.isOpened(), f"cannot reopen {path}"
info = (
int(cap.get(cv2.CAP_PROP_FRAME_COUNT)),
int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
)
cap.release()
return info
def test_writer_creates_output_file(tmp_path):
out = tmp_path / "test_output.mp4"
writer = AnnotatedVideoWriter(str(out), fps=25.0, frame_size=(640, 480))
@@ -37,3 +49,60 @@ def test_writer_close_idempotent(tmp_path):
def test_writer_invalid_fps():
with pytest.raises(ValueError):
AnnotatedVideoWriter("/tmp/x.mp4", fps=0.0, frame_size=(640, 480))
def test_max_height_downscale(tmp_path):
out = tmp_path / "down.mp4"
writer = AnnotatedVideoWriter(
str(out), fps=25.0, frame_size=(640, 480), max_height=240
)
assert writer.written_size == (320, 240)
for _ in range(3):
writer.write_frame(np.zeros((480, 640, 3), dtype=np.uint8))
writer.finish()
assert out.exists() and out.stat().st_size > 0
count, w, h = probe(out)
assert count == 3
assert (w, h) == writer.written_size
def test_no_max_height_keeps_native_size(tmp_path):
out = tmp_path / "native.mp4"
writer = AnnotatedVideoWriter(str(out), fps=25.0, frame_size=(640, 480))
assert writer.written_size == (640, 480)
writer.write_frame(np.zeros((480, 640, 3), dtype=np.uint8))
writer.finish()
assert probe(out)[1:] == (640, 480)
def test_even_dimensions_after_downscale(tmp_path):
out = tmp_path / "even.mp4"
writer = AnnotatedVideoWriter(
str(out), fps=25.0, frame_size=(641, 481), max_height=240
)
tw, th = writer.written_size
assert tw % 2 == 0 and th % 2 == 0
assert th <= 240
writer.write_frame(np.zeros((481, 641, 3), dtype=np.uint8))
writer.finish()
assert probe(out)[1:] == writer.written_size
def test_auto_codec_records_backend(tmp_path):
out = tmp_path / "auto.mp4"
writer = AnnotatedVideoWriter(str(out), fps=25.0, frame_size=(640, 480))
assert isinstance(writer.backend, str)
assert writer.backend
writer.write_frame(np.zeros((480, 640, 3), dtype=np.uint8))
writer.finish()
def test_explicit_codec_forces_fourcc(tmp_path):
out = tmp_path / "explicit.mp4"
writer = AnnotatedVideoWriter(
str(out), fps=25.0, frame_size=(640, 480), codec="mp4v"
)
assert writer.backend == "mp4v"
writer.write_frame(np.zeros((480, 640, 3), dtype=np.uint8))
writer.finish()
assert out.exists() and out.stat().st_size > 0