Skip to content

Python: Scalar typing change complicates usage #27528

@vfazio

Description

@vfazio

System Information

opencv 4.12.0.88
OS: Ubuntu 22.04
Python 3.10+

Detailed description

PR #26826 changed the typing of cv2.typing.Scalar:

Scalar = _typing.Union[_typing.Sequence[float], float]

Based on the conversation in that PR and related issue, the original problem seems to be that for calls from Python -> C++, for functions typed as taking a Scalar, a Python float and int gets implicitly converted to a 4 channel Scalar type as a convenience to mimic the constructor for Scalar:

if (PyFloat_Check(o) || PyInt_Check(o)) {
s = PyFloat_AsDouble(o);
} else {

template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
{
public:
//! default constructor
Scalar_();
Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0);
Scalar_(_Tp v0);
Scalar_(const Scalar_& s);
Scalar_(Scalar_&& s) CV_NOEXCEPT;
Scalar_& operator=(const Scalar_& s);
Scalar_& operator=(Scalar_&& s) CV_NOEXCEPT;
template<typename _Tp2, int cn>

The code which highlighted the problem:

>>> cv2.line(image, (1, 1), (8, 8), color=255)
array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0, 255,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0, 255,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0, 255,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0, 255,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0, 255,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0, 255,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0, 255,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0, 255,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)

Note that the following works as well and conforms to the original typing:

>>> cv2.line(image, (1, 1), (8, 8), color=(255,0,0,0))
array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0, 255,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0, 255,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0, 255,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0, 255,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0, 255,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0, 255,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0, 255,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0, 255,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ cat test.py 
import cv2
import numpy as np

image = np.zeros((10, 10), dtype=np.uint8)
cv2.line(image, (1, 1), (8, 8), color=(255,0,0,0))
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ mypy test.py 
Success: no issues found in 1 source file

Now that the typing has changed, all functions that return a Scalar:

(xtf-py3.12) xtf-py3.12vfazio@vfazio4 /mnt/development/xtf $ grep -r "\-> cv2.typing.Scalar" .venv/lib/python3.12/site-packages/cv2/
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def mean(src: cv2.typing.MatLike, mask: cv2.typing.MatLike | None = ...) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def mean(src: UMat, mask: UMat | None = ...) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def sumElems(src: cv2.typing.MatLike) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def sumElems(src: UMat) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def trace(mtx: cv2.typing.MatLike) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def trace(mtx: UMat) -> cv2.typing.Scalar: ...

have to contend with the fact that Scalar is now either a Sequence[float] or float. In reality, they will likely only be Sequence[float] due to the Scalar being marshaled back to Python as a 4 element tuple:

PyObject* pyopencv_from(const Scalar& src)
{
return Py_BuildValue("(dddd)", src[0], src[1], src[2], src[3]);
}

All callers have to cast this value to drop the float condition since it will never be true.

value = cast(Sequence[float], cv2.mean(ssim_map))

Since the conversion is uni-directional, arguments into functions can be typed Sequence[float] | float, but return values, if they're actually backed by Scalar, should only return Sequence[float].

This can be solved by:

  • Having a type for each direction
    • Allows flexibility in argument types
    • single value conversion path can still be utilized
    • this complicates typing and stub generation
  • Revert the type change
    • A single value will still work, callers can use a pragma to ignore the typing error or convert to a sequence type to comply with type checks
    • Allows a future deprecation of the single value conversion path

Steps to reproduce

(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ cat test2.py 
import cv2
import numpy as np

image = np.zeros((10, 10), dtype=np.uint8)
x = cv2.mean(image)
reveal_type(x)
z = tuple(x)

(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ mypy test2.py
test2.py:6: note: Revealed type is "Union[typing.Sequence[builtins.float], builtins.float]"
test2.py:7: error: Argument 1 to "tuple" has incompatible type "Sequence[float] | float"; expected "Iterable[float]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Issue submission checklist

  • I report the issue, it's not a question
  • I checked the problem with documentation, FAQ, open issues, forum.opencv.org, Stack Overflow, etc and have not found any solution
  • I updated to the latest OpenCV version and the issue is still there
  • There is reproducer code and related data files (videos, images, onnx, etc)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy