From 2b65f890de00288acc58e94683f6a7a8fb21ae2b Mon Sep 17 00:00:00 2001 From: Utsab Dahal Date: Sat, 4 Oct 2025 18:00:36 +0545 Subject: [PATCH 1/4] =?UTF-8?q?Simplify=20save=5Fimg:=20remove=20=5Fformat?= =?UTF-8?q?,=20normalize=20jpg=E2=86=92jpeg,=20add=20RGBA=E2=86=92RGB=20ha?= =?UTF-8?q?ndling=20and=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- integration_tests/test_save_img.py | 27 +++++++++++++++++++++++++++ keras/src/utils/image_utils.py | 7 +++++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 integration_tests/test_save_img.py diff --git a/integration_tests/test_save_img.py b/integration_tests/test_save_img.py new file mode 100644 index 000000000000..baec2712bfc2 --- /dev/null +++ b/integration_tests/test_save_img.py @@ -0,0 +1,27 @@ +import os + +import numpy as np +import pytest + +from keras.utils import img_to_array +from keras.utils import load_img +from keras.utils import save_img + + +@pytest.mark.parametrize( + "shape, name", + [ + ((50, 50, 3), "rgb.jpg"), + ((50, 50, 4), "rgba.jpg"), + ], +) +def test_save_jpg(tmp_path, shape, name): + img = np.random.randint(0, 256, size=shape, dtype=np.uint8) + path = tmp_path / name + save_img(path, img, file_format="jpg") + assert os.path.exists(path) + + # Check that the image was saved correctly and converted to RGB if needed. + loaded_img = load_img(path) + loaded_array = img_to_array(loaded_img) + assert loaded_array.shape == (50, 50, 3) \ No newline at end of file diff --git a/keras/src/utils/image_utils.py b/keras/src/utils/image_utils.py index ca8289c9f9b7..a8781a0f46ae 100644 --- a/keras/src/utils/image_utils.py +++ b/keras/src/utils/image_utils.py @@ -175,10 +175,13 @@ def save_img(path, x, data_format=None, file_format=None, scale=True, **kwargs): **kwargs: Additional keyword arguments passed to `PIL.Image.save()`. """ data_format = backend.standardize_data_format(data_format) + # Normalize jpg → jpeg + if file_format is not None and file_format.lower() == "jpg": + file_format = "jpeg" img = array_to_img(x, data_format=data_format, scale=scale) - if img.mode == "RGBA" and (file_format == "jpg" or file_format == "jpeg"): + if img.mode == "RGBA" and file_format == "jpeg": warnings.warn( - "The JPG format does not support RGBA images, converting to RGB." + "The JPEG format does not support RGBA images, converting to RGB." ) img = img.convert("RGB") img.save(path, format=file_format, **kwargs) From 49fc1b7852e91a0e1fd93c2490aacea16365c6ea Mon Sep 17 00:00:00 2001 From: Utsab Dahal Date: Fri, 17 Oct 2025 17:47:21 +0545 Subject: [PATCH 2/4] Fix bug in preprocess_input for 3D channels_first inputs (#21749) --- keras/src/applications/imagenet_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/keras/src/applications/imagenet_utils.py b/keras/src/applications/imagenet_utils.py index f88c0af64d88..26829b1e2855 100644 --- a/keras/src/applications/imagenet_utils.py +++ b/keras/src/applications/imagenet_utils.py @@ -278,7 +278,12 @@ def _preprocess_tensor_input(x, data_format, mode): # Zero-center by mean pixel if data_format == "channels_first": - mean_tensor = ops.reshape(mean_tensor, (1, 3) + (1,) * (ndim - 2)) + if ndim == 3: + mean_tensor = ops.reshape(mean_tensor, (3, 1, 1)) + elif ndim ==4: + mean_tensor = ops.reshape(mean_tensor, (1, 3, 1, 1)) + else: + raise ValueError(f"Unsupported shape for channels_first: {x.shape}") else: mean_tensor = ops.reshape(mean_tensor, (1,) * (ndim - 1) + (3,)) x += mean_tensor From 544e7f14952dde99a72ffdacc85cb0c5ded1d109 Mon Sep 17 00:00:00 2001 From: Utsab Dahal Date: Fri, 17 Oct 2025 17:53:10 +0545 Subject: [PATCH 3/4] Fix: handle 3D input for channels_first in preprocess_input() --- keras/src/applications/imagenet_utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/keras/src/applications/imagenet_utils.py b/keras/src/applications/imagenet_utils.py index 26829b1e2855..1de2f9942100 100644 --- a/keras/src/applications/imagenet_utils.py +++ b/keras/src/applications/imagenet_utils.py @@ -280,7 +280,7 @@ def _preprocess_tensor_input(x, data_format, mode): if data_format == "channels_first": if ndim == 3: mean_tensor = ops.reshape(mean_tensor, (3, 1, 1)) - elif ndim ==4: + elif ndim == 4: mean_tensor = ops.reshape(mean_tensor, (1, 3, 1, 1)) else: raise ValueError(f"Unsupported shape for channels_first: {x.shape}") @@ -290,7 +290,14 @@ def _preprocess_tensor_input(x, data_format, mode): if std is not None: std_tensor = ops.convert_to_tensor(np.array(std), dtype=x.dtype) if data_format == "channels_first": - std_tensor = ops.reshape(std_tensor, (-1, 1, 1)) + if ndim == 3: + std_tensor = ops.reshape(std_tensor, (3, 1, 1)) + elif ndim == 4: + std_tensor = ops.reshape(std_tensor, (1, 3, 1, 1)) + else: + raise ValueError(f"Unsupported shape for channels_first: {x.shape}") + else: + std_tensor = ops.reshape(std_tensor, (1,) * (ndim - 1) + (3,)) x /= std_tensor return x From a935e1c200178ad54de732237a750ce95fae12f1 Mon Sep 17 00:00:00 2001 From: Utsab Dahal Date: Fri, 17 Oct 2025 17:57:31 +0545 Subject: [PATCH 4/4] Fix: handle 3D input for channels_first in preprocess_input() --- .gitignore | 1 + integration_tests/test_save_img.py | 2 +- keras/src/applications/imagenet_utils.py | 4 +++- keras/src/utils/image_utils.py | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index afd700b49952..416f213f2c82 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ **/.vscode test/** **/.vscode-smoke/** **/.venv*/ +venv bin/** build/** obj/** diff --git a/integration_tests/test_save_img.py b/integration_tests/test_save_img.py index baec2712bfc2..6ec7951564cb 100644 --- a/integration_tests/test_save_img.py +++ b/integration_tests/test_save_img.py @@ -24,4 +24,4 @@ def test_save_jpg(tmp_path, shape, name): # Check that the image was saved correctly and converted to RGB if needed. loaded_img = load_img(path) loaded_array = img_to_array(loaded_img) - assert loaded_array.shape == (50, 50, 3) \ No newline at end of file + assert loaded_array.shape == (50, 50, 3) diff --git a/keras/src/applications/imagenet_utils.py b/keras/src/applications/imagenet_utils.py index 1de2f9942100..7af0c659f62f 100644 --- a/keras/src/applications/imagenet_utils.py +++ b/keras/src/applications/imagenet_utils.py @@ -295,7 +295,9 @@ def _preprocess_tensor_input(x, data_format, mode): elif ndim == 4: std_tensor = ops.reshape(std_tensor, (1, 3, 1, 1)) else: - raise ValueError(f"Unsupported shape for channels_first: {x.shape}") + raise ValueError( + f"Unsupported shape for channels_first: {x.shape}" + ) else: std_tensor = ops.reshape(std_tensor, (1,) * (ndim - 1) + (3,)) x /= std_tensor diff --git a/keras/src/utils/image_utils.py b/keras/src/utils/image_utils.py index a8781a0f46ae..abf5c413fde0 100644 --- a/keras/src/utils/image_utils.py +++ b/keras/src/utils/image_utils.py @@ -179,7 +179,7 @@ def save_img(path, x, data_format=None, file_format=None, scale=True, **kwargs): if file_format is not None and file_format.lower() == "jpg": file_format = "jpeg" img = array_to_img(x, data_format=data_format, scale=scale) - if img.mode == "RGBA" and file_format == "jpeg": + if img.mode == "RGBA" and file_format == "jpeg": warnings.warn( "The JPEG format does not support RGBA images, converting to RGB." )