diff --git a/coloraide/algebra.py b/coloraide/algebra.py index 7925c165a..f22ad87d3 100644 --- a/coloraide/algebra.py +++ b/coloraide/algebra.py @@ -515,12 +515,12 @@ def cross(a: VectorLike, b: VectorLike) -> Vector: @overload -def cross(a: MatrixLike, b: VectorLike | MatrixLike) -> Matrix: +def cross(a: MatrixLike, b: Any) -> Matrix: ... @overload -def cross(a: VectorLike | MatrixLike, b: MatrixLike) -> Matrix: +def cross(a: Any, b: MatrixLike) -> Matrix: ... @@ -872,47 +872,57 @@ def _vector_math(op: Callable[..., float], a: VectorLike, b: VectorLike) -> Vect @overload -def _math(op: Callable[..., float], a: float, b: float, *, dims: Optional[tuple[int, int]] = None) -> float: - ... - - -@overload -def _math(op: Callable[..., float], a: float, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def _math(op: Callable[..., float], a: VectorLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def _math(op: Callable[..., float], a: float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def _math(op: Callable[..., float], a: MatrixLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def _math( + op: Callable[..., float], + a: float, + b: float, + *, + dims: Optional[tuple[int, int]] = None +) -> float: ... @overload -def _math(op: Callable[..., float], a: VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def _math( + op: Callable[..., float], + a: float | VectorLike, + b: VectorLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Vector: ... @overload -def _math(op: Callable[..., float], a: VectorLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def _math( + op: Callable[..., float], + a: VectorLike, + b: float | VectorLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Vector: ... @overload -def _math(op: Callable[..., float], a: MatrixLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def _math( + op: Callable[..., float], + a: MatrixLike, + b: float | ArrayLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Matrix: ... @overload -def _math(op: Callable[..., float], a: MatrixLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def _math( + op: Callable[..., float], + a: ArrayLike | float, + b: MatrixLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Matrix: ... @@ -1008,42 +1018,22 @@ def divide(a: float, b: float, *, dims: Optional[tuple[int, int]] = None) -> flo @overload -def divide(a: float, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def divide(a: VectorLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def divide(a: float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def divide(a: MatrixLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def divide(a: float | VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def divide(a: VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def divide(a: VectorLike, b: float | VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def divide(a: VectorLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def divide(a: MatrixLike, b: float | ArrayLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @overload -def divide(a: MatrixLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def divide(a: MatrixLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def divide(a: ArrayLike | float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @@ -1064,42 +1054,22 @@ def multiply(a: float, b: float, *, dims: Optional[tuple[int, int]] = None) -> f @overload -def multiply(a: float, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def multiply(a: float | VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def multiply(a: VectorLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def multiply(a: VectorLike, b: float | VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def multiply(a: float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def multiply(a: MatrixLike, b: float | ArrayLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @overload -def multiply(a: MatrixLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def multiply(a: VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def multiply(a: VectorLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def multiply(a: MatrixLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def multiply(a: MatrixLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def multiply(a: ArrayLike | float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @@ -1120,42 +1090,22 @@ def add(a: float, b: float, *, dims: Optional[tuple[int, int]] = None) -> float: @overload -def add(a: float, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def add(a: VectorLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def add(a: float | VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def add(a: float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def add(a: VectorLike, b: float | VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def add(a: MatrixLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def add(a: MatrixLike, b: float | ArrayLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @overload -def add(a: VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: - ... - - -@overload -def add(a: VectorLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def add(a: MatrixLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: - ... - - -@overload -def add(a: MatrixLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def add(a: ArrayLike | float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @@ -1176,54 +1126,104 @@ def subtract(a: float, b: float, *, dims: Optional[tuple[int, int]] = None) -> f @overload -def subtract(a: float, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def subtract(a: float | VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def subtract(a: VectorLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def subtract(a: VectorLike, b: float | VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: ... @overload -def subtract(a: float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def subtract(a: MatrixLike, b: float | ArrayLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... @overload -def subtract(a: MatrixLike, b: float, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def subtract(a: ArrayLike | float, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: ... +def subtract( + a: float | ArrayLike, + b: float | ArrayLike, + *, + dims: Optional[tuple[int, int]] = None +) -> float | Array: + """Subtract simple numbers, vectors, and 2D matrices.""" + + return _math(operator.sub, a, b, dims=dims) + + @overload -def subtract(a: VectorLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Vector: +def apply( + fn: Callable[..., float], + a: float, + b: Optional[float] = None, + *, + dims: Optional[tuple[int, int]] = None +) -> float: ... @overload -def subtract(a: VectorLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def apply( + fn: Callable[..., float], + a: float | VectorLike, + b: VectorLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Vector: ... @overload -def subtract(a: MatrixLike, b: VectorLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def apply( + fn: Callable[..., float], + a: VectorLike, + b: Optional[float | VectorLike] = None, + *, + dims: Optional[tuple[int, int]] = None +) -> Vector: ... @overload -def subtract(a: MatrixLike, b: MatrixLike, *, dims: Optional[tuple[int, int]] = None) -> Matrix: +def apply( + fn: Callable[..., float], + a: MatrixLike, + b: Optional[float | ArrayLike] = None, + *, + dims: Optional[tuple[int, int]] = None +) -> Matrix: ... -def subtract( +@overload +def apply( + fn: Callable[..., float], + a: ArrayLike | float, + b: MatrixLike, + *, + dims: Optional[tuple[int, int]] = None +) -> Matrix: + ... + + +def apply( + fn: Callable[..., float], a: float | ArrayLike, - b: float | ArrayLike, + b: Optional[float | ArrayLike] = None, *, dims: Optional[tuple[int, int]] = None ) -> float | Array: - """Subtract simple numbers, vectors, and 2D matrices.""" + """Apply a given function over each element of the matrix.""" - return _math(operator.sub, a, b, dims=dims) + if b is None: + return reshape([fn(f) for f in flatiter(a)], shape(a)) + + return _math(fn, a, b, dims=dims) class BroadcastTo: @@ -1790,7 +1790,7 @@ def diag(array: VectorLike, k: int = 0) -> Matrix: @overload -def diag(array: Matrix, k: int = 0) -> Vector: +def diag(array: MatrixLike, k: int = 0) -> Vector: ... diff --git a/tests/test_algebra.py b/tests/test_algebra.py index 47fe289da..bb3eb93d7 100644 --- a/tests/test_algebra.py +++ b/tests/test_algebra.py @@ -1,6 +1,7 @@ """Test Algebra.""" import unittest from coloraide import algebra as alg +import math class TestAlgebra(unittest.TestCase): @@ -1289,3 +1290,19 @@ def test_interpolate_natural(self): alg.interpolate([[3, 4], [6, 8], [9, 2]], method='natural').steps(5), [[3.0, 4.0], [4.5, 6.9375], [6.0, 8.0], [7.5, 5.9375], [9.0, 2.0]] ) + + def test_apply_two_inputs(self): + """Test apply with two inputs.""" + + self.assertEqual( + alg.apply(alg.npow, [[1, 2, 3], [4, 5, 6]], 2), + [[1, 4, 9], [16, 25, 36]] + ) + + def test_apply_one_input(self): + """Test apply with one input.""" + + self.assertEqual( + alg.apply(math.sqrt, [[1, 4, 9], [16, 25, 36]]), + [[1, 2, 3], [4, 5, 6]] + ) diff --git a/tools/calc_lch_threshold.py b/tools/calc_lch_threshold.py index 0c00575a8..152513df7 100644 --- a/tools/calc_lch_threshold.py +++ b/tools/calc_lch_threshold.py @@ -80,7 +80,7 @@ def run(lch, lab): m = RE_LEAD_ZERO.match(num) minimum = '0' better = '0' - if len(m.group(0)) != len(num): + if m and len(m.group(0)) != len(num): count = len(m.group(0)[2:]) if int(num[m.end(0)]) == 9: # Close to rolling over @@ -90,9 +90,13 @@ def run(lch, lab): minimum = '> 0.{}{}'.format('0' * count, str(int(num[m.end(0)]) + 1)) # Give at least a little over a decimal point better = '> 0.{}2'.format('0' * (count - 1)) + print('{}: minimum threshold: {}'.format(lch, minimum)) + print('{}: relaxed threshold: {}'.format(lch, better)) + else: + print('{}: minimum threshold: {}'.format(lch, '?')) + print('{}: relaxed threshold: {}'.format(lch, '?')) + print('{}: maximum chroma: {:.53f}'.format(lch, max_chroma)) - print('{}: minimum threshold: {}'.format(lch, minimum)) - print('{}: relaxed threshold: {}'.format(lch, better)) print( '\n* Only potential recommendations, adjustments can be made if desired.' diff --git a/tools/calc_range_rgb.py b/tools/calc_range_rgb.py index ac98a4597..e4fc2fee4 100644 --- a/tools/calc_range_rgb.py +++ b/tools/calc_range_rgb.py @@ -28,12 +28,15 @@ def main(): parser.add_argument( '--res', '-s', type=int, default=100, help="Resolution to use when calculating range, default is 100." ) + parser.add_argument( + '--precision', '-p', type=int, default=3, help="Precision for displaying the range." + ) args = parser.parse_args() - return run(args.color, args.rgb, args.res) + return run(args.color, args.rgb, args.res, args.precision) -def run(target, rgb, res): +def run(target, rgb, res, p): """Run.""" max_x = float('-inf') @@ -78,9 +81,9 @@ def run(target, rgb, res): print('') chan_x, chan_y, chan_z = Color('white').convert(target)._space.CHANNELS print('---- {} range in {} ----'.format(target, rgb)) - print('{}: [{}, {}]'.format(chan_x, alg.round_half_up(min_x, 3), alg.round_half_up(max_x, 3))) - print('{}: [{}, {}]'.format(chan_y, alg.round_half_up(min_y, 3), alg.round_half_up(max_y, 3))) - print('{}: [{}, {}]'.format(chan_z, alg.round_half_up(min_z, 3), alg.round_half_up(max_z, 3))) + print('{}: [{}, {}]'.format(chan_x, alg.round_half_up(min_x, p), alg.round_half_up(max_x, p))) + print('{}: [{}, {}]'.format(chan_y, alg.round_half_up(min_y, p), alg.round_half_up(max_y, p))) + print('{}: [{}, {}]'.format(chan_z, alg.round_half_up(min_z, p), alg.round_half_up(max_z, p))) return 0 diff --git a/tools/cie_diagrams.py b/tools/cie_diagrams.py index 8e02a4325..3481419c6 100644 --- a/tools/cie_diagrams.py +++ b/tools/cie_diagrams.py @@ -488,7 +488,7 @@ def __init__(self, mode="1931", observer='2deg', theme="light", title=""): self.title = title if theme == 'light': - plt.style.use('seaborn-darkgrid') + plt.style.use('seaborn-v0_8-darkgrid') self.default_color = "#00000088" self.default_colorized_color = "#00000088" self.locus_label_color = "#00000088" diff --git a/tools/easing_diagram.py b/tools/easing_diagram.py index ad561cbcc..71fa3fd01 100644 --- a/tools/easing_diagram.py +++ b/tools/easing_diagram.py @@ -49,7 +49,7 @@ def main(): xs.append(x) ys.append(ease(x)) - plt.style.use('seaborn-darkgrid') + plt.style.use('seaborn-v0_8-darkgrid') default_color = 'black' figure = plt.figure() diff --git a/tools/gamut_3d_diagrams.py b/tools/gamut_3d_diagrams.py index 32eb70d37..32f9715e1 100644 --- a/tools/gamut_3d_diagrams.py +++ b/tools/gamut_3d_diagrams.py @@ -243,6 +243,8 @@ def plot_gamut_in_space(space, gamut, title="", dark=False, resolution=70, rotat # Some spaces need us to rearrange the order of the data if is_labish: axm = [1, 2, 0] + elif is_lchish: + axm = target.lchish_indexes() elif is_cyl: axm = [2, 1, 0] else: @@ -254,7 +256,7 @@ def plot_gamut_in_space(space, gamut, title="", dark=False, resolution=70, rotat if dark: plt.style.use('dark_background') else: - plt.style.use('seaborn-bright') + plt.style.use('seaborn-v0_8-bright') # Setup figure and axis figure = plt.figure() diff --git a/tools/interp_3d_diagram.py b/tools/interp_3d_diagram.py index a675cc8a0..5bb12bd52 100644 --- a/tools/interp_3d_diagram.py +++ b/tools/interp_3d_diagram.py @@ -40,7 +40,7 @@ def main(): else: edge = 'black' line = '#333333' - plt.style.use('seaborn-bright') + plt.style.use('seaborn-v0_8-bright') # Setup figure and axis figure = plt.figure() diff --git a/tools/slice_diagram.py b/tools/slice_diagram.py index 8db61ba85..6d8aeb68a 100644 --- a/tools/slice_diagram.py +++ b/tools/slice_diagram.py @@ -58,7 +58,7 @@ def plot_slice( res = resolution if not dark: - plt.style.use('seaborn-darkgrid') + plt.style.use('seaborn-v0_8-darkgrid') default_color = 'black' else: plt.style.use('dark_background')