Visitar URL original
RTMpose_x superanimal_humanbody by MMathisLab · Pull Request #3043 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
062e07e
edge case fix
MMathisLab Jul 1, 2025
be335f3
fix
MMathisLab Jul 1, 2025
8698d86
best
MMathisLab Jul 1, 2025
240584b
rc10
MMathisLab Jul 1, 2025
83f34b8
humanbody model zoo
MMathisLab Jul 5, 2025
165ff4c
Merge branch 'main' into mwm/humanbody
MMathisLab Jul 5, 2025
277fb22
Update video_inference.py
MMathisLab Jul 5, 2025
1fecece
onwards
MMathisLab Jul 5, 2025
618483a
working
MMathisLab Jul 5, 2025
8e46f4e
simpler
MMathisLab Jul 5, 2025
3e6ec52
working
MMathisLab Jul 5, 2025
7fab6fc
import depth
MMathisLab Jul 5, 2025
dbd0fd4
better warning message
MMathisLab Jul 5, 2025
ed4f786
less brittle check for torchvision model
MMathisLab Jul 5, 2025
65d00f1
all files
MMathisLab Jul 5, 2025
501f142
video fix
MMathisLab Jul 5, 2025
6b464b3
adapt other API
MMathisLab Jul 5, 2025
7600167
fix
MMathisLab Jul 5, 2025
8ce206b
cleaning up my mistakes
MMathisLab Jul 5, 2025
3515ba5
superanimals working
MMathisLab Jul 5, 2025
63929e8
likely fix for failing test
MMathisLab Jul 5, 2025
9286699
Fix hard-coded identity_scores dimensions in identity accuracy tests …
Copilot Jul 5, 2025
2dbeea6
Fix visualization reshape error with descriptive error messages and v…
Copilot Jul 5, 2025
dda7254
revert to main behavior in # of indv
MMathisLab Jul 6, 2025
74f420f
fix utils for logic
MMathisLab Jul 6, 2025
aff5703
remove my local test file
MMathisLab Jul 6, 2025
59a46cb
Update fasterRCNN.py
MMathisLab Jul 6, 2025
bd32b30
cleaning
MMathisLab Jul 6, 2025
cb3ec0c
testing
MMathisLab Jul 6, 2025
3536746
Update superanimal_inference.py
MMathisLab Jul 6, 2025
91d43c1
longer test back to main status
MMathisLab Jul 6, 2025
fee2e2f
Leave comment in unittests on what should be done
AlexEMG Jul 6, 2025
02cbf93
Update TF docstring
AlexEMG Jul 6, 2025
9a9b2f8
small fix for 2x run for pose
MMathisLab Jul 6, 2025
686bd4a
Update version to rc11
AlexEMG Jul 6, 2025
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
Fix visualization reshape error with descriptive error messages and v…
…alidation (#3047)
  • Loading branch information
Copilot authored Jul 5, 2025
commit 2dbeea68b8a0cea05d7b42317850698b367a17a0
59 changes: 49 additions & 10 deletions deeplabcut/utils/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,37 @@
from deeplabcut.utils import auxiliaryfunctions, auxfun_videos


def safe_reshape(array: np.ndarray, shape: tuple, context: str = "") -> np.ndarray:
"""
Safely reshape an array with validation and descriptive error messages.

Args:
array: The numpy array to reshape
shape: The target shape for reshaping
context: Optional context string to include in error messages

Returns:
The reshaped array

Raises:
ValueError: If the array cannot be reshaped to the target shape with
a descriptive error message
"""
expected_size = np.prod(shape)
actual_size = array.size

if actual_size != expected_size:
context_str = f" for {context}" if context else ""
raise ValueError(
f"Cannot reshape array of size {actual_size} into shape {shape}{context_str}. "
f"Expected {expected_size} elements but got {actual_size}. "
f"This may indicate missing data or inconsistent data structure. "
f"Please verify that all expected individuals, bodyparts, and coordinates are present in the data."
)

return array.reshape(shape)


def get_cmap(n: int, name: str = "hsv") -> Colormap:
"""
Args:
Expand Down Expand Up @@ -514,8 +545,16 @@ def plot_evaluation_results(
df_predictions = row_multi[model_name]

# Shape (num_individuals, num_bodyparts, xy)
ground_truth = df_gt.to_numpy().reshape((individuals, bodyparts, 2))
predictions = df_predictions.to_numpy().reshape((individuals, bodyparts, 3))
ground_truth = safe_reshape(
df_gt.to_numpy(),
(individuals, bodyparts, 2),
f"ground truth data with {individuals} individuals and {bodyparts} bodyparts"
)
predictions = safe_reshape(
df_predictions.to_numpy(),
(individuals, bodyparts, 3),
f"prediction data with {individuals} individuals and {bodyparts} bodyparts"
)

bboxes = bounding_boxes.get(row_index)

Expand All @@ -527,15 +566,15 @@ def plot_evaluation_results(
unique_bodyparts = len(
row_unique.index.get_level_values("bodyparts").unique()
)
unique_ground_truth = (
row_unique[scorer]
.to_numpy()
.reshape((unique_individuals, unique_bodyparts, 2))
unique_ground_truth = safe_reshape(
row_unique[scorer].to_numpy(),
(unique_individuals, unique_bodyparts, 2),
f"unique ground truth data with {unique_individuals} individuals and {unique_bodyparts} bodyparts"
)
unique_predictions = (
row_unique[model_name]
.to_numpy()
.reshape((unique_individuals, unique_bodyparts, 3))
unique_predictions = safe_reshape(
row_unique[model_name].to_numpy(),
(unique_individuals, unique_bodyparts, 3),
f"unique prediction data with {unique_individuals} individuals and {unique_bodyparts} bodyparts"
)

fig, ax = create_minimal_figure()
Expand Down
127 changes: 127 additions & 0 deletions tests/test_visualization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#
# DeepLabCut Toolbox (deeplabcut.org)
# © A. & M.W. Mathis Labs
# https://github.com/DeepLabCut/DeepLabCut
#
# Please see AUTHORS for contributors.
# https://github.com/DeepLabCut/DeepLabCut/blob/master/AUTHORS
#
# Licensed under GNU Lesser General Public License v3.0
#

import numpy as np
import pytest
from deeplabcut.utils.visualization import safe_reshape


class TestSafeReshape:
"""Test cases for the safe_reshape function."""

def test_valid_reshape(self):
"""Test that valid reshapes work correctly."""
array = np.array([1, 2, 3, 4, 5, 6, 7, 8])
result = safe_reshape(array, (2, 2, 2))
expected = array.reshape((2, 2, 2))
np.testing.assert_array_equal(result, expected)

def test_valid_reshape_with_context(self):
"""Test that valid reshapes work correctly with context."""
array = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
result = safe_reshape(array, (2, 2, 3), "test context")
expected = array.reshape((2, 2, 3))
np.testing.assert_array_equal(result, expected)

def test_invalid_reshape_too_small(self):
"""Test that reshape with too few elements raises proper error."""
array = np.array([1, 2, 3, 4]) # 4 elements
with pytest.raises(ValueError) as exc_info:
safe_reshape(array, (2, 2, 2)) # Expects 8 elements

error_msg = str(exc_info.value)
assert "Cannot reshape array of size 4 into shape (2, 2, 2)" in error_msg
assert "Expected 8 elements but got 4" in error_msg
assert "missing data or inconsistent data structure" in error_msg

def test_invalid_reshape_too_large(self):
"""Test that reshape with too many elements raises proper error."""
array = np.array(range(20)) # 20 elements
with pytest.raises(ValueError) as exc_info:
safe_reshape(array, (2, 2, 2)) # Expects 8 elements

error_msg = str(exc_info.value)
assert "Cannot reshape array of size 20 into shape (2, 2, 2)" in error_msg
assert "Expected 8 elements but got 20" in error_msg

def test_invalid_reshape_with_context(self):
"""Test that reshape errors include context when provided."""
array = np.array([1, 2, 3, 4])
context = "ground truth data with 2 individuals and 2 bodyparts"

with pytest.raises(ValueError) as exc_info:
safe_reshape(array, (2, 2, 2), context)

error_msg = str(exc_info.value)
assert f"for {context}" in error_msg
assert "Cannot reshape array of size 4 into shape (2, 2, 2)" in error_msg

def test_empty_array(self):
"""Test that empty arrays are handled correctly."""
array = np.array([])
with pytest.raises(ValueError) as exc_info:
safe_reshape(array, (1, 1), "empty array")

error_msg = str(exc_info.value)
assert "Cannot reshape array of size 0 into shape (1, 1)" in error_msg

def test_original_error_scenario(self):
"""Test the specific error scenario from the issue."""
# This reproduces the exact error: "cannot reshape array of size 8 into shape (2,4,2)"
array = np.array([1, 2, 3, 4, 5, 6, 7, 8]) # 8 elements
with pytest.raises(ValueError) as exc_info:
safe_reshape(array, (2, 4, 2)) # Expects 16 elements

error_msg = str(exc_info.value)
assert "Cannot reshape array of size 8 into shape (2, 4, 2)" in error_msg
assert "Expected 16 elements but got 8" in error_msg

def test_single_dimension(self):
"""Test reshaping to single dimension."""
array = np.array([[1, 2], [3, 4]])
result = safe_reshape(array, (4,))
expected = array.reshape((4,))
np.testing.assert_array_equal(result, expected)

def test_multidimensional_input(self):
"""Test reshaping multidimensional input arrays."""
array = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # 2x2x2 = 8 elements
result = safe_reshape(array, (4, 2))
expected = array.reshape((4, 2))
np.testing.assert_array_equal(result, expected)

@pytest.mark.parametrize("shape", [
(2, 2, 2),
(1, 8, 1),
(4, 2),
(8,),
])
def test_various_valid_shapes(self, shape):
"""Test various valid reshape operations."""
array = np.arange(8)
result = safe_reshape(array, shape)
expected = array.reshape(shape)
np.testing.assert_array_equal(result, expected)

@pytest.mark.parametrize("array_size,shape", [
(6, (2, 2, 2)), # 6 elements into 8-element shape
(10, (2, 2, 2)), # 10 elements into 8-element shape
(4, (3, 3)), # 4 elements into 9-element shape
(0, (2, 2)), # empty array into non-empty shape
])
def test_various_invalid_shapes(self, array_size, shape):
"""Test various invalid reshape operations."""
array = np.arange(array_size)
with pytest.raises(ValueError) as exc_info:
safe_reshape(array, shape)

error_msg = str(exc_info.value)
assert f"Cannot reshape array of size {array_size} into shape {shape}" in error_msg