Files
FastDeploy/tests/multimodal/test_multimodal_utils.py
essos 191a597d9f [CI]【Hackathon 9th Sprint No.56】NO.56 功能模块 fastdeploy/multimodal/utils.py 单测补充 (#4954)
* update test utils

* update test utils code

* update test file name
2025-11-14 10:37:27 +08:00

281 lines
11 KiB
Python

# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import unittest
from unittest.mock import Mock, patch
from PIL import Image, ImageDraw
# Determine import method based on environment
# Use environment variable FD_TEST_MODE=standalone for local testing
TEST_MODE = os.environ.get("FD_TEST_MODE", "normal")
if TEST_MODE == "standalone":
# Local testing mode - use dynamic import
# Mock the logger to avoid import issues
mock_logger = Mock()
# Create a mock module structure
class MockUtils:
data_processor_logger = mock_logger
sys.modules["fastdeploy"] = Mock()
sys.modules["fastdeploy.utils"] = MockUtils()
sys.modules["fastdeploy.multimodal"] = Mock()
# Import the utils module directly
import importlib.util
spec = importlib.util.spec_from_file_location(
"multimodal_utils", os.path.join(os.path.dirname(__file__), "../../fastdeploy/multimodal/utils.py")
)
multimodal_utils = importlib.util.module_from_spec(spec)
multimodal_utils.data_processor_logger = mock_logger
spec.loader.exec_module(multimodal_utils)
# Extract the function we want to test
process_transparency = multimodal_utils.process_transparency
else:
# Normal mode - direct import (for CI/CD and production)
try:
from fastdeploy.multimodal.utils import process_transparency
# If we can import directly, we don't need mocking
mock_logger = None
except ImportError:
# Fallback to standalone mode if direct import fails
print("Warning: Direct import failed, falling back to standalone mode")
TEST_MODE = "standalone"
# Re-run the standalone setup
mock_logger = Mock()
class MockUtils:
data_processor_logger = mock_logger
sys.modules["fastdeploy"] = Mock()
sys.modules["fastdeploy.utils"] = MockUtils()
sys.modules["fastdeploy.multimodal"] = Mock()
import importlib.util
spec = importlib.util.spec_from_file_location(
"multimodal_utils", os.path.join(os.path.dirname(__file__), "../../fastdeploy/multimodal/utils.py")
)
multimodal_utils = importlib.util.module_from_spec(spec)
multimodal_utils.data_processor_logger = mock_logger
spec.loader.exec_module(multimodal_utils)
process_transparency = multimodal_utils.process_transparency
class TestProcessTransparency(unittest.TestCase):
"""Test cases for multimodal utils functions."""
def setUp(self):
"""Set up test fixtures with various image types."""
# Create a 100x100 RGB image (no transparency)
self.rgb_image = Image.new("RGB", (100, 100), color="red")
# Create a 100x100 RGBA image with full opacity
self.rgba_opaque = Image.new("RGBA", (100, 100), color=(255, 0, 0, 255))
# Create a 100x100 RGBA image with transparency
self.rgba_transparent = Image.new("RGBA", (100, 100), color=(255, 0, 0, 128))
# Create a 100x100 RGBA image with some fully transparent pixels
self.rgba_partial_transparent = Image.new("RGBA", (100, 100), color=(255, 0, 0, 255))
draw = ImageDraw.Draw(self.rgba_partial_transparent)
draw.rectangle([10, 10, 50, 50], fill=(0, 255, 0, 0)) # Fully transparent rectangle
# Create LA image with transparency
self.la_transparent = Image.new("LA", (100, 100), color=(128, 128))
# Create P mode image with transparency
self.p_transparent = Image.new("P", (100, 100))
self.p_transparent.info["transparency"] = 0
# Create P mode image without transparency
self.p_opaque = Image.new("P", (100, 100))
def test_process_transparency_with_opaque_rgb(self):
"""Test processing RGB image without transparency."""
result = process_transparency(self.rgb_image)
# Should return same image (no conversion needed)
self.assertEqual(result.mode, "RGB")
self.assertEqual(result.size, (100, 100))
def test_process_transparency_with_opaque_rgba(self):
"""Test processing RGBA image with full opacity."""
result = process_transparency(self.rgba_opaque)
# Should return same image (no conversion needed)
self.assertEqual(result.mode, "RGBA")
self.assertEqual(result.size, (100, 100))
def test_process_transparency_with_transparent_rgba(self):
"""Test processing RGBA image with transparency."""
result = process_transparency(self.rgba_transparent)
# Should convert to RGB with white background
self.assertEqual(result.mode, "RGB")
self.assertEqual(result.size, (100, 100))
def test_process_transparency_with_partial_transparent_rgba(self):
"""Test processing RGBA image with some transparent pixels."""
result = process_transparency(self.rgba_partial_transparent)
# Should convert to RGB with white background
self.assertEqual(result.mode, "RGB")
self.assertEqual(result.size, (100, 100))
def test_process_transparency_with_transparent_la(self):
"""Test processing LA image with transparency."""
result = process_transparency(self.la_transparent)
# Should convert to RGB with white background
self.assertEqual(result.mode, "RGB")
self.assertEqual(result.size, (100, 100))
def test_process_transparency_with_palette_transparency(self):
"""Test processing P mode image with transparency info."""
result = process_transparency(self.p_transparent)
# P mode with transparency info should be detected as transparent
# but conversion might fail due to "bad transparency mask" error
# In case of error, the function falls back to the original image
self.assertEqual(result.size, (100, 100))
# The mode could be P (if error occurred) or RGB (if conversion succeeded)
def test_process_transparency_with_opaque_palette(self):
"""Test processing P mode image without transparency."""
result = process_transparency(self.p_opaque)
# P mode without transparency should remain P mode (no transparency detected)
# But will go through exif_transpose which might change mode
self.assertEqual(result.size, (100, 100))
# The exact mode depends on exif_transpose behavior
def test_process_transparency_error_handling(self):
"""Test error handling in transparency processing."""
# Create a mock image that will raise an exception
mock_image = Mock()
mock_image.mode = "RGBA"
mock_image.convert.side_effect = Exception("Test error")
# Should not raise exception, should return result of exif_transpose
with patch("PIL.ImageOps.exif_transpose") as mock_exif:
mock_exif.return_value = self.rgb_image
result = process_transparency(mock_image)
# Should return the result from exif_transpose
self.assertEqual(result, self.rgb_image)
def test_convert_transparent_paste_white_background(self):
"""Test that transparent paste creates white background."""
# Create a simple transparent image
transparent_img = Image.new("RGBA", (10, 10), (255, 0, 0, 0)) # Fully transparent red
result = process_transparency(transparent_img)
# Should be RGB mode with white background
self.assertEqual(result.mode, "RGB")
# Check that the converted image has white background
# (since original was fully transparent, should be white)
pixels = list(result.getdata())
# All pixels should be white (255, 255, 255)
for pixel in pixels:
self.assertEqual(pixel, (255, 255, 255))
def test_convert_transparent_paste_partial_transparency(self):
"""Test transparent paste with partially transparent image."""
# Create image with partial transparency
img = Image.new("RGBA", (10, 10), (255, 0, 0, 128)) # 50% transparent red
result = process_transparency(img)
# Should be RGB mode
self.assertEqual(result.mode, "RGB")
# Should have been pasted onto white background
pixels = list(result.getdata())
# All pixels should be the same (blended with white background)
for pixel in pixels:
# With 50% transparency, red (255,0,0) blended with white (255,255,255)
# should give a pinkish color
self.assertGreater(pixel[0], 128) # Red component should be significant
self.assertGreaterEqual(pixel[1], 127) # Green component from white background
self.assertGreaterEqual(pixel[2], 127) # Blue component from white background
def test_edge_case_min_alpha_value(self):
"""Test edge case with minimum alpha value."""
# Create image with alpha at minimum (0)
img = Image.new("RGBA", (1, 1), (255, 0, 0, 0))
result = process_transparency(img)
# Should be converted to RGB
self.assertEqual(result.mode, "RGB")
def test_edge_case_max_alpha_value(self):
"""Test edge case with maximum alpha value."""
# Create image with alpha at maximum (255)
img = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
result = process_transparency(img)
# Should remain RGBA (no transparency detected)
self.assertEqual(result.mode, "RGBA")
def test_edge_case_empty_image(self):
"""Test edge case with empty (0x0) image."""
img = Image.new("RGBA", (0, 0))
result = process_transparency(img)
# Should handle empty image gracefully
self.assertEqual(result.size, (0, 0))
def test_edge_case_single_pixel_transparent(self):
"""Test edge case with single pixel transparent image."""
img = Image.new("RGBA", (1, 1), (255, 0, 0, 0))
result = process_transparency(img)
# Should convert to RGB
self.assertEqual(result.mode, "RGB")
self.assertEqual(result.size, (1, 1))
def test_edge_case_single_pixel_opaque(self):
"""Test edge case with single pixel opaque image."""
img = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
result = process_transparency(img)
# Should remain RGBA
self.assertEqual(result.mode, "RGBA")
self.assertEqual(result.size, (1, 1))
if __name__ == "__main__":
# Print current test mode for clarity
print(f"Running tests in {TEST_MODE} mode")
if TEST_MODE == "standalone":
print("To run in normal mode, ensure fastdeploy is properly installed")
print("Or set FD_TEST_MODE=normal environment variable")
unittest.main(verbosity=2)