From f82d529e4977e3f1b25a1fb1878bfdce8c8fec91 Mon Sep 17 00:00:00 2001 From: Michael Simpson Date: Wed, 25 Feb 2026 14:40:46 -0500 Subject: [PATCH 1/2] Return `None` When There Isn't Meaningful Letterboxing The current implementation will return the original image if there isn't any meaningful letterboxing around the image. This PR changes that so that the caller knows if an image was modified or not. --- perception/hashers/tools.py | 17 +++-------- perception/local_descriptor_deduplication.py | 9 +++--- tests/test_tools.py | 30 +++++++++----------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/perception/hashers/tools.py b/perception/hashers/tools.py index 0414859..ff76d64 100644 --- a/perception/hashers/tools.py +++ b/perception/hashers/tools.py @@ -1064,10 +1064,7 @@ def unletterbox( if len(set(corners)) == 4: LOGGER.debug("No common corner color detected, skipping content detection.") - return ( - (0, w), - (0, h), - ) # Return full image bounds instead of None to maintain backwards compatibility + return None # Use the most common corner value as the background intensity. counts = Counter(corners) bg_gray = counts.most_common(1)[0][0] @@ -1079,10 +1076,7 @@ def unletterbox( # If every pixel is classified as content, there is no border to remove. if content_mask.all(): LOGGER.debug("All pixels differ from background; no letterbox detected.") - return ( - (0, w), - (0, h), - ) # Return full image bounds instead of None to maintain backwards compatibility + return None # Find the content bounding box by projecting the mask onto rows and # columns. cv2.reduce is used instead of np.sum for performance. @@ -1116,10 +1110,7 @@ def unletterbox( "Crop would not reduce either dimension by %.0f%%; skipping.", min_reduction * 100, ) - return ( - (0, w), - (0, h), - ) # Return full image bounds instead of None to maintain backwards compatibility + return None # Reject if the remaining content region is too small to be useful. if width < min_side_length or height < min_side_length: LOGGER.debug( @@ -1156,7 +1147,7 @@ def unletterbox_crop( min_reduction: The minimum fraction (0–1) of the original width or height that must be removed for the crop to be worthwhile. If the crop removes less than this from both dimensions, - the original image is returned. Defaults to 0.02 (2%). + ``None`` is returned. Defaults to 0.02 (2%). Returns: The cropped image or None if the image is mostly blank space. """ diff --git a/perception/local_descriptor_deduplication.py b/perception/local_descriptor_deduplication.py index fbacc2e..639d14e 100644 --- a/perception/local_descriptor_deduplication.py +++ b/perception/local_descriptor_deduplication.py @@ -157,7 +157,7 @@ def validate_match( descriptorB["descriptors"].astype("float32"), 2 ) good_A2B, good_B2A = map( - lambda distances: (distances[:, 0] < distances[:, 1] * self.ratio), + lambda distances: distances[:, 0] < distances[:, 1] * self.ratio, [distances_A2B, distances_B2A], ) match = min( @@ -289,10 +289,9 @@ def load_and_preprocess(filepath, max_size=DEFAULT_MAX_SIZE, grayscale=True): LOGGER.warning("Failed to load image %s", filepath) return None res = pht.unletterbox(image) - if res is None: - return None - (x1, x2), (y1, y2) = res - image = np.ascontiguousarray(image[y1:y2, x1:x2]) + if res is not None: + (x1, x2), (y1, y2) = res + image = np.ascontiguousarray(image[y1:y2, x1:x2]) if grayscale: image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) diff --git a/tests/test_tools.py b/tests/test_tools.py index 7d73372..4b97d00 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -106,7 +106,14 @@ def test_compute_euclidean_pairwise_duplicates(): # Use grouped files. counts = np.array([3, 3, 2, 2]) expected = np.array( - [[2 / 3, 2 / 3], [0, 0], [0, 0], [1 / 3, 1 / 2], [0, 0], [0, 0]] + [ + [2 / 3, 2 / 3], + [0, 0], + [0, 0], + [1 / 3, 1 / 2], + [0, 0], + [0, 0], + ] ) actual = tools.extensions.compute_euclidean_pairwise_duplicates( X=X.astype("int32"), @@ -206,12 +213,7 @@ def test_unletterbox_color(): padded[50 : 50 + image.shape[0], 25 : 25 + image.shape[1]] = image # Should not unletterbox since not black. results = hashers.tools.unletterbox(padded, only_remove_black=True) - assert results is not None - (x1, x2), (y1, y2) = results - assert y1 == 0 - assert y2 == padded.shape[0] - assert x1 == 0 - assert x2 == padded.shape[1] + assert results is None # Should unletterbox color: results = hashers.tools.unletterbox(padded, only_remove_black=False) @@ -252,12 +254,7 @@ def test_unletterbox_noblackbars(): image = hashers.tools.read(testing.DEFAULT_TEST_IMAGES[0]) results = hashers.tools.unletterbox(image) - assert results is not None - (x1, x2), (y1, y2) = results - assert x1 == 0 - assert y1 == 0 - assert x2 == image.shape[1] - assert y2 == image.shape[0] + assert results is None def test_ffmpeg_video(): @@ -277,9 +274,10 @@ def test_ffmpeg_video(): ): diff = np.abs(frame1.astype("int32") - frame2.astype("int32")).flatten() assert index1 == index2, f"Index mismatch for {filename}" - np.testing.assert_allclose( - timestamp1, timestamp2 - ), f"Timestamp mismatch for {filename}" + ( + np.testing.assert_allclose(timestamp1, timestamp2), + f"Timestamp mismatch for {filename}", + ) assert np.percentile(diff, 75) < 25, f"Frame mismatch for {filename}" From 9e0ea598c8ca93a189c4a8f91b4e0726488c65e1 Mon Sep 17 00:00:00 2001 From: Michael Simpson Date: Wed, 25 Feb 2026 14:46:13 -0500 Subject: [PATCH 2/2] Update tests/test_tools.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_tools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 4b97d00..cb9681c 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -274,9 +274,10 @@ def test_ffmpeg_video(): ): diff = np.abs(frame1.astype("int32") - frame2.astype("int32")).flatten() assert index1 == index2, f"Index mismatch for {filename}" - ( - np.testing.assert_allclose(timestamp1, timestamp2), - f"Timestamp mismatch for {filename}", + np.testing.assert_allclose( + timestamp1, + timestamp2, + err_msg=f"Timestamp mismatch for {filename}", ) assert np.percentile(diff, 75) < 25, f"Frame mismatch for {filename}"