From 5239520b65802d80a3497f13cc40bb0d6bb196d6 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:01:03 +0100 Subject: [PATCH 01/14] Fix parsing color as sequence of ints 0-255 --- branca/colormap.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/branca/colormap.py b/branca/colormap.py index 2e76ba5..7b8d847 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -56,8 +56,9 @@ def _color_float_to_int(x: float) -> int: def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats: + """Convert an unknown color value to an RGBA tuple between 0 and 1.""" if isinstance(x, (tuple, list)): - return tuple(tuple(x) + (1.0,))[:4] # type: ignore + return _parse_color_as_numerical_sequence(x) elif isinstance(x, str) and _is_hex(x): return _parse_hex(x) elif isinstance(x, str): @@ -69,6 +70,23 @@ def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats: raise ValueError(f"Unrecognized color code {x!r}") +def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: + """Convert a color as a sequence of numbers to an RGBA tuple between 0 and 1.""" + if not 3 <= len(x) <= 4: + raise ValueError(f"Color sequence should have 3 or 4 elements, not {len(x)}.") + color: List[float] = [] + for value in x: + if isinstance(value, int): + color.append(_color_int_to_float(value)) + elif isinstance(value, float): + color.append(value) + else: + raise TypeError(f"Unexpected type in color sequence: {type(value)}.") + if len(color) == 3: + color.append(1.0) # add alpha channel + return tuple(color) # type: ignore + + def _base(x: float) -> float: if x > 0: base = pow(10, math.floor(math.log10(x))) From d4f573788c75a4e25ffedda1db42c3f6f61bc239 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:39:47 +0100 Subject: [PATCH 02/14] improve --- branca/colormap.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 7b8d847..30950c4 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -38,19 +38,19 @@ def _is_hex(x: str) -> bool: def _parse_hex(color_code: str) -> TypeRGBAFloats: return ( - _color_int_to_float(int(color_code[1:3], 16)), - _color_int_to_float(int(color_code[3:5], 16)), - _color_int_to_float(int(color_code[5:7], 16)), + _color_byte_to_normalized_float(int(color_code[1:3], 16)), + _color_byte_to_normalized_float(int(color_code[3:5], 16)), + _color_byte_to_normalized_float(int(color_code[5:7], 16)), 1.0, ) -def _color_int_to_float(x: int) -> float: +def _color_byte_to_normalized_float(x: Union[int, float]) -> float: """Convert an integer between 0 and 255 to a float between 0. and 1.0""" return x / 255.0 -def _color_float_to_int(x: float) -> int: +def _color_normalized_float_to_byte_int(x: float) -> int: """Convert a float between 0. and 1.0 to an integer between 0 and 255""" return int(x * 255.9999) @@ -72,19 +72,19 @@ def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats: def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: """Convert a color as a sequence of numbers to an RGBA tuple between 0 and 1.""" + if not all(isinstance(value, (int, float)) for value in x): + raise TypeError("Components in color sequence should all be int or float.") if not 3 <= len(x) <= 4: raise ValueError(f"Color sequence should have 3 or 4 elements, not {len(x)}.") - color: List[float] = [] - for value in x: - if isinstance(value, int): - color.append(_color_int_to_float(value)) - elif isinstance(value, float): - color.append(value) - else: - raise TypeError(f"Unexpected type in color sequence: {type(value)}.") + conversion_function = float + if 1 < max(x) <= 255: + conversion_function = _color_byte_to_normalized_float + if min(x) < 0 or max(x) > 255: + raise ValueError("Color components should be between 0.0 and 1.0 or 0 and 255.") + color: List[float] = [conversion_function(value) for value in x] if len(color) == 3: color.append(1.0) # add alpha channel - return tuple(color) # type: ignore + return color[0], color[1], color[2], color[3] def _base(x: float) -> float: @@ -175,7 +175,7 @@ def rgba_bytes_tuple(self, x: float) -> TypeRGBAInts: """Provides the color corresponding to value `x` in the form of a tuple (R,G,B,A) with int values between 0 and 255. """ - return tuple(_color_float_to_int(u) for u in self.rgba_floats_tuple(x)) # type: ignore + return tuple(_color_normalized_float_to_byte_int(u) for u in self.rgba_floats_tuple(x)) # type: ignore def rgb_bytes_tuple(self, x: float) -> TypeRGBInts: """Provides the color corresponding to value `x` in the From 43469e15cf0bcc92f3d6c6dbd4db8e261e8fdaab Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:39:51 +0100 Subject: [PATCH 03/14] add tests --- tests/test_colormap_parse.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_colormap_parse.py diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py new file mode 100644 index 0000000..ac69448 --- /dev/null +++ b/tests/test_colormap_parse.py @@ -0,0 +1,28 @@ +import pytest + +from branca.colormap import _parse_color_as_numerical_sequence + + +@pytest.mark.parametrize("input_data, expected", [ + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ((255, 0, 0), (1.0, 0.0, 0.0, 1.0)), + ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)), + ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5, 1.0)), + ((0.1, 0.2, 0.3, 0.4), (0.1, 0.2, 0.3, 0.4)), + ((0.0, 1.0, 0.0, 0.5), (0.0, 1.0, 0.0, 0.5)), + ((0, 0, 0, 255.0), (0.0, 0.0, 0.0, 1.0)), + ((0, 0, 255.0, 0.0), (0.0, 0.0, 1.0, 0.0)), +]) +def test_parse_color_as_numerical_sequence(input_data, expected): + assert _parse_color_as_numerical_sequence(input_data) == expected + +@pytest.mark.parametrize("input_data, raises", [ + ((256, 0, 0), ValueError), + ((0, 0, -1), ValueError), + ((0, 1, 2, 3, 4), ValueError), + ((0.5, 0.5, 0.5, "string"), TypeError), +]) +def test_parse_color_as_numerical_sequence_invalid(input_data, raises): + with pytest.raises(raises): + _parse_color_as_numerical_sequence(input_data) From 8b2f79a1cf62948a4b8bfcb00b1f3ed82dc13683 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:49:00 +0100 Subject: [PATCH 04/14] more tests! --- tests/test_colormap_parse.py | 140 ++++++++++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 20 deletions(-) diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index ac69448..3b9636e 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -1,28 +1,128 @@ import pytest -from branca.colormap import _parse_color_as_numerical_sequence - - -@pytest.mark.parametrize("input_data, expected", [ - ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), - ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), - ((255, 0, 0), (1.0, 0.0, 0.0, 1.0)), - ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)), - ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5, 1.0)), - ((0.1, 0.2, 0.3, 0.4), (0.1, 0.2, 0.3, 0.4)), - ((0.0, 1.0, 0.0, 0.5), (0.0, 1.0, 0.0, 0.5)), - ((0, 0, 0, 255.0), (0.0, 0.0, 0.0, 1.0)), - ((0, 0, 255.0, 0.0), (0.0, 0.0, 1.0, 0.0)), -]) +from branca.colormap import ( + _parse_color_as_numerical_sequence, + _color_normalized_float_to_byte_int, + _color_byte_to_normalized_float, + _parse_hex, + _is_hex, + _parse_color, +) + + +@pytest.mark.parametrize( + "input_data, expected", + [ + ((255, 0, 0), (1.0, 0.0, 0.0, 1.0)), + ((255, 0, 0, 127), (1.0, 0.0, 0.0, 0.4980392156862745)), + ("#FF0000", (1.0, 0.0, 0.0, 1.0)), + ("red", (1.0, 0.0, 0.0, 1.0)), + ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5, 1.0)), + ((0.25, 0.5, 0.75), (0.25, 0.5, 0.75, 1.0)), + ((0.1, 0.2, 0.3, 0.4), (0.1, 0.2, 0.3, 0.4)), + ("#0000FF", (0.0, 0.0, 1.0, 1.0)), + ("#00FF00", (0.0, 1.0, 0.0, 1.0)), + ("#FFFFFF", (1.0, 1.0, 1.0, 1.0)), + ("#000000", (0.0, 0.0, 0.0, 1.0)), + ("#808080", (0.5019607843137255, 0.5019607843137255, 0.5019607843137255, 1.0)), + ( + "#1A2B3C", + (0.10196078431372549, 0.16862745098039217, 0.23529411764705882, 1.0), + ), + ("green", (0.0, 0.5019607843137255, 0.0, 1.0)), + ], +) +def test_parse_color(input_data, expected): + assert _parse_color(input_data) == expected + + +@pytest.mark.parametrize( + "input_data, expected", + [ + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ((255, 0, 0), (1.0, 0.0, 0.0, 1.0)), + ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)), + ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5, 1.0)), + ((0.1, 0.2, 0.3, 0.4), (0.1, 0.2, 0.3, 0.4)), + ((0.0, 1.0, 0.0, 0.5), (0.0, 1.0, 0.0, 0.5)), + ((0, 0, 0, 255.0), (0.0, 0.0, 0.0, 1.0)), + ((0, 0, 255.0, 0.0), (0.0, 0.0, 1.0, 0.0)), + ], +) def test_parse_color_as_numerical_sequence(input_data, expected): assert _parse_color_as_numerical_sequence(input_data) == expected -@pytest.mark.parametrize("input_data, raises", [ - ((256, 0, 0), ValueError), - ((0, 0, -1), ValueError), - ((0, 1, 2, 3, 4), ValueError), - ((0.5, 0.5, 0.5, "string"), TypeError), -]) + +@pytest.mark.parametrize( + "input_data, raises", + [ + ((256, 0, 0), ValueError), + ((0, 0, -1), ValueError), + ((0, 1, 2, 3, 4), ValueError), + ((0.5, 0.5, 0.5, "string"), TypeError), + ], +) def test_parse_color_as_numerical_sequence_invalid(input_data, raises): with pytest.raises(raises): _parse_color_as_numerical_sequence(input_data) + + +@pytest.mark.parametrize( + "input_data, expected", + [ + ("#123456", True), + ("#abcdef", True), + ("#ABCDEF", True), + ("#1A2B3C", True), + ("#123", False), + ("123456", False), + ("#1234567", False), + ], +) +def test_is_hex(input_data, expected): + assert _is_hex(input_data) == expected + + +@pytest.mark.parametrize( + "input_data, expected", + [ + ("#000000", (0.0, 0.0, 0.0, 1.0)), + ("#FFFFFF", (1.0, 1.0, 1.0, 1.0)), + ("#FF0000", (1.0, 0.0, 0.0, 1.0)), + ("#00FF00", (0.0, 1.0, 0.0, 1.0)), + ("#0000FF", (0.0, 0.0, 1.0, 1.0)), + ("#808080", (0.5019607843137255, 0.5019607843137255, 0.5019607843137255, 1.0)), + ], +) +def test_parse_hex(input_data, expected): + assert _parse_hex(input_data) == expected + + +@pytest.mark.parametrize( + "input_data, expected", + [ + (0, 0.0), + (255, 1.0), + (128, 0.5019607843137255), + (64, 0.25098039215686274), + (192, 0.7529411764705882), + ], +) +def test_color_byte_to_normalized_float(input_data, expected): + assert _color_byte_to_normalized_float(input_data) == expected + + +@pytest.mark.parametrize( + "input_data, expected", + [ + (0.0, 0), + (0.5, 127), + (1.0, 255), + (0.9999, 255), + (0.1, 25), + (0.75, 191), + ], +) +def test_color_normalized_float_to_byte_int(input_data, expected): + assert _color_normalized_float_to_byte_int(input_data) == expected From 49710d0055c3daf695a1b89efc5d91779483baf3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 14:49:38 +0000 Subject: [PATCH 05/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_colormap_parse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index 3b9636e..b797eac 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -1,12 +1,12 @@ import pytest from branca.colormap import ( - _parse_color_as_numerical_sequence, - _color_normalized_float_to_byte_int, _color_byte_to_normalized_float, - _parse_hex, + _color_normalized_float_to_byte_int, _is_hex, _parse_color, + _parse_color_as_numerical_sequence, + _parse_hex, ) From 58aa4b259ec6a32566b39aa215ec81872072afe3 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:54:04 +0100 Subject: [PATCH 06/14] precommit --- branca/colormap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/branca/colormap.py b/branca/colormap.py index 30950c4..32e5848 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -175,7 +175,8 @@ def rgba_bytes_tuple(self, x: float) -> TypeRGBAInts: """Provides the color corresponding to value `x` in the form of a tuple (R,G,B,A) with int values between 0 and 255. """ - return tuple(_color_normalized_float_to_byte_int(u) for u in self.rgba_floats_tuple(x)) # type: ignore + return tuple(_color_normalized_float_to_byte_int(u) + for u in self.rgba_floats_tuple(x)) # type: ignore def rgb_bytes_tuple(self, x: float) -> TypeRGBInts: """Provides the color corresponding to value `x` in the From e5d0422ff7eb684e0ccc438c2a12a5fd3d722766 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:55:40 +0100 Subject: [PATCH 07/14] mypy --- branca/colormap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 32e5848..44c9b92 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -9,7 +9,7 @@ import json import math import os -from typing import Dict, List, Optional, Sequence, Tuple, Union +from typing import Dict, List, Optional, Sequence, Tuple, Union, Callable from jinja2 import Template @@ -76,7 +76,7 @@ def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: raise TypeError("Components in color sequence should all be int or float.") if not 3 <= len(x) <= 4: raise ValueError(f"Color sequence should have 3 or 4 elements, not {len(x)}.") - conversion_function = float + conversion_function: Callable = float if 1 < max(x) <= 255: conversion_function = _color_byte_to_normalized_float if min(x) < 0 or max(x) > 255: From f9b1e2881c03fcb27561afe051814bb45a8bdd22 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 14:55:50 +0000 Subject: [PATCH 08/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- branca/colormap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 44c9b92..544a163 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -9,7 +9,7 @@ import json import math import os -from typing import Dict, List, Optional, Sequence, Tuple, Union, Callable +from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union from jinja2 import Template @@ -175,8 +175,9 @@ def rgba_bytes_tuple(self, x: float) -> TypeRGBAInts: """Provides the color corresponding to value `x` in the form of a tuple (R,G,B,A) with int values between 0 and 255. """ - return tuple(_color_normalized_float_to_byte_int(u) - for u in self.rgba_floats_tuple(x)) # type: ignore + return tuple( + _color_normalized_float_to_byte_int(u) for u in self.rgba_floats_tuple(x) + ) # type: ignore def rgb_bytes_tuple(self, x: float) -> TypeRGBInts: """Provides the color corresponding to value `x` in the From 8a997f919a4715ab97f9699c4341e4311e845a95 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 16:09:27 +0100 Subject: [PATCH 09/14] rename back --- branca/colormap.py | 16 +++++++--------- tests/test_colormap_parse.py | 8 ++++---- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 544a163..6e4c64a 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -38,19 +38,19 @@ def _is_hex(x: str) -> bool: def _parse_hex(color_code: str) -> TypeRGBAFloats: return ( - _color_byte_to_normalized_float(int(color_code[1:3], 16)), - _color_byte_to_normalized_float(int(color_code[3:5], 16)), - _color_byte_to_normalized_float(int(color_code[5:7], 16)), + _color_int_to_float(int(color_code[1:3], 16)), + _color_int_to_float(int(color_code[3:5], 16)), + _color_int_to_float(int(color_code[5:7], 16)), 1.0, ) -def _color_byte_to_normalized_float(x: Union[int, float]) -> float: +def _color_int_to_float(x: Union[int, float]) -> float: """Convert an integer between 0 and 255 to a float between 0. and 1.0""" return x / 255.0 -def _color_normalized_float_to_byte_int(x: float) -> int: +def _color_float_to_int(x: float) -> int: """Convert a float between 0. and 1.0 to an integer between 0 and 255""" return int(x * 255.9999) @@ -78,7 +78,7 @@ def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: raise ValueError(f"Color sequence should have 3 or 4 elements, not {len(x)}.") conversion_function: Callable = float if 1 < max(x) <= 255: - conversion_function = _color_byte_to_normalized_float + conversion_function = _color_int_to_float if min(x) < 0 or max(x) > 255: raise ValueError("Color components should be between 0.0 and 1.0 or 0 and 255.") color: List[float] = [conversion_function(value) for value in x] @@ -175,9 +175,7 @@ def rgba_bytes_tuple(self, x: float) -> TypeRGBAInts: """Provides the color corresponding to value `x` in the form of a tuple (R,G,B,A) with int values between 0 and 255. """ - return tuple( - _color_normalized_float_to_byte_int(u) for u in self.rgba_floats_tuple(x) - ) # type: ignore + return tuple(_color_float_to_int(u) for u in self.rgba_floats_tuple(x)) # type: ignore def rgb_bytes_tuple(self, x: float) -> TypeRGBInts: """Provides the color corresponding to value `x` in the diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index b797eac..fc665ae 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -1,8 +1,8 @@ import pytest from branca.colormap import ( - _color_byte_to_normalized_float, - _color_normalized_float_to_byte_int, + _color_int_to_float, + _color_float_to_int, _is_hex, _parse_color, _parse_color_as_numerical_sequence, @@ -110,7 +110,7 @@ def test_parse_hex(input_data, expected): ], ) def test_color_byte_to_normalized_float(input_data, expected): - assert _color_byte_to_normalized_float(input_data) == expected + assert _color_int_to_float(input_data) == expected @pytest.mark.parametrize( @@ -125,4 +125,4 @@ def test_color_byte_to_normalized_float(input_data, expected): ], ) def test_color_normalized_float_to_byte_int(input_data, expected): - assert _color_normalized_float_to_byte_int(input_data) == expected + assert _color_float_to_int(input_data) == expected From 42e4d72fff4d345630441b59b1a72cb07570e7f6 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 16:10:26 +0100 Subject: [PATCH 10/14] docstrings --- branca/colormap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 6e4c64a..6f317d4 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -46,12 +46,12 @@ def _parse_hex(color_code: str) -> TypeRGBAFloats: def _color_int_to_float(x: Union[int, float]) -> float: - """Convert an integer between 0 and 255 to a float between 0. and 1.0""" + """Convert a byte between 0 and 255 to a normalized float between 0. and 1.0""" return x / 255.0 def _color_float_to_int(x: float) -> int: - """Convert a float between 0. and 1.0 to an integer between 0 and 255""" + """Convert a float between 0. and 1.0 to a byte integer between 0 and 255""" return int(x * 255.9999) From e3b97402fb809f9e42869ff993e2ca70b2d8441d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:10:49 +0000 Subject: [PATCH 11/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_colormap_parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index fc665ae..b974fb5 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -1,8 +1,8 @@ import pytest from branca.colormap import ( - _color_int_to_float, _color_float_to_int, + _color_int_to_float, _is_hex, _parse_color, _parse_color_as_numerical_sequence, From 91e20b186bfa39861a4884cb8e87489f27d6c926 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Sat, 15 Mar 2025 16:14:19 +0100 Subject: [PATCH 12/14] Update test_colormap_parse.py --- tests/test_colormap_parse.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index b974fb5..b3df2be 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -57,9 +57,15 @@ def test_parse_color_as_numerical_sequence(input_data, expected): @pytest.mark.parametrize( "input_data, raises", [ + # larger than 255 ((256, 0, 0), ValueError), + # smaller than 0 ((0, 0, -1), ValueError), + # sequence too long ((0, 1, 2, 3, 4), ValueError), + # sequence too short + ((0, 1), ValueError), + # invalid type in sequence ((0.5, 0.5, 0.5, "string"), TypeError), ], ) From fdc5cca1ec996dd8f612db0a3dd31061926ba360 Mon Sep 17 00:00:00 2001 From: Conengmo <33519926+Conengmo@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:21:46 +0100 Subject: [PATCH 13/14] deal with case of int {0, 1} --- branca/colormap.py | 21 +++++++++++++++------ tests/test_colormap_parse.py | 11 +++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/branca/colormap.py b/branca/colormap.py index 6f317d4..f8dda6d 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -56,7 +56,7 @@ def _color_float_to_int(x: float) -> int: def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats: - """Convert an unknown color value to an RGBA tuple between 0 and 1.""" + """Convert an unknown color value to an RGBA tuple of floats between 0 and 1.""" if isinstance(x, (tuple, list)): return _parse_color_as_numerical_sequence(x) elif isinstance(x, str) and _is_hex(x): @@ -71,19 +71,28 @@ def _parse_color(x: Union[tuple, list, str]) -> TypeRGBAFloats: def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: - """Convert a color as a sequence of numbers to an RGBA tuple between 0 and 1.""" + """Convert a color as a sequence of numbers to an RGBA tuple of floats between 0 and 1.""" if not all(isinstance(value, (int, float)) for value in x): raise TypeError("Components in color sequence should all be int or float.") if not 3 <= len(x) <= 4: - raise ValueError(f"Color sequence should have 3 or 4 elements, not {len(x)}.") - conversion_function: Callable = float - if 1 < max(x) <= 255: - conversion_function = _color_int_to_float + raise ValueError(f"Color sequence should have 3 or 4 components, not {len(x)}.") if min(x) < 0 or max(x) > 255: raise ValueError("Color components should be between 0.0 and 1.0 or 0 and 255.") + + if all(isinstance(value, int) for value in x): + # assume integers are a sequence of bytes that have to be normalized + conversion_function = _color_int_to_float + elif 1 < max(x) <= 255: + # values between 1 and 255 are bytes no matter the type and should be normalized + conversion_function = _color_int_to_float + else: + # else assume it's already normalized + conversion_function = float + color: List[float] = [conversion_function(value) for value in x] if len(color) == 3: color.append(1.0) # add alpha channel + return color[0], color[1], color[2], color[3] diff --git a/tests/test_colormap_parse.py b/tests/test_colormap_parse.py index b3df2be..34e9559 100644 --- a/tests/test_colormap_parse.py +++ b/tests/test_colormap_parse.py @@ -39,15 +39,26 @@ def test_parse_color(input_data, expected): @pytest.mark.parametrize( "input_data, expected", [ + # these are byte values as ints and should be normalized and converted ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), ((255, 0, 0), (1.0, 0.0, 0.0, 1.0)), + # a special case: ints that are 0 or 1 should be considered bytes + ((0, 0, 1), (0.0, 0.0, 1 / 255, 1.0)), + ((0, 0, 0, 1), (0.0, 0.0, 0.0, 1 / 255)), + # these already are normalized floats ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)), + ((0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0)), ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5, 1.0)), ((0.1, 0.2, 0.3, 0.4), (0.1, 0.2, 0.3, 0.4)), ((0.0, 1.0, 0.0, 0.5), (0.0, 1.0, 0.0, 0.5)), + # these are byte values as floats and should be normalized ((0, 0, 0, 255.0), (0.0, 0.0, 0.0, 1.0)), ((0, 0, 255.0, 0.0), (0.0, 0.0, 1.0, 0.0)), + # if floats and ints are mixed, assume they are intended as floats + ((0, 0, 1.0), (0.0, 0.0, 1.0, 1.0)), + # unless some of them are between 1 and 255 + ((0, 0, 1.0, 128), (0.0, 0.0, 1 / 255, 128 / 255)), ], ) def test_parse_color_as_numerical_sequence(input_data, expected): From beb8292d14cbbf7dd8d9d31dece55c833601b50a Mon Sep 17 00:00:00 2001 From: Conengmo <33519926+Conengmo@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:24:20 +0100 Subject: [PATCH 14/14] mypy --- branca/colormap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/branca/colormap.py b/branca/colormap.py index f8dda6d..0418ec0 100644 --- a/branca/colormap.py +++ b/branca/colormap.py @@ -81,7 +81,7 @@ def _parse_color_as_numerical_sequence(x: Union[tuple, list]) -> TypeRGBAFloats: if all(isinstance(value, int) for value in x): # assume integers are a sequence of bytes that have to be normalized - conversion_function = _color_int_to_float + conversion_function: Callable = _color_int_to_float elif 1 < max(x) <= 255: # values between 1 and 255 are bytes no matter the type and should be normalized conversion_function = _color_int_to_float