From d991df651b53778d9268dc7c8cb26248e9e0a0db Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Jul 2024 22:52:51 +1000 Subject: [PATCH] Return boolean from ImageMath comparison operations --- Tests/test_imagemath_lambda_eval.py | 203 ++++++++-------------------- Tests/test_imagemath_unsafe_eval.py | 76 +++++------ docs/reference/ImageMath.rst | 4 +- src/PIL/ImageMath.py | 42 +++--- 4 files changed, 120 insertions(+), 205 deletions(-) diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py index 360325780bb..259079fc3e7 100644 --- a/Tests/test_imagemath_lambda_eval.py +++ b/Tests/test_imagemath_lambda_eval.py @@ -187,8 +187,9 @@ def test_compare() -> None: ) == "I 2" ) - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1" - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0" + for i in range(1, 3): + assert ImageMath.lambda_eval(lambda args: args["A"] == i, **images) is False + assert ImageMath.lambda_eval(lambda args: args["A"] != i, **images) is True def test_one_image_larger() -> None: @@ -310,198 +311,108 @@ def test_bitwise_rightshift() -> None: def test_logical_eq() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["B"], B=B)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["B"], A=A, B=B)) - == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["A"], A=A, B=B)) - == "I 0" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A) is True + assert ImageMath.lambda_eval(lambda args: args["B"] == args["B"], B=B) is True + assert ImageMath.lambda_eval(lambda args: args["A"] == args["B"], A=A, B=B) is False + assert ImageMath.lambda_eval(lambda args: args["B"] == args["A"], A=A, B=B) is False def test_logical_ne() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["A"], A=A)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["B"], B=B)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["B"], A=A, B=B)) - == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["A"], A=A, B=B)) - == "I 1" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] != args["A"], A=A) is False + assert ImageMath.lambda_eval(lambda args: args["B"] != args["B"], B=B) is False + assert ImageMath.lambda_eval(lambda args: args["A"] != args["B"], A=A, B=B) is True + assert ImageMath.lambda_eval(lambda args: args["B"] != args["A"], A=A, B=B) is True def test_logical_lt() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["A"], A=A)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["B"], B=B)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["B"], A=A, B=B)) - == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["A"], A=A, B=B)) - == "I 0" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] < args["A"], A=A) is False + assert ImageMath.lambda_eval(lambda args: args["B"] < args["B"], B=B) is False + assert ImageMath.lambda_eval(lambda args: args["A"] < args["B"], A=A, B=B) is True + assert ImageMath.lambda_eval(lambda args: args["B"] < args["A"], A=A, B=B) is False def test_logical_le() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["A"], A=A)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["B"], B=B)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["B"], A=A, B=B)) - == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["A"], A=A, B=B)) - == "I 0" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] <= args["A"], A=A) is True + assert ImageMath.lambda_eval(lambda args: args["B"] <= args["B"], B=B) is True + assert ImageMath.lambda_eval(lambda args: args["A"] <= args["B"], A=A, B=B) is True + assert ImageMath.lambda_eval(lambda args: args["B"] <= args["A"], A=A, B=B) is False def test_logical_gt() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["A"], A=A)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["B"], B=B)) == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["B"], A=A, B=B)) - == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["A"], A=A, B=B)) - == "I 1" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] > args["A"], A=A) is False + assert ImageMath.lambda_eval(lambda args: args["B"] > args["B"], B=B) is False + assert ImageMath.lambda_eval(lambda args: args["A"] > args["B"], A=A, B=B) is False + assert ImageMath.lambda_eval(lambda args: args["B"] > args["A"], A=A, B=B) is True def test_logical_ge() -> None: - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["A"], A=A)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["B"], B=B)) == "I 1" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["B"], A=A, B=B)) - == "I 0" - ) - assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["A"], A=A, B=B)) - == "I 1" - ) + assert ImageMath.lambda_eval(lambda args: args["A"] >= args["A"], A=A) is True + assert ImageMath.lambda_eval(lambda args: args["B"] >= args["B"], B=B) is True + assert ImageMath.lambda_eval(lambda args: args["A"] >= args["B"], A=A, B=B) is False + assert ImageMath.lambda_eval(lambda args: args["B"] >= args["A"], A=A, B=B) is True def test_logical_equal() -> None: assert ( - pixel( - ImageMath.lambda_eval(lambda args: args["equal"](args["A"], args["A"]), A=A) - ) - == "I 1" + ImageMath.lambda_eval(lambda args: args["equal"](args["A"], args["A"]), A=A) + is True ) assert ( - pixel( - ImageMath.lambda_eval(lambda args: args["equal"](args["B"], args["B"]), B=B) - ) - == "I 1" + ImageMath.lambda_eval(lambda args: args["equal"](args["B"], args["B"]), B=B) + is True ) assert ( - pixel( - ImageMath.lambda_eval(lambda args: args["equal"](args["Z"], args["Z"]), Z=Z) - ) - == "I 1" + ImageMath.lambda_eval(lambda args: args["equal"](args["Z"], args["Z"]), Z=Z) + is True ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["equal"](args["A"], args["B"]), A=A, B=B - ) + ImageMath.lambda_eval( + lambda args: args["equal"](args["A"], args["B"]), A=A, B=B ) - == "I 0" + is False ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["equal"](args["B"], args["A"]), A=A, B=B - ) + ImageMath.lambda_eval( + lambda args: args["equal"](args["B"], args["A"]), A=A, B=B ) - == "I 0" + is False ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["equal"](args["A"], args["Z"]), A=A, Z=Z - ) + ImageMath.lambda_eval( + lambda args: args["equal"](args["A"], args["Z"]), A=A, Z=Z ) - == "I 0" + is False ) def test_logical_not_equal() -> None: assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["A"], args["A"]), A=A - ) - ) - == "I 0" + ImageMath.lambda_eval(lambda args: args["notequal"](args["A"], args["A"]), A=A) + is False ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["B"], args["B"]), B=B - ) - ) - == "I 0" + ImageMath.lambda_eval(lambda args: args["notequal"](args["B"], args["B"]), B=B) + is False ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["Z"], args["Z"]), Z=Z - ) - ) - == "I 0" + ImageMath.lambda_eval(lambda args: args["notequal"](args["Z"], args["Z"]), Z=Z) + is False ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["A"], args["B"]), A=A, B=B - ) + ImageMath.lambda_eval( + lambda args: args["notequal"](args["A"], args["B"]), A=A, B=B ) - == "I 1" + is True ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["B"], args["A"]), A=A, B=B - ) + ImageMath.lambda_eval( + lambda args: args["notequal"](args["B"], args["A"]), A=A, B=B ) - == "I 1" + is True ) assert ( - pixel( - ImageMath.lambda_eval( - lambda args: args["notequal"](args["A"], args["Z"]), A=A, Z=Z - ) + ImageMath.lambda_eval( + lambda args: args["notequal"](args["A"], args["Z"]), A=A, Z=Z ) - == "I 1" + is True ) diff --git a/Tests/test_imagemath_unsafe_eval.py b/Tests/test_imagemath_unsafe_eval.py index b7ac8469180..43a53c6c170 100644 --- a/Tests/test_imagemath_unsafe_eval.py +++ b/Tests/test_imagemath_unsafe_eval.py @@ -104,8 +104,8 @@ def test_convert() -> None: def test_compare() -> None: assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1" assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2" - assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0" + assert ImageMath.unsafe_eval("A == 1", **images) is False + assert ImageMath.unsafe_eval("A == 2", **images) is False def test_one_image_larger() -> None: @@ -169,60 +169,60 @@ def test_bitwise_rightshift() -> None: def test_logical_eq() -> None: - assert pixel(ImageMath.unsafe_eval("A==A", A=A)) == "I 1" - assert pixel(ImageMath.unsafe_eval("B==B", B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A==B", A=A, B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B==A", A=A, B=B)) == "I 0" + assert ImageMath.unsafe_eval("A==A", A=A) is True + assert ImageMath.unsafe_eval("B==B", B=B) is True + assert ImageMath.unsafe_eval("A==B", A=A, B=B) is False + assert ImageMath.unsafe_eval("B==A", A=A, B=B) is False def test_logical_ne() -> None: - assert pixel(ImageMath.unsafe_eval("A!=A", A=A)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B!=B", B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("A!=B", A=A, B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("B!=A", A=A, B=B)) == "I 1" + assert ImageMath.unsafe_eval("A!=A", A=A) is False + assert ImageMath.unsafe_eval("B!=B", B=B) is False + assert ImageMath.unsafe_eval("A!=B", A=A, B=B) is True + assert ImageMath.unsafe_eval("B!=A", A=A, B=B) is True def test_logical_lt() -> None: - assert pixel(ImageMath.unsafe_eval("A None: - assert pixel(ImageMath.unsafe_eval("A<=A", A=A)) == "I 1" - assert pixel(ImageMath.unsafe_eval("B<=B", B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A<=B", A=A, B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("B<=A", A=A, B=B)) == "I 0" + assert ImageMath.unsafe_eval("A<=A", A=A) is True + assert ImageMath.unsafe_eval("B<=B", B=B) is True + assert ImageMath.unsafe_eval("A<=B", A=A, B=B) is True + assert ImageMath.unsafe_eval("B<=A", A=A, B=B) is False def test_logical_gt() -> None: - assert pixel(ImageMath.unsafe_eval("A>A", A=A)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B>B", B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("A>B", A=A, B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B>A", A=A, B=B)) == "I 1" + assert ImageMath.unsafe_eval("A>A", A=A) is False + assert ImageMath.unsafe_eval("B>B", B=B) is False + assert ImageMath.unsafe_eval("A>B", A=A, B=B) is False + assert ImageMath.unsafe_eval("B>A", A=A, B=B) is True def test_logical_ge() -> None: - assert pixel(ImageMath.unsafe_eval("A>=A", A=A)) == "I 1" - assert pixel(ImageMath.unsafe_eval("B>=B", B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A>=B", A=A, B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B>=A", A=A, B=B)) == "I 1" + assert ImageMath.unsafe_eval("A>=A", A=A) is True + assert ImageMath.unsafe_eval("B>=B", B=B) is True + assert ImageMath.unsafe_eval("A>=B", A=A, B=B) is False + assert ImageMath.unsafe_eval("B>=A", A=A, B=B) is True def test_logical_equal() -> None: - assert pixel(ImageMath.unsafe_eval("equal(A, A)", A=A)) == "I 1" - assert pixel(ImageMath.unsafe_eval("equal(B, B)", B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("equal(Z, Z)", Z=Z)) == "I 1" - assert pixel(ImageMath.unsafe_eval("equal(A, B)", A=A, B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("equal(B, A)", A=A, B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("equal(A, Z)", A=A, Z=Z)) == "I 0" + assert ImageMath.unsafe_eval("equal(A, A)", A=A) is True + assert ImageMath.unsafe_eval("equal(B, B)", B=B) is True + assert ImageMath.unsafe_eval("equal(Z, Z)", Z=Z) is True + assert ImageMath.unsafe_eval("equal(A, B)", A=A, B=B) is False + assert ImageMath.unsafe_eval("equal(B, A)", A=A, B=B) is False + assert ImageMath.unsafe_eval("equal(A, Z)", A=A, Z=Z) is False def test_logical_not_equal() -> None: - assert pixel(ImageMath.unsafe_eval("notequal(A, A)", A=A)) == "I 0" - assert pixel(ImageMath.unsafe_eval("notequal(B, B)", B=B)) == "I 0" - assert pixel(ImageMath.unsafe_eval("notequal(Z, Z)", Z=Z)) == "I 0" - assert pixel(ImageMath.unsafe_eval("notequal(A, B)", A=A, B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("notequal(B, A)", A=A, B=B)) == "I 1" - assert pixel(ImageMath.unsafe_eval("notequal(A, Z)", A=A, Z=Z)) == "I 1" + assert ImageMath.unsafe_eval("notequal(A, A)", A=A) is False + assert ImageMath.unsafe_eval("notequal(B, B)", B=B) is False + assert ImageMath.unsafe_eval("notequal(Z, Z)", Z=Z) is False + assert ImageMath.unsafe_eval("notequal(A, B)", A=A, B=B) is True + assert ImageMath.unsafe_eval("notequal(B, A)", A=A, B=B) is True + assert ImageMath.unsafe_eval("notequal(A, Z)", A=A, Z=Z) is True diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index f4e1081e6b7..6a6e389b1b0 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -42,7 +42,7 @@ Example: Using the :py:mod:`~PIL.ImageMath` module shown in the above example. :param \**kw: Values to add to the function's dictionary, mapping image names to Image instances. - :return: An image, an integer value, a floating point value, + :return: An image, an integer value, a floating point value, a boolean, or a pixel tuple, depending on the expression. .. py:function:: unsafe_eval(expression, options, **kw) @@ -68,7 +68,7 @@ Example: Using the :py:mod:`~PIL.ImageMath` module shown in the above example. :param \**kw: Values to add to the evaluation context, mapping image names to Image instances. - :return: An image, an integer value, a floating point value, + :return: An image, an integer value, a floating point value, a boolean, or a pixel tuple, depending on the expression. Expression syntax diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 191cc2a5f88..13f917c9263 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -176,23 +176,27 @@ def __rshift__(self, other: _Operand | float) -> _Operand: return self.apply("rshift", self, other) # logical - def __eq__(self, other): - return self.apply("eq", self, other) + def __eq__(self, other: object) -> bool: + if not isinstance(other, _Operand): + return False + return bool(self.apply("eq", self, other)) - def __ne__(self, other): - return self.apply("ne", self, other) + def __ne__(self, other: object) -> bool: + if not isinstance(other, _Operand): + return True + return bool(self.apply("ne", self, other)) - def __lt__(self, other: _Operand | float) -> _Operand: - return self.apply("lt", self, other) + def __lt__(self, other: _Operand | float) -> bool: + return bool(self.apply("lt", self, other)) - def __le__(self, other: _Operand | float) -> _Operand: - return self.apply("le", self, other) + def __le__(self, other: _Operand | float) -> bool: + return bool(self.apply("le", self, other)) - def __gt__(self, other: _Operand | float) -> _Operand: - return self.apply("gt", self, other) + def __gt__(self, other: _Operand | float) -> bool: + return bool(self.apply("gt", self, other)) - def __ge__(self, other: _Operand | float) -> _Operand: - return self.apply("ge", self, other) + def __ge__(self, other: _Operand | float) -> bool: + return bool(self.apply("ge", self, other)) # conversions @@ -205,12 +209,12 @@ def imagemath_float(self: _Operand) -> _Operand: # logical -def imagemath_equal(self: _Operand, other: _Operand | float | None) -> _Operand: - return self.apply("eq", self, other, mode="I") +def imagemath_equal(self: _Operand, other: _Operand | float | None) -> bool: + return bool(self.apply("eq", self, other, mode="I")) -def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> _Operand: - return self.apply("ne", self, other, mode="I") +def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> bool: + return bool(self.apply("ne", self, other, mode="I")) def imagemath_min(self: _Operand, other: _Operand | float | None) -> _Operand: @@ -253,7 +257,7 @@ def lambda_eval( You can instead use one or more keyword arguments. :param **kw: Values to add to the function's dictionary. :return: The expression result. This is usually an image object, but can - also be an integer, a floating point value, or a pixel tuple, + also be an integer, a floating point value, a boolean, or a pixel tuple, depending on the expression. """ @@ -298,7 +302,7 @@ def unsafe_eval( You can instead use one or more keyword arguments. :param **kw: Values to add to the evaluation context. :return: The evaluated expression. This is usually an image object, but can - also be an integer, a floating point value, or a pixel tuple, + also be an integer, a floating point value, a boolean, or a pixel tuple, depending on the expression. """ @@ -357,7 +361,7 @@ def eval( can either use a dictionary, or one or more keyword arguments. :return: The evaluated expression. This is usually an image object, but can - also be an integer, a floating point value, or a pixel tuple, + also be an integer, a floating point value, a boolean, or a pixel tuple, depending on the expression. .. deprecated:: 10.3.0