Visitar URL original
`InferenceRunner` + `analyze_videos`: Fix top-down analysis crashing for zero-detections and unreadable video handling by deruyter92 · Pull Request #3488 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
64a2f4e
fix _generate_output_data crash on empty predictions
deruyter92 Sep 8, 2026
510090e
Allow create_df_from_prediction() to handle an empty predictions list.
deruyter92 Sep 8, 2026
4b45fb9
Treat zero predictions as a distinct warning case in `video_inference()`
deruyter92 Sep 8, 2026
c78f439
Don't write dataframe for empty predictions
deruyter92 Sep 9, 2026
16a2977
update test_videos: pop `"PAFinds"` before comparison
deruyter92 Sep 9, 2026
f534de6
update test_videos: assert no DF is written for empty predictions
deruyter92 Sep 9, 2026
624b1b1
Return empty-detection inference results
C-Achard Sep 9, 2026
c57dd35
Use CTD predictions for video output
C-Achard Sep 9, 2026
4e4dab7
Handle empty prediction outputs gracefully
C-Achard Sep 9, 2026
42098ac
Reuse shared frame key width helper
C-Achard Sep 9, 2026
292d068
Clarify video inference warnings
C-Achard Sep 9, 2026
d5ba290
Go back to ValueError for empty predictions
C-Achard Sep 9, 2026
bff4fea
Remove NaN fallback for empty video predictions
C-Achard Sep 9, 2026
06486de
Fix CTD export guard for empty predictions
C-Achard Sep 9, 2026
e456aeb
Clarify empty CTD prediction handling
C-Achard Sep 9, 2026
a162439
Centralize test fakes
C-Achard Sep 9, 2026
11ff4fd
Add test for async with no images
C-Achard Sep 9, 2026
a210b69
Clarify async inference test comment
C-Achard Sep 9, 2026
c7a8c34
Use shared frame key width helper
C-Achard Sep 9, 2026
8248a10
Merge branch 'dev' into jaap/fix-empty-video-predictions
C-Achard Sep 17, 2026
90849a5
Don't write pickle for unreadable video
deruyter92 Oct 22, 2026
4b8c0ca
Video inference: update warning for empty predictions
deruyter92 Oct 22, 2026
44918dd
InferenceRunner: don't emit results for producer exceptions
deruyter92 Oct 22, 2026
0e40ba8
update code comment
deruyter92 Oct 22, 2026
b0fbd36
fix ShelfWriter: report zero framens when empty
deruyter92 Oct 22, 2026
ce627ae
Skip unreadable videos with shelf writer + improve docstring and log …
deruyter92 Oct 22, 2026
4867b92
Merge branch 'dev' into jaap/fix-empty-video-predictions
deruyter92 Oct 22, 2026
89d06d0
Fix warning typo + update tests
deruyter92 Oct 22, 2026
a67e257
Remove unreachable CTD empty-export branch and reword FMPose error
deruyter92 Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Clarify video inference warnings
Refines `video_inference` logging so empty results distinguish unreadable videos from detector misses, and keeps re-encoding guidance for partial reads. Expands tests to cover zero-prediction, partial-prediction, and shelf-writer cases.
  • Loading branch information
