diff --git a/src/cmap/_colormap.py b/src/cmap/_colormap.py index 97a0272e4..b15d1ad8d 100644 --- a/src/cmap/_colormap.py +++ b/src/cmap/_colormap.py @@ -5,6 +5,7 @@ import base64 import warnings from collections.abc import Iterable, Sequence +from copy import copy from functools import partial from numbers import Number from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Union, cast, overload @@ -306,7 +307,9 @@ def __init__( name = value if isinstance(value, str) else "custom colormap" self.interpolation = _norm_interp(interpolation) - stops._interpolation = self.interpolation + if stops._interpolation != self.interpolation: + # stops may be owned by the caller: don't write the mode into it + stops = stops._with_interpolation(self.interpolation) self.color_stops = stops self.name = name self.identifier = _make_identifier(identifier or name) @@ -456,6 +459,7 @@ def with_extremes( self.color_stops, name=self.name, category=self.category, + interpolation=self.interpolation, bad=bad, under=under, over=over, @@ -1175,6 +1179,16 @@ def _reverser(func: LutCallable, x: NDArray) -> NDArray: def _is_reversed_lut_func(self, f: Callable) -> TypeGuard[partial]: return isinstance(f, partial) and f.func is self._reverser + def _with_interpolation(self, interpolation: Interpolation) -> ColorStops: + """Return a copy of self, with a different interpolation mode. + + A shallow copy: rebuilding from `_lut_func` would call the user's callable + a second time, which is neither cheap nor guaranteed to be repeatable. + """ + new = copy(self) + new._interpolation = interpolation + return new + def reversed(self) -> ColorStops: """Return a new ColorStops object with reversed colors.""" if (lut_func := self._lut_func) is not None: diff --git a/tests/test_colormap.py b/tests/test_colormap.py index c17ad6f7a..e403c76bf 100644 --- a/tests/test_colormap.py +++ b/tests/test_colormap.py @@ -99,6 +99,24 @@ def test_colorstops_reversed_does_not_mutate_source() -> None: npt.assert_array_equal(np.asarray(stops), before) +def test_construction_does_not_change_source_interpolation() -> None: + stops = Colormap(["red", "blue"], interpolation="nearest").color_stops + before = stops.to_lut(4).copy() + + Colormap(stops, interpolation="linear") + + npt.assert_array_equal(stops.to_lut(4), before) + + +def test_with_extremes_preserves_interpolation() -> None: + cmap = Colormap(["red", "blue"], interpolation="nearest") + + new = cmap.with_extremes(bad="red") + + assert new.interpolation == "nearest" + npt.assert_array_equal(new.lut(4), cmap.lut(4)) + + def test_colormap_copy() -> None: """Test Colormap copy.""" import pickle