From 69a8e688eb2d50dee8e43b90cbecf197c797aabb Mon Sep 17 00:00:00 2001 From: ybai08 <271969568+ybai08@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:20:10 -0500 Subject: [PATCH] Keep integer dtype in mercator_transform mercator_transform always returned a float64 array, so a uint8 image passed to ImageOverlay(mercator_project=True) reached write_png as float data and had every channel rescaled to its maximum. A uniform (100, 50, 200, 255) image came out white. Round and cast the result back to the input's integer dtype. --- folium/utilities.py | 5 +++++ tests/test_raster_layers.py | 11 +++++++++++ tests/test_utilities.py | 10 ++++++++++ 3 files changed, 26 insertions(+) diff --git a/folium/utilities.py b/folium/utilities.py index e9807ad91d..7bfdb9bdfb 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -273,6 +273,11 @@ def mercator(x): for j in range(nblayers): out[:, i, j] = np.interp(latslats, mercator(lats), array[:, i, j]) + # Keep integer images (e.g. uint8 RGBA) in their original dtype, otherwise + # write_png treats them as float data and rescales each channel. + if np.issubdtype(array.dtype, np.integer): + out = np.round(out).astype(array.dtype) + # Eventually flip the image. if origin == "upper": out = out[::-1, :, :] diff --git a/tests/test_raster_layers.py b/tests/test_raster_layers.py index 918568b3cb..bddc6c446a 100644 --- a/tests/test_raster_layers.py +++ b/tests/test_raster_layers.py @@ -4,6 +4,7 @@ """ +import numpy as np import pytest import xyzservices @@ -113,6 +114,16 @@ def test_image_overlay(): assert bounds == [[0, -180], [90, 180]], bounds +def test_image_overlay_mercator_project_keeps_uint8_colors(): + data = np.full((4, 3, 4), (100, 50, 200, 255), dtype="uint8") + bounds = [[40, 0], [50, 10]] + + plain = folium.raster_layers.ImageOverlay(data, bounds) + projected = folium.raster_layers.ImageOverlay(data, bounds, mercator_project=True) + + assert projected.url == plain.url + + @pytest.mark.parametrize( "tiles", ["CartoDB DarkMatter", xyzservices.providers.CartoDB.DarkMatter] ) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 26d346ca39..d7c81ce1b6 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -12,6 +12,7 @@ get_obj_in_upper_tree, if_pandas_df_convert_to_numpy, javascript_identifier_path_to_array_notation, + mercator_transform, normalize_bounds_type, parse_font_size, parse_options, @@ -171,6 +172,15 @@ def test_normalize_bounds_type(bounds, expected): assert normalize_bounds_type(bounds) == expected +def test_mercator_transform_keeps_integer_dtype(): + # A uint8 image must stay uint8, otherwise write_png treats the result as + # float data and stretches every channel to its maximum. + data = np.full((4, 3, 4), (100, 50, 200, 255), dtype="uint8") + out = mercator_transform(data, (40, 50)) + assert out.dtype == np.uint8 + np.testing.assert_array_equal(out, data) + + def test_camelize(): assert camelize("variable_name") == "variableName" assert camelize("variableName") == "variableName"