Skip to content

Add comprehensive unit tests for image processing functions#22513

Open
xingzihai wants to merge 1 commit intokeras-team:masterfrom
xingzihai:add-comprehensive-image-ops-tests-v2
Open

Add comprehensive unit tests for image processing functions#22513
xingzihai wants to merge 1 commit intokeras-team:masterfrom
xingzihai:add-comprehensive-image-ops-tests-v2

Conversation

@xingzihai
Copy link
Copy Markdown

Summary

This PR adds extensive test coverage for keras.ops.image functions, focusing on edge cases, invalid inputs, round-trip conversions, and data type handling that were not covered by existing tests.

Changes

Added 14 new test classes with 894 lines of test code:

New Test Classes

  1. RGBToGrayscaleEdgeCasesTest (10 tests)

    • Single pixel conversion
    • All black/white images
    • Pure RGB colors
    • Relative brightness verification (G > R > B)
    • uint8 dtype support
    • Batch consistency
  2. RGBHSVRoundTripTest (9 tests)

    • RGB → HSV → RGB round-trip
    • HSV → RGB → HSV round-trip
    • Pure colors (red, green, blue)
    • Black/white colors
    • Grayscale colors (saturation=0 verification)
    • Batch consistency
  3. ResizeEdgeCasesTest (10 tests)

    • Single pixel resize
    • Resize to single pixel
    • Identity resize
    • Large up/downsampling
    • Non-square resize
    • Value range preservation
    • Aspect ratio options
    • Invalid size error handling
  4. AffineTransformEdgeCasesTest (5 tests)

    • Identity transform
    • Translation transform
    • Small images
    • Single pixel
    • Batched transforms
  5. PadImagesEdgeCasesTest (8 tests)

    • Zero padding
    • Asymmetric padding
    • Single pixel
    • Large padding
    • Target dimensions
    • Error handling for negative padding
  6. CropImagesEdgeCasesTest (7 tests)

    • Zero cropping
    • Asymmetric cropping
    • Target dimensions
    • Minimum crop (to 1x1)
    • Error handling for invalid inputs
  7. GaussianBlurEdgeCasesTest (6 tests)

    • Small/large sigma
    • Asymmetric kernel
    • Single pixel
    • Mean preservation
    • Uniform image handling
  8. ElasticTransformEdgeCasesTest (5 tests)

    • Zero/small alpha
    • Reproducibility with seed
    • Different seeds produce different results
    • Small images
  9. PerspectiveTransformEdgeCasesTest (3 tests)

    • Identity transform
    • Small perspective shift
    • Single pixel
  10. ExtractPatchesEdgeCasesTest (4 tests)

    • Small images
    • Full image coverage
    • Overlapping patches
    • Single pixel
  11. MapCoordinatesEdgeCasesTest (3 tests)

    • Identity mapping
    • Single pixel
    • Coordinate shift
  12. ScaleAndTranslateEdgeCasesTest (4 tests)

    • Identity operation
    • Small/large scale
    • Translation
  13. ImageOpsInvalidInputTest (7 tests)

    • Wrong dtypes (int for HSV functions)
    • Wrong size types
    • Conflicting arguments
    • Coordinate rank mismatch
  14. ImageOpsChannelsFirstEdgeCasesTest (7 tests)

    • Channels_first format support for all major functions

Testing

All tests follow the existing test patterns in the repository:

  • Use testing.TestCase base class
  • Proper setUp/tearDown for data format
  • Use parameterized tests where appropriate
  • Clear test names and docstrings

Checklist

  • Tests follow existing patterns in image_test.py
  • All test classes have proper setUp/tearDown
  • Tests cover edge cases, invalid inputs, and different data formats
  • Clear commit message describing changes

This commit adds extensive test coverage for keras.ops.image functions:

- RGBToGrayscaleEdgeCasesTest: Edge cases for rgb_to_grayscale including
  single pixel, all black/white images, pure colors, relative brightness,
  uint8 support, and batch consistency

- RGBHSVRoundTripTest: Round-trip conversion tests between RGB and HSV
  including identity, pure colors (red, green, blue), grayscale colors

- ResizeEdgeCasesTest: Edge cases for resize including single pixel,
  identity resize, large up/down sampling, non-square, aspect ratio
  preservation, and invalid size handling

