From abe5a23c79f798cdf38414669a2bce52d1e6b3ea Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Tue, 14 Oct 2025 15:22:27 -0400 Subject: [PATCH 1/7] Add error handling to to handle empty detections list --- src/mouse_tracking/matching/vectorized_features.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mouse_tracking/matching/vectorized_features.py b/src/mouse_tracking/matching/vectorized_features.py index a3ed4c97..526a2e11 100644 --- a/src/mouse_tracking/matching/vectorized_features.py +++ b/src/mouse_tracking/matching/vectorized_features.py @@ -35,6 +35,10 @@ def __init__(self, detections: list[Detection]): def _extract_poses(self, detections: list[Detection]) -> np.ndarray: """Extract pose data into a vectorized array.""" + if len(detections) == 0: + # Return properly shaped empty array + return np.zeros((0, 12, 2), dtype=np.float64) + poses = [] for det in detections: if det.pose is not None: From 8307b088556704a9c8b98014f134e4816c5b3c74 Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Tue, 14 Oct 2025 15:38:45 -0400 Subject: [PATCH 2/7] Update tests associated with empty list fix --- .../test_compute_vectorized_detection_features.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/matching/vectorized_features/test_compute_vectorized_detection_features.py b/tests/matching/vectorized_features/test_compute_vectorized_detection_features.py index e516a170..48a2ac31 100644 --- a/tests/matching/vectorized_features/test_compute_vectorized_detection_features.py +++ b/tests/matching/vectorized_features/test_compute_vectorized_detection_features.py @@ -33,11 +33,9 @@ def test_init_empty_detections(self): assert features.n_detections == 0 assert features.detections == [] - assert features.poses.shape == (0,) # Empty array has shape (0,) + assert features.poses.shape == (0, 12, 2) # Properly shaped empty array assert features.embeddings.shape == (0, 0) # Empty embeddings - assert ( - features.valid_pose_masks.shape == () - ) # Empty array results in scalar shape + assert features.valid_pose_masks.shape == (0, 12) # Properly shaped empty mask assert features.valid_embed_masks.shape == (0,) def test_init_mixed_valid_invalid(self, detection_factory): From 1a8862900a2eb3612ccaa768af09df17181aacd1 Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Tue, 14 Oct 2025 15:43:25 -0400 Subject: [PATCH 3/7] Additional test fix related to empty list fix --- .../test_compute_vectorized_pose_distances.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py index 553235e3..9bfca1a8 100644 --- a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py +++ b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py @@ -179,10 +179,12 @@ def test_pose_distances_empty_features(self): features1 = VectorizedDetectionFeatures([]) features2 = VectorizedDetectionFeatures([]) - # This will likely crash due to empty array indexing - mark as expected behavior - # TODO: This reveals a bug in the function with empty features - with pytest.raises(IndexError): - compute_vectorized_pose_distances(features1, features2) + # Should handle empty features gracefully + distances = compute_vectorized_pose_distances(features1, features2) + + # Should return empty distance matrix with correct shape + assert distances.shape == (0, 0) + assert distances.dtype == np.float64 def test_pose_distances_single_detection(self, features_factory): """Test pose distance computation with single detection.""" From 2e19326372861a31b10588ed2a44941ff0d857ed Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Tue, 14 Oct 2025 15:44:24 -0400 Subject: [PATCH 4/7] Additional test fix related to empty list fix --- tests/matching/vectorized_features/test_get_rotated_poses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/matching/vectorized_features/test_get_rotated_poses.py b/tests/matching/vectorized_features/test_get_rotated_poses.py index 522b6192..72fef7f2 100644 --- a/tests/matching/vectorized_features/test_get_rotated_poses.py +++ b/tests/matching/vectorized_features/test_get_rotated_poses.py @@ -115,8 +115,8 @@ def test_get_rotated_poses_empty_detections(self): # Should not call rotate_pose assert mock_rotate.call_count == 0 - # Should return empty array matching poses shape - assert rotated_poses.shape == (0,) + # Should return properly shaped empty array + assert rotated_poses.shape == (0, 12, 2) assert np.array_equal(rotated_poses, features.poses) def test_get_rotated_poses_uses_detection_rotate_pose(self, detection_factory): From f4ed82d7b3a13ea96b2b1ff272b87bb04b2fa935 Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Wed, 15 Oct 2025 14:16:50 -0400 Subject: [PATCH 5/7] Add tests for one empty and one not --- .../test_compute_vectorized_pose_distances.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py index 9bfca1a8..16095f80 100644 --- a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py +++ b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py @@ -174,10 +174,16 @@ def test_pose_distances_different_sizes(self, features_factory): assert distances.shape == (3, 2) assert not np.any(np.isnan(distances)) # All should be valid - def test_pose_distances_empty_features(self): + @pytest.mark.parametrize("n_features1, n_features2", [ + (0,0), + (0, 1), + (1, 0) + ]) + def test_pose_distances_empty_features(self, n_features1, n_features2, features_factory): """Test pose distance computation with empty features.""" - features1 = VectorizedDetectionFeatures([]) - features2 = VectorizedDetectionFeatures([]) + example_pose_config = [{"has_pose": True, "center": (0, 0)}] + features1 = features_factory(n_detections=n_features1, pose_configs=example_pose_config) + features2 = features_factory(n_detections=n_features2, pose_configs=example_pose_config) # Should handle empty features gracefully distances = compute_vectorized_pose_distances(features1, features2) From d49393b01a68b76d52ea7a4d00669b8cea8180bf Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Wed, 15 Oct 2025 14:59:21 -0400 Subject: [PATCH 6/7] Fix autoformatting --- .../test_compute_vectorized_pose_distances.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py index 16095f80..311e1f30 100644 --- a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py +++ b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py @@ -174,16 +174,18 @@ def test_pose_distances_different_sizes(self, features_factory): assert distances.shape == (3, 2) assert not np.any(np.isnan(distances)) # All should be valid - @pytest.mark.parametrize("n_features1, n_features2", [ - (0,0), - (0, 1), - (1, 0) - ]) - def test_pose_distances_empty_features(self, n_features1, n_features2, features_factory): + @pytest.mark.parametrize("n_features1, n_features2", [(0, 0), (0, 1), (1, 0)]) + def test_pose_distances_empty_features( + self, n_features1, n_features2, features_factory + ): """Test pose distance computation with empty features.""" example_pose_config = [{"has_pose": True, "center": (0, 0)}] - features1 = features_factory(n_detections=n_features1, pose_configs=example_pose_config) - features2 = features_factory(n_detections=n_features2, pose_configs=example_pose_config) + features1 = features_factory( + n_detections=n_features1, pose_configs=example_pose_config + ) + features2 = features_factory( + n_detections=n_features2, pose_configs=example_pose_config + ) # Should handle empty features gracefully distances = compute_vectorized_pose_distances(features1, features2) From 1f8d00451ca3e17cab324bc8f85a8b1bf7b3261e Mon Sep 17 00:00:00 2001 From: Alexander Berger Date: Wed, 15 Oct 2025 15:03:55 -0400 Subject: [PATCH 7/7] Fix shap assert --- .../test_compute_vectorized_pose_distances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py index 311e1f30..370a2272 100644 --- a/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py +++ b/tests/matching/vectorized_features/test_compute_vectorized_pose_distances.py @@ -191,7 +191,7 @@ def test_pose_distances_empty_features( distances = compute_vectorized_pose_distances(features1, features2) # Should return empty distance matrix with correct shape - assert distances.shape == (0, 0) + assert distances.shape == (n_features1, n_features2) assert distances.dtype == np.float64 def test_pose_distances_single_detection(self, features_factory):