diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index 3019e67e96..f92c5c8ce1 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -8,7 +8,7 @@ from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode, ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode, AggregatedTypeNode, CallableTypeNode, AnyTypeNode, TupleTypeNode, UnionTypeNode, ProtocolClassNode, - DictTypeNode, ClassTypeNode) + DictTypeNode, ClassTypeNode, AliasRefTypeNode) from .ast_utils import (find_function_node, SymbolName, for_each_function_overload) from .types_conversion import create_type_node @@ -376,6 +376,62 @@ def _find_argument_index(arguments: Sequence[FunctionNode.Arg], return None +def make_matlike_or_scalar_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]: + """Make arguments accept both MatLike and Scalar types. + + This is used for functions like inRange where the C++ InputArray parameter + can accept both Mat objects and Scalar values (tuples, floats, etc.). + + Example: cv2.inRange(img, (0, 0, 0), (255, 255, 255)) should be valid. + """ + def _make_matlike_or_scalar_arg(root_node: NamespaceNode, + function_symbol_name: SymbolName) -> None: + from .predefined_types import PREDEFINED_TYPES + + function = find_function_node(root_node, function_symbol_name) + for arg_name in arg_names: + found_overload_with_arg = False + + for overload in function.overloads: + arg_idx = _find_argument_index(overload.arguments, arg_name) + + # skip overloads without this argument + if arg_idx is None: + continue + + current_type = overload.arguments[arg_idx].type_node + + # Check if it's already a union or if it already includes Scalar + if isinstance(current_type, UnionTypeNode): + # Check if Scalar is already in the union + has_scalar = any( + isinstance(item, AliasRefTypeNode) and item.typename == "Scalar" + for item in current_type.items + ) + if has_scalar: + continue + # Add Scalar to existing union + scalar_ref = AliasRefTypeNode("Scalar") + current_type.items = current_type.items + (scalar_ref,) + else: + # Create a union of current type and Scalar + scalar_ref = AliasRefTypeNode("Scalar") + overload.arguments[arg_idx].type_node = UnionTypeNode( + f"{arg_name}_type", + (cast(TypeNode, current_type), scalar_ref) + ) + + found_overload_with_arg = True + + if not found_overload_with_arg: + raise RuntimeError( + f"Failed to find argument with name: '{arg_name}'" + f" in '{function_symbol_name.name}' overloads" + ) + + return _make_matlike_or_scalar_arg + + NODES_TO_REFINE = { SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"), SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), @@ -401,6 +457,8 @@ NODES_TO_REFINE = { SymbolName(("cv", "fisheye"), (), "initUndistortRectifyMap"): make_optional_arg("D"), SymbolName(("cv", ), (), "imread"): make_optional_none_return, SymbolName(("cv", ), (), "imdecode"): make_optional_none_return, + # Fix for issue #28534: inRange should accept Scalar for lowerb and upperb + SymbolName(("cv", ), (), "inRange"): make_matlike_or_scalar_arg("lowerb", "upperb"), } ERROR_CLASS_PROPERTIES = (