1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2026-03-19 09:19:35 +03:00
67 changed files with 1881 additions and 555 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ def __load_extra_py_code_for_module(base, name, enable_debug_print=False):
native_module = sys.modules.pop(module_name, None)
try:
py_module = importlib.import_module(module_name)
except ImportError as err:
except (ImportError, AttributeError) as err:
if enable_debug_print:
print("Can't load Python code for module:", module_name,
". Reason:", err)
@@ -13,6 +13,21 @@ else:
from distutils.dir_util import copy_tree
def _remove_stale_pyi_files(directory):
"""Remove .pyi files and py.typed markers from the directory tree.
During incremental builds, disabling a previously enabled module leaves
stale typing stubs in the loader directory from a previous copy. Since
copy_tree merges rather than replaces, those stale files persist.
Removing all stub files before copying ensures only stubs for currently
enabled modules are present. Runtime .py files are not affected.
"""
for dirpath, dirnames, filenames in os.walk(directory):
for fname in filenames:
if fname.endswith('.pyi') or fname == 'py.typed':
os.remove(os.path.join(dirpath, fname))
def main():
args = parse_arguments()
py_typed_path = os.path.join(args.stubs_dir, 'py.typed')
@@ -24,6 +39,8 @@ def main():
'generation phase.'.format(py_typed_path)
)
return
if os.path.isdir(args.output_dir):
_remove_stale_pyi_files(args.output_dir)
copy_tree(args.stubs_dir, args.output_dir)
+1 -1
View File
@@ -227,7 +227,7 @@ static PyObject* createSubmodule(PyObject* parent_module, const std::string& nam
}
/// Populates parent module dictionary. Submodule lifetime should be managed
/// by the global modules dictionary and parent module dictionary, so Py_DECREF after
/// successfull call to the `PyDict_SetItemString` is redundant.
/// successful call to the `PyDict_SetItemString` is redundant.
if (PyDict_SetItemString(parent_module_dict, submodule_name.c_str(), submodule) < 0) {
return PyErr_Format(PyExc_ImportError,
"Can't register a submodule '%s' (full name: '%s')",
@@ -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,11 @@ 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,
SymbolName(("cv", ), (), "HoughCircles"): make_optional_none_return,
SymbolName(("cv", ), (), "HoughLines"): make_optional_none_return,
SymbolName(("cv", ), (), "HoughLinesP"): 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 = (
@@ -3,6 +3,7 @@ __all__ = ("generate_typing_stubs", )
from io import StringIO
from pathlib import Path
import re
import shutil
from typing import (Callable, NamedTuple, Union, Set, Dict,
Collection, Tuple, List)
import warnings
@@ -24,6 +25,22 @@ from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode,
ConditionalAliasTypeNode, PrimitiveTypeNode)
def _clean_stale_stubs_dirs(stubs_root: Path) -> None:
"""Remove all subdirectories under stubs_root.
During incremental builds, disabling a previously enabled module leaves
behind its typing stub directory (e.g. cv2/gapi/). Removing all
subdirectories before regeneration ensures only stubs for currently
enabled modules are present. Top-level files (py.typed, __init__.pyi)
are kept because they are managed separately.
"""
if not stubs_root.is_dir():
return
for item in stubs_root.iterdir():
if item.is_dir():
shutil.rmtree(item)
def generate_typing_stubs(root: NamespaceNode, output_path: Path):
"""Generates typing stubs for the AST with root `root` and outputs
created files tree to directory pointed by `output_path`.
@@ -88,6 +105,12 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path):
# The whole process should fail !only! when all possible scopes are
# checked and at least 1 node is still unresolved.
root.resolve_type_nodes()
# Remove stale typing stub subdirectories from previous builds.
# In incremental builds, disabling a module (e.g. -DBUILD_opencv_gapi=OFF)
# no longer generates its stubs, but leftover directories from a previous
# build persist and propagate through the copy/install steps, causing
# type-checker errors for stubs referencing unavailable modules.
_clean_stale_stubs_dirs(Path(output_path) / root.export_name)
_generate_typing_module(root, output_path)
_populate_reexported_symbols(root)
_generate_typing_stubs(root, output_path)
@@ -708,7 +731,7 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
"""
def has_all_required_modules(type_node: TypeNode) -> bool:
return all(em in root.namespaces for em in node.required_modules)
return all(em in root.namespaces for em in type_node.required_modules)
def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
assert isinstance(type_node, AggregatedTypeNode), \