- AffineTransformEdgeCasesTest: Edge cases for affine_transform including
  identity transform, translation, small images, single pixel, batched
  transforms

- PadImagesEdgeCasesTest: Edge cases for pad_images including zero padding,
  asymmetric padding, single pixel, large padding, target dimensions, and
  error handling for invalid inputs

- CropImagesEdgeCasesTest: Edge cases for crop_images including zero
  cropping, asymmetric cropping, target dimensions, minimum crop, and
  error handling

- GaussianBlurEdgeCasesTest: Edge cases for gaussian_blur including small
  and large sigma, asymmetric kernels, single pixel, mean preservation,
  uniform image handling

- ElasticTransformEdgeCasesTest: Edge cases for elastic_transform including
  zero/small alpha, reproducibility with seed, different seeds, small images

- PerspectiveTransformEdgeCasesTest: Edge cases for perspective_transform
  including identity transform, small shifts, single pixel

- ExtractPatchesEdgeCasesTest: Edge cases for extract_patches including
  small images, full image coverage, overlapping patches, single pixel

- MapCoordinatesEdgeCasesTest: Edge cases for map_coordinates including
  identity mapping, single pixel, coordinate shift

- ScaleAndTranslateEdgeCasesTest: Edge cases for scale_and_translate
  including identity, small/large scale, translation

- ImageOpsInvalidInputTest: Tests for invalid inputs including wrong dtypes,
  wrong channel counts, invalid size types, conflicting arguments

- ImageOpsChannelsFirstEdgeCasesTest: Tests for channels_first data format
  across all major functions
@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 26, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the robustness of keras.ops.image functions by introducing comprehensive unit tests. These new tests cover a wide array of scenarios, including various edge cases, handling of invalid inputs, ensuring correct round-trip conversions between color spaces, and verifying data type consistency, ultimately leading to more reliable image processing operations within Keras.

Highlights

  • Extensive Test Coverage Added: Introduced 14 new test classes with 894 lines of test code to significantly enhance coverage for keras.ops.image functions.
  • Edge Case Handling: New tests specifically target various edge cases for functions like rgb_to_grayscale, resize, affine_transform, pad_images, crop_images, gaussian_blur, elastic_transform, perspective_transform, extract_patches, map_coordinates, and scale_and_translate.
  • Color Space Conversion Validation: Comprehensive round-trip tests for RGB and HSV conversions were added, ensuring accuracy and consistency across different color values including pure colors, black, white, and grayscale.
  • Invalid Input and Data Type Testing: Dedicated tests were implemented to verify error handling for invalid inputs, such as incorrect data types (e.g., integer dtypes for float-expected functions), wrong size types, conflicting arguments, and coordinate rank mismatches.
  • Channels-First Data Format Support: New test cases confirm proper functionality and shape consistency for image operations when using the channels_first data format.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive suite of new test cases for various Keras image manipulation operations, including color conversions (RGB to Grayscale, RGB/HSV round-trip), geometric transformations (resize, affine, perspective, elastic), and utility functions (pad, crop, blur, extract patches, map coordinates, scale/translate). The tests cover edge cases, invalid inputs, and channels_first data format scenarios, significantly enhancing the robustness of the image operations. The review feedback suggests improving the rigor and completeness of several tests by adding more precise assertions for pixel values, content transformation, and output data types, using tighter tolerances for numerical comparisons, and addressing an inconsistency in RGBToGrayscale's input channel validation.

Comment on lines +3698 to +3701
# at the ops level for RGB conversion (backend handles it)
out = kimage.rgb_to_grayscale(x)
self.assertEqual(out.shape, (10, 10, 1))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The RGBToGrayscale operation's compute_output_spec (lines 28-31 in keras/src/ops/image.py) does not validate that the input image has 3 channels, unlike RGBToHSV and HSVToRGB. This inconsistency could lead to unexpected behavior or less informative error messages if a non-3-channel image is passed. It should explicitly check for 3 channels.

def test_crop_images_with_target_dimensions(self):
"""Test cropping using target dimensions."""
x = np.ones((10, 10, 3), dtype="float32")
out = kimage.crop_images(x, 0, 0, target_height=5, target_width=5)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only verifies the output shape. It should also include assertions to confirm that the correct portion of the original image is retained after cropping.