C-Achard committed Sep 9, 2026
commit 292d068807d0eb4f85122d8ff75bc52cf34c28f5
32 changes: 20 additions & 12 deletions deeplabcut/pose_estimation_pytorch/apis/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,21 +229,29 @@ def video_inference(
if shelf_writer is not None:
shelf_writer.close()

if shelf_writer is None and len(predictions) == 0 and n_frames > 0:
logging.warning(
"No predictions were produced for this video. This can happen if no "
"animals were detected in any frame. If that is unexpected, check "
"model performance and verify that the video can be read correctly."
)
elif shelf_writer is None and len(predictions) != n_frames:
if shelf_writer is None:
tip_url = "https://deeplabcut.github.io/DeepLabCut/docs/recipes/io.html"
header = "#tips-on-video-re-encoding-and-preprocessing"
logging.warning(
f"The video metadata indicates that there {n_frames} in the video, but "
f"only {len(predictions)} were able to be processed. This can happen if "
"the video is corrupted. You can try to fix the issue by re-encoding your "
f"video (tips on how to do that: {tip_url}{header})"
reencoding_tip = (
"This can happen if the video is corrupted. You can try to fix the issue "
f"by re-encoding your video (tips on how to do that: {tip_url}{header})"
)
if len(predictions) == 0:
causes = (
"no animals were detected in any frame, or the video could not be read"
if detector_runner is not None
else "the video could not be read"
)
logging.warning(
f"No predictions were produced for {video.video_path}: {causes}. "
f"Check model performance if that is unexpected. {reencoding_tip}"
)
elif len(predictions) != n_frames:
logging.warning(
f"The video metadata indicates that there are {n_frames} frames in "
f"the video, but only {len(predictions)} were able to be processed. "
f"{reencoding_tip}"
)

return predictions

Expand Down
115 changes: 98 additions & 17 deletions tests/pose_estimation_pytorch/apis/test_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,108 @@ def test_create_df_from_prediction_rejects_empty_predictions(tmp_path):
assert not (tmp_path / "video.h5").exists()


def test_video_inference_warns_distinctly_for_zero_predictions(monkeypatch, caplog):
class FakeVideoIterator:
def __init__(self, video_path, cropping=None):
self.video_path = video_path
self.fps = 25
self.dimensions = (640, 480)
@pytest.fixture
def patch_video_iterator(monkeypatch):
"""Patches ``VideoIterator`` with a stub reporting ``n_frames`` empty frames."""

def get_n_frames(self, robust=False):
return 3
def _patch(n_frames: int):
class FakeVideoIterator:
def __init__(self, video_path, cropping=None):
self.video_path = video_path
self.fps = 25
self.dimensions = (640, 480)

def __iter__(self):
return iter(())
def get_n_frames(self, robust=False):
return n_frames

monkeypatch.setattr(videos, "VideoIterator", FakeVideoIterator)
pose_runner = Mock()
pose_runner.batch_size = 2
pose_runner.inference.return_value = []
def set_context(self, context):
pass

def __iter__(self):
return iter(())

monkeypatch.setattr(videos, "VideoIterator", FakeVideoIterator)
return FakeVideoIterator

return _patch


def _pose_runner(predictions: list) -> Mock:
runner = Mock()
runner.batch_size = 2
runner.inference.return_value = predictions
return runner


def test_video_inference_zero_predictions_warns_about_unreadable_video(patch_video_iterator, caplog):
"""Without a detector, every readable frame yields a prediction, so an empty
result means the video could not be read - not that no animals were found."""
patch_video_iterator(3)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", _pose_runner([]))

assert predictions == []
assert "No predictions were produced for video.mp4" in caplog.text
assert "the video could not be read" in caplog.text
assert "no animals were detected" not in caplog.text
# the re-encoding tip is the actionable advice in this case
assert "re-encoding your video" in caplog.text


def test_video_inference_zero_predictions_with_detector_warns_about_detections(patch_video_iterator, caplog):
patch_video_iterator(3)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", _pose_runner([]), detector_runner=_pose_runner([]))

assert predictions == []
assert "no animals were detected in any frame" in caplog.text
assert "the video could not be read" in caplog.text
assert "re-encoding your video" in caplog.text


def test_video_inference_zero_predictions_warns_when_frame_count_is_zero(patch_video_iterator, caplog):
"""A video whose metadata reports 0 frames must still warn."""
patch_video_iterator(0)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", _pose_runner([]))

assert predictions == []
assert "No predictions were produced for video.mp4" in caplog.text


def test_video_inference_warns_when_some_frames_are_missing(patch_video_iterator, caplog):
patch_video_iterator(3)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", _pose_runner([{}, {}]))

assert len(predictions) == 2
assert "there are 3 frames in the video, but only 2" in caplog.text
assert "re-encoding your video" in caplog.text
assert "No predictions were produced" not in caplog.text


def test_video_inference_does_not_warn_when_all_frames_are_predicted(patch_video_iterator, caplog):
patch_video_iterator(3)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", _pose_runner([{}, {}, {}]))

assert len(predictions) == 3
assert "No predictions were produced" not in caplog.text
assert "were able to be processed" not in caplog.text


def test_video_inference_does_not_warn_when_writing_to_a_shelf(patch_video_iterator, caplog):
"""The returned list is empty by design when a shelf writer is given."""
patch_video_iterator(3)

with caplog.at_level("WARNING"):
predictions = videos.video_inference("video.mp4", pose_runner)
predictions = videos.video_inference("video.mp4", _pose_runner([]), shelf_writer=Mock())

assert predictions == []
assert "No predictions were produced for this video." in caplog.text
assert "video is corrupted" not in caplog.text
assert "No predictions were produced" not in caplog.text
assert "were able to be processed" not in caplog.text