|
|
|
@ -241,6 +241,7 @@ class ClassProp(object): |
|
|
|
|
def __init__(self, decl): |
|
|
|
|
self.tp = decl[0].replace("*", "_ptr") |
|
|
|
|
self.name = decl[1] |
|
|
|
|
self.default_value = decl[2] |
|
|
|
|
self.readonly = True |
|
|
|
|
if "/RW" in decl[3]: |
|
|
|
|
self.readonly = False |
|
|
|
@ -268,6 +269,7 @@ class ClassInfo(object): |
|
|
|
|
|
|
|
|
|
self.cname = name.replace(".", "::") |
|
|
|
|
self.ismap = False |
|
|
|
|
self.is_parameters = False |
|
|
|
|
self.issimple = False |
|
|
|
|
self.isalgorithm = False |
|
|
|
|
self.methods = {} |
|
|
|
@ -300,6 +302,9 @@ class ClassInfo(object): |
|
|
|
|
self.ismap = True |
|
|
|
|
elif m == "/Simple": |
|
|
|
|
self.issimple = True |
|
|
|
|
elif m == "/Params": |
|
|
|
|
self.is_parameters = True |
|
|
|
|
self.issimple = True |
|
|
|
|
self.props = [ClassProp(p) for p in decl[3]] |
|
|
|
|
|
|
|
|
|
if not self.has_export_alias and self.original_name.startswith("Cv"): |
|
|
|
@ -421,39 +426,55 @@ def handle_ptr(tp): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ArgInfo(object): |
|
|
|
|
def __init__(self, arg_tuple): |
|
|
|
|
self.tp = handle_ptr(arg_tuple[0]) |
|
|
|
|
self.name = arg_tuple[1] |
|
|
|
|
if self.name in python_reserved_keywords: |
|
|
|
|
self.name += "_" |
|
|
|
|
self.defval = arg_tuple[2] |
|
|
|
|
def __init__(self, atype, name, default_value, modifiers=(), |
|
|
|
|
enclosing_arg=None): |
|
|
|
|
# type: (ArgInfo, str, str, str, tuple[str, ...], ArgInfo | None) -> None |
|
|
|
|
self.tp = handle_ptr(atype) |
|
|
|
|
self.name = name |
|
|
|
|
self.defval = default_value |
|
|
|
|
self._modifiers = tuple(modifiers) |
|
|
|
|
self.isarray = False |
|
|
|
|
self.is_smart_ptr = self.tp.startswith('Ptr<') # FIXIT: handle through modifiers - need to modify parser |
|
|
|
|
self.arraylen = 0 |
|
|
|
|
self.arraycvt = None |
|
|
|
|
self.inputarg = True |
|
|
|
|
self.outputarg = False |
|
|
|
|
self.returnarg = False |
|
|
|
|
self.isrvalueref = False |
|
|
|
|
for m in arg_tuple[3]: |
|
|
|
|
if m == "/O": |
|
|
|
|
self.inputarg = False |
|
|
|
|
self.outputarg = True |
|
|
|
|
self.returnarg = True |
|
|
|
|
elif m == "/IO": |
|
|
|
|
self.inputarg = True |
|
|
|
|
self.outputarg = True |
|
|
|
|
self.returnarg = True |
|
|
|
|
elif m.startswith("/A"): |
|
|
|
|
for m in self._modifiers: |
|
|
|
|
if m.startswith("/A"): |
|
|
|
|
self.isarray = True |
|
|
|
|
self.arraylen = m[2:].strip() |
|
|
|
|
elif m.startswith("/CA"): |
|
|
|
|
self.isarray = True |
|
|
|
|
self.arraycvt = m[2:].strip() |
|
|
|
|
elif m == "/RRef": |
|
|
|
|
self.isrvalueref = True |
|
|
|
|
self.py_inputarg = False |
|
|
|
|
self.py_outputarg = False |
|
|
|
|
self.enclosing_arg = enclosing_arg |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def export_name(self): |
|
|
|
|
if self.name in python_reserved_keywords: |
|
|
|
|
return self.name + '_' |
|
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def inputarg(self): |
|
|
|
|
return '/O' not in self._modifiers |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def outputarg(self): |
|
|
|
|
return '/O' in self._modifiers or '/IO' in self._modifiers |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def returnarg(self): |
|
|
|
|
return self.outputarg |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def isrvalueref(self): |
|
|
|
|
return '/RRef' in self._modifiers |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def full_name(self): |
|
|
|
|
if self.enclosing_arg is None: |
|
|
|
|
return self.name |
|
|
|
|
return self.enclosing_arg.name + '.' + self.name |
|
|
|
|
|
|
|
|
|
def isbig(self): |
|
|
|
|
return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector") |
|
|
|
@ -462,9 +483,62 @@ class ArgInfo(object): |
|
|
|
|
return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_argument_class_info(argument_type, function_namespace, |
|
|
|
|
function_class_name, known_classes): |
|
|
|
|
# type: (str, str, str, dict[str, ClassInfo]) -> ClassInfo | None |
|
|
|
|
"""Tries to find corresponding class info for the provided argument type |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
argument_type (str): Function argument type |
|
|
|
|
function_namespace (str): Namespace of the function declaration |
|
|
|
|
function_class_name (str): Name of the class if function is a method of class |
|
|
|
|
known_classes (dict[str, ClassInfo]): Mapping between string class |
|
|
|
|
identifier and ClassInfo struct. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
Optional[ClassInfo]: class info struct if the provided argument type |
|
|
|
|
refers to a known C++ class, None otherwise. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
possible_classes = tuple(filter(lambda cls: cls.endswith(argument_type), known_classes)) |
|
|
|
|
# If argument type is not a known class - just skip it |
|
|
|
|
if not possible_classes: |
|
|
|
|
return None |
|
|
|
|
if len(possible_classes) == 1: |
|
|
|
|
return known_classes[possible_classes[0]] |
|
|
|
|
|
|
|
|
|
# If there is more than 1 matched class, try to select the most probable one |
|
|
|
|
# Look for a matched class name in different scope, starting from the |
|
|
|
|
# narrowest one |
|
|
|
|
|
|
|
|
|
# First try to find argument inside class scope of the function (if any) |
|
|
|
|
if function_class_name: |
|
|
|
|
type_to_match = function_class_name + '_' + argument_type |
|
|
|
|
if type_to_match in possible_classes: |
|
|
|
|
return known_classes[type_to_match] |
|
|
|
|
else: |
|
|
|
|
type_to_match = argument_type |
|
|
|
|
|
|
|
|
|
# Trying to find argument type in the namespace of the function |
|
|
|
|
type_to_match = '{}_{}'.format( |
|
|
|
|
function_namespace.lstrip('cv.').replace('.', '_'), type_to_match |
|
|
|
|
) |
|
|
|
|
if type_to_match in possible_classes: |
|
|
|
|
return known_classes[type_to_match] |
|
|
|
|
|
|
|
|
|
# Try to find argument name as is |
|
|
|
|
if argument_type in possible_classes: |
|
|
|
|
return known_classes[argument_type] |
|
|
|
|
|
|
|
|
|
# NOTE: parser is broken - some classes might not be visible, depending on |
|
|
|
|
# the order of parsed headers. |
|
|
|
|
# print("[WARNING] Can't select an appropriate class for argument: '", |
|
|
|
|
# argument_type, "'. Possible matches: '", possible_classes, "'") |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FuncVariant(object): |
|
|
|
|
def __init__(self, classname, name, decl, isconstructor, isphantom=False): |
|
|
|
|
self.classname = classname |
|
|
|
|
def __init__(self, namespace, classname, name, decl, isconstructor, known_classes, isphantom=False): |
|
|
|
|
self.name = self.wname = name |
|
|
|
|
self.isconstructor = isconstructor |
|
|
|
|
self.isphantom = isphantom |
|
|
|
@ -476,8 +550,14 @@ class FuncVariant(object): |
|
|
|
|
self.rettype = "" |
|
|
|
|
self.args = [] |
|
|
|
|
self.array_counters = {} |
|
|
|
|
for a in decl[3]: |
|
|
|
|
ainfo = ArgInfo(a) |
|
|
|
|
for arg_decl in decl[3]: |
|
|
|
|
assert len(arg_decl) == 4, \ |
|
|
|
|
'ArgInfo contract is violated. Arg declaration should contain:' \ |
|
|
|
|
'"arg_type", "name", "default_value", "modifiers". '\ |
|
|
|
|
'Got tuple: {}'.format(arg_decl) |
|
|
|
|
|
|
|
|
|
ainfo = ArgInfo(atype=arg_decl[0], name=arg_decl[1], |
|
|
|
|
default_value=arg_decl[2], modifiers=arg_decl[3]) |
|
|
|
|
if ainfo.isarray and not ainfo.arraycvt: |
|
|
|
|
c = ainfo.arraylen |
|
|
|
|
c_arrlist = self.array_counters.get(c, []) |
|
|
|
@ -486,9 +566,9 @@ class FuncVariant(object): |
|
|
|
|
else: |
|
|
|
|
self.array_counters[c] = [ainfo.name] |
|
|
|
|
self.args.append(ainfo) |
|
|
|
|
self.init_pyproto() |
|
|
|
|
self.init_pyproto(namespace, classname, known_classes) |
|
|
|
|
|
|
|
|
|
def init_pyproto(self): |
|
|
|
|
def init_pyproto(self, namespace, classname, known_classes): |
|
|
|
|
# string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g. |
|
|
|
|
# "src1, src2[, dst[, mask]]" for cv.add |
|
|
|
|
argstr = "" |
|
|
|
@ -510,12 +590,44 @@ class FuncVariant(object): |
|
|
|
|
outlist = [] |
|
|
|
|
|
|
|
|
|
firstoptarg = 1000000 |
|
|
|
|
argno = -1 |
|
|
|
|
for a in self.args: |
|
|
|
|
argno += 1 |
|
|
|
|
|
|
|
|
|
# Check if there is params structure in arguments |
|
|
|
|
arguments = [] |
|
|
|
|
for arg in self.args: |
|
|
|
|
arg_class_info = find_argument_class_info( |
|
|
|
|
arg.tp, namespace, classname, known_classes |
|
|
|
|
) |
|
|
|
|
# If argument refers to the 'named arguments' structure - instead of |
|
|
|
|
# the argument put its properties |
|
|
|
|
if arg_class_info is not None and arg_class_info.is_parameters: |
|
|
|
|
for prop in arg_class_info.props: |
|
|
|
|
# Convert property to ArgIfno and mark that argument is |
|
|
|
|
# a part of the parameters structure: |
|
|
|
|
arguments.append( |
|
|
|
|
ArgInfo(prop.tp, prop.name, prop.default_value, |
|
|
|
|
enclosing_arg=arg) |
|
|
|
|
) |
|
|
|
|
else: |
|
|
|
|
arguments.append(arg) |
|
|
|
|
# Prevent names duplication after named arguments are merged |
|
|
|
|
# to the main arguments list |
|
|
|
|
argument_names = tuple(arg.name for arg in arguments) |
|
|
|
|
assert len(set(argument_names)) == len(argument_names), \ |
|
|
|
|
"Duplicate arguments with names '{}' in function '{}'. "\ |
|
|
|
|
"Please, check named arguments used in function interface".format( |
|
|
|
|
argument_names, self.name |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
self.args = arguments |
|
|
|
|
|
|
|
|
|
for argno, a in enumerate(self.args): |
|
|
|
|
if a.name in self.array_counters: |
|
|
|
|
continue |
|
|
|
|
assert not a.tp in forbidden_arg_types, 'Forbidden type "{}" for argument "{}" in "{}" ("{}")'.format(a.tp, a.name, self.name, self.classname) |
|
|
|
|
assert a.tp not in forbidden_arg_types, \ |
|
|
|
|
'Forbidden type "{}" for argument "{}" in "{}" ("{}")'.format( |
|
|
|
|
a.tp, a.name, self.name, self.classname |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
if a.tp in ignored_arg_types: |
|
|
|
|
continue |
|
|
|
|
if a.returnarg: |
|
|
|
@ -542,7 +654,7 @@ class FuncVariant(object): |
|
|
|
|
firstoptarg = min(firstoptarg, len(arglist)) |
|
|
|
|
|
|
|
|
|
noptargs = len(arglist) - firstoptarg |
|
|
|
|
argnamelist = [aname for aname, argno in arglist] |
|
|
|
|
argnamelist = [self.args[argno].export_name for _, argno in arglist] |
|
|
|
|
argstr = ", ".join(argnamelist[:firstoptarg]) |
|
|
|
|
argstr = "[, ".join([argstr] + argnamelist[firstoptarg:]) |
|
|
|
|
argstr += "]" * noptargs |
|
|
|
@ -552,9 +664,8 @@ class FuncVariant(object): |
|
|
|
|
assert outlist == [] |
|
|
|
|
outlist = [("self", -1)] |
|
|
|
|
if self.isconstructor: |
|
|
|
|
classname = self.classname |
|
|
|
|
if classname.startswith("Cv"): |
|
|
|
|
classname=classname[2:] |
|
|
|
|
classname = classname[2:] |
|
|
|
|
outstr = "<%s object>" % (classname,) |
|
|
|
|
elif outlist: |
|
|
|
|
outstr = ", ".join([o[0] for o in outlist]) |
|
|
|
@ -566,9 +677,9 @@ class FuncVariant(object): |
|
|
|
|
self.py_prototype = "%s(%s) -> %s" % (self.wname, argstr, outstr) |
|
|
|
|
self.py_noptargs = noptargs |
|
|
|
|
self.py_arglist = arglist |
|
|
|
|
for aname, argno in arglist: |
|
|
|
|
for _, argno in arglist: |
|
|
|
|
self.args[argno].py_inputarg = True |
|
|
|
|
for aname, argno in outlist: |
|
|
|
|
for _, argno in outlist: |
|
|
|
|
if argno >= 0: |
|
|
|
|
self.args[argno].py_outputarg = True |
|
|
|
|
self.py_outlist = outlist |
|
|
|
@ -584,8 +695,11 @@ class FuncInfo(object): |
|
|
|
|
self.is_static = is_static |
|
|
|
|
self.variants = [] |
|
|
|
|
|
|
|
|
|
def add_variant(self, decl, isphantom=False): |
|
|
|
|
self.variants.append(FuncVariant(self.classname, self.name, decl, self.isconstructor, isphantom)) |
|
|
|
|
def add_variant(self, decl, known_classes, isphantom=False): |
|
|
|
|
self.variants.append( |
|
|
|
|
FuncVariant(self.namespace, self.classname, self.name, decl, |
|
|
|
|
self.isconstructor, known_classes, isphantom) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
def get_wrapper_name(self): |
|
|
|
|
name = self.name |
|
|
|
@ -698,6 +812,7 @@ class FuncInfo(object): |
|
|
|
|
# add necessary conversions from Python objects to code_cvt_list, |
|
|
|
|
# form the function/method call, |
|
|
|
|
# for the list of type mappings |
|
|
|
|
instantiated_args = set() |
|
|
|
|
for a in v.args: |
|
|
|
|
if a.tp in ignored_arg_types: |
|
|
|
|
defval = a.defval |
|
|
|
@ -738,17 +853,29 @@ class FuncInfo(object): |
|
|
|
|
arg_type_info = ArgTypeInfo(tp, FormatStrings.object, defval0, True) |
|
|
|
|
|
|
|
|
|
parse_name = a.name |
|
|
|
|
if a.py_inputarg: |
|
|
|
|
if arg_type_info.strict_conversion: |
|
|
|
|
code_decl += " PyObject* pyobj_%s = NULL;\n" % (a.name,) |
|
|
|
|
parse_name = "pyobj_" + a.name |
|
|
|
|
if a.tp == 'char': |
|
|
|
|
code_cvt_list.append("convert_to_char(pyobj_%s, &%s, %s)" % (a.name, a.name, a.crepr())) |
|
|
|
|
else: |
|
|
|
|
code_cvt_list.append("pyopencv_to_safe(pyobj_%s, %s, %s)" % (a.name, a.name, a.crepr())) |
|
|
|
|
if a.py_inputarg and arg_type_info.strict_conversion: |
|
|
|
|
parse_name = "pyobj_" + a.full_name.replace('.', '_') |
|
|
|
|
code_decl += " PyObject* %s = NULL;\n" % (parse_name,) |
|
|
|
|
if a.tp == 'char': |
|
|
|
|
code_cvt_list.append("convert_to_char(%s, &%s, %s)" % (parse_name, a.full_name, a.crepr())) |
|
|
|
|
else: |
|
|
|
|
code_cvt_list.append("pyopencv_to_safe(%s, %s, %s)" % (parse_name, a.full_name, a.crepr())) |
|
|
|
|
|
|
|
|
|
all_cargs.append([arg_type_info, parse_name]) |
|
|
|
|
|
|
|
|
|
# Argument is actually a part of the named arguments structure, |
|
|
|
|
# but it is possible to mimic further processing like it is normal arg |
|
|
|
|
if a.enclosing_arg: |
|
|
|
|
a = a.enclosing_arg |
|
|
|
|
arg_type_info = ArgTypeInfo(a.tp, FormatStrings.object, |
|
|
|
|
default_value=a.defval, |
|
|
|
|
strict_conversion=True) |
|
|
|
|
# Skip further actions if enclosing argument is already instantiated |
|
|
|
|
# by its another field |
|
|
|
|
if a.name in instantiated_args: |
|
|
|
|
continue |
|
|
|
|
instantiated_args.add(a.name) |
|
|
|
|
|
|
|
|
|
defval = a.defval |
|
|
|
|
if not defval: |
|
|
|
|
defval = arg_type_info.default_value |
|
|
|
@ -773,9 +900,9 @@ class FuncInfo(object): |
|
|
|
|
code_args += ", " |
|
|
|
|
|
|
|
|
|
if a.isrvalueref: |
|
|
|
|
a.name = 'std::move(' + a.name + ')' |
|
|
|
|
|
|
|
|
|
code_args += amp + a.name |
|
|
|
|
code_args += amp + 'std::move(' + a.name + ')' |
|
|
|
|
else: |
|
|
|
|
code_args += amp + a.name |
|
|
|
|
|
|
|
|
|
code_args += ")" |
|
|
|
|
|
|
|
|
@ -821,7 +948,7 @@ class FuncInfo(object): |
|
|
|
|
# form the format spec for PyArg_ParseTupleAndKeywords |
|
|
|
|
fmtspec = "".join([ |
|
|
|
|
get_type_format_string(all_cargs[argno][0]) |
|
|
|
|
for aname, argno in v.py_arglist |
|
|
|
|
for _, argno in v.py_arglist |
|
|
|
|
]) |
|
|
|
|
if v.py_noptargs > 0: |
|
|
|
|
fmtspec = fmtspec[:-v.py_noptargs] + "|" + fmtspec[-v.py_noptargs:] |
|
|
|
@ -832,10 +959,10 @@ class FuncInfo(object): |
|
|
|
|
# - calls PyArg_ParseTupleAndKeywords |
|
|
|
|
# - converts complex arguments from PyObject's to native OpenCV types |
|
|
|
|
code_parse = gen_template_parse_args.substitute( |
|
|
|
|
kw_list = ", ".join(['"' + aname + '"' for aname, argno in v.py_arglist]), |
|
|
|
|
fmtspec = fmtspec, |
|
|
|
|
parse_arglist = ", ".join(["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]), |
|
|
|
|
code_cvt = " &&\n ".join(code_cvt_list)) |
|
|
|
|
kw_list=", ".join(['"' + v.args[argno].export_name + '"' for _, argno in v.py_arglist]), |
|
|
|
|
fmtspec=fmtspec, |
|
|
|
|
parse_arglist=", ".join(["&" + all_cargs[argno][1] for _, argno in v.py_arglist]), |
|
|
|
|
code_cvt=" &&\n ".join(code_cvt_list)) |
|
|
|
|
else: |
|
|
|
|
code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))" |
|
|
|
|
|
|
|
|
@ -1036,7 +1163,7 @@ class PythonWrapperGenerator(object): |
|
|
|
|
# Add it as a method to the class |
|
|
|
|
func_map = self.classes[classname].methods |
|
|
|
|
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static)) |
|
|
|
|
func.add_variant(decl, isphantom) |
|
|
|
|
func.add_variant(decl, self.classes, isphantom) |
|
|
|
|
|
|
|
|
|
# Add it as global function |
|
|
|
|
g_name = "_".join(classes+[name]) |
|
|
|
@ -1053,10 +1180,10 @@ class PythonWrapperGenerator(object): |
|
|
|
|
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs |
|
|
|
|
# Exports static function with internal name (backward compatibility) |
|
|
|
|
func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace_str, False)) |
|
|
|
|
func.add_variant(decl, isphantom) |
|
|
|
|
func.add_variant(decl, self.classes, isphantom) |
|
|
|
|
if g_wname != g_name: # TODO OpenCV 5.0 |
|
|
|
|
wfunc = func_map.setdefault(g_wname, FuncInfo("", g_wname, cname, isconstructor, namespace_str, False)) |
|
|
|
|
wfunc.add_variant(decl, isphantom) |
|
|
|
|
wfunc.add_variant(decl, self.classes, isphantom) |
|
|
|
|
else: |
|
|
|
|
if classname and not isconstructor: |
|
|
|
|
if not isphantom: |
|
|
|
@ -1066,7 +1193,7 @@ class PythonWrapperGenerator(object): |
|
|
|
|
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs |
|
|
|
|
|
|
|
|
|
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static)) |
|
|
|
|
func.add_variant(decl, isphantom) |
|
|
|
|
func.add_variant(decl, self.classes, isphantom) |
|
|
|
|
|
|
|
|
|
if classname and isconstructor: |
|
|
|
|
self.classes[classname].constructor = func |
|
|
|
|