From ff1d46153d79b5b9257fac66bf662be6c115f906 Mon Sep 17 00:00:00 2001 From: Matthias Schabel Date: Mon, 10 Aug 2026 14:05:30 -0700 Subject: [PATCH] fix: byteswap non-native input instead of reinterpreting it Data in non-native byte order maps to the wrong colors. On a little-endian host, a big-endian float array has its bytes relabelled rather than reordered, so -0.5, 1.5 and NaN all decode as tiny denormals and land on the first ramp color. under, over and bad are unreachable for such input. The numpy 2 migration in #60 replaced xa.byteswap().newbyteorder(), which numpy 2 removed, with a plain .view() of the swapped dtype. byteswap().view(dtype.newbyteorder()) is the numpy 2 spelling of the original, and what matplotlib uses today. The existing coverage maps an array of zeros and asserts only its shape, both of which are invariant under the bug. Co-Authored-By: Claude Opus 5 (1M context) Reviewed-By: Codex (gpt-5.6-sol, reasoning effort xhigh) --- src/cmap/_colormap.py | 3 +-- tests/test_colormap.py | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cmap/_colormap.py b/src/cmap/_colormap.py index 97a0272e4..ed75c4b3f 100644 --- a/src/cmap/_colormap.py +++ b/src/cmap/_colormap.py @@ -415,8 +415,7 @@ def __call__( xa = np.array(x, copy=True) if not xa.dtype.isnative: # Native byteorder is faster. - native: Literal[">", "<"] = ">" if xa.dtype.byteorder in ("<", "=") else "<" - xa = xa.view(xa.dtype.newbyteorder(native)) + xa = xa.byteswap().view(xa.dtype.newbyteorder()) if xa.dtype.kind == "f": xa *= N # xa == 1 (== N after multiplication) is not out of range. diff --git a/tests/test_colormap.py b/tests/test_colormap.py index c17ad6f7a..fa75b4885 100644 --- a/tests/test_colormap.py +++ b/tests/test_colormap.py @@ -144,6 +144,14 @@ def test_colormap_apply() -> None: assert cmap1(swapped).shape == (10, 10, 4) +def test_non_native_byte_order_maps_the_same_colors() -> None: + cmap = Colormap(["red", "blue"], under="green", over="yellow", bad="black") + native = np.array([-0.5, 0.0, 0.5, 1.0, 1.5, np.nan]) + non_native = native.astype(native.dtype.newbyteorder()) + + npt.assert_array_equal(cmap(non_native), cmap(native)) + + def test_colormap_masked_array_with_unmasked_nan() -> None: cmap = Colormap("viridis", bad="red") mask = [True, False, False, False]