dtype="float32")
end_points = np.array([[1, 1], [0, 10], [10, 0], [10, 10]],
dtype="float32")
out = kimage.perspective_transform(x, start_points, end_points)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only checks the output shape. It should include assertions to verify that the image content has been transformed as expected due to the small perspective shift.

out = kimage.resize(x, (5, 5))
self.assertEqual(out.shape, (5, 5, 3))
# Values should be very close (not exact due to interpolation)
self.assertTrue(np.allclose(x, out, atol=0.1))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The atol=0.1 for an identity resize operation is a relatively high tolerance. For an operation that should ideally return the input unchanged, a smaller tolerance would provide a stronger guarantee of correctness.

x = np.repeat(np.repeat(x, 10, axis=0), 10, axis=1)
out = kimage.resize(x, (5, 5))
# Values should still be in valid uint8 range
self.assertTrue(np.all(out >= 0))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test checks the value range for uint8 but does not assert the output dtype. Given that the _resize function explicitly handles uint8 casting, the output dtype should be uint8.

out = kimage.rgb_to_grayscale(x)
self.assertEqual(out.shape, (1, 1, 1))
# Red channel has weight 0.299 in standard conversion
self.assertTrue(out[0, 0, 0] > 0.28 and out[0, 0, 0] < 0.31)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The assertion self.assertTrue(out[0, 0, 0] > 0.28 and out[0, 0, 0] < 0.31) uses a hardcoded range. It would be more robust to calculate the expected grayscale value based on the standard conversion formula (e.g., 0.299 * R + 0.587 * G + 0.114 * B) and use self.assertAllClose with a small tolerance.

[[1.0, 1.0], [2.0, 2.0]],
[[0.0, 0.0], [0.0, 0.0]]
], dtype="float32")
out = kimage.map_coordinates(x, coords, order=0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only checks the output shape. It should include assertions to verify that the pixel value from the original (0,0) has been correctly shifted to (1,0) in the output.

spatial_dims=(0, 1),
method="linear"
)
self.assertEqual(out.shape, (10, 10, 3))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only checks the output shape. It should include an assertAllClose to verify that the output image is identical to the input for an identity scale and translate operation.

spatial_dims=(0, 1),
method="linear"
)
self.assertEqual(out.shape, (10, 10, 3))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only checks the output shape. It should include assertions to verify that the bright pixel has been correctly translated to the expected location in the output.

"""Test conversion with uint8 dtype."""
x = np.random.randint(0, 256, (10, 10, 3), dtype="uint8")
out = kimage.rgb_to_grayscale(x)
self.assertEqual(out.shape, (10, 10, 1))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only checks the output shape. It should also verify that the output dtype is uint8 and that the values are within the expected [0, 255] range, as the rgb_to_grayscale function is expected to handle uint8 inputs correctly.

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 24.94%. Comparing base (cc7078b) to head (e5b546f).
⚠️ Report is 4 commits behind head on master.

❗ There is a different number of reports uploaded between BASE (cc7078b) and HEAD (e5b546f). Click for more details.

HEAD has 10 uploads less than BASE
Flag BASE (cc7078b) HEAD (e5b546f)
keras 6 1
keras-jax 2 1
keras-numpy 1 0
keras-openvino 1 0
keras-torch 1 0
keras-tensorflow 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #22513       +/-   ##
===========================================
- Coverage   83.20%   24.94%   -58.27%     
===========================================
  Files         596      596               
  Lines       67621    67621               
  Branches    10531    10531               
===========================================
- Hits        56266    16866    -39400     
- Misses       8630    49885    +41255     
+ Partials     2725      870     -1855     
Flag Coverage Δ
keras 24.94% <ø> (-58.09%) ⬇️
keras-jax 24.94% <ø> (-35.06%) ⬇️
keras-numpy ?
keras-openvino ?
keras-tensorflow ?
keras-torch ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

@hertschuh hertschuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also approve the CLA

self.assertEqual(patches.shape, expected_shape)


class RGBToGrayscaleEdgeCasesTest(testing.TestCase):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests significantly overlap the existing ones. Most of them can be removed.

Additionally, these tests should be interspersed with the existing ones so that all tests for the same method are grouped together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants