mirror of
https://github.com/opencv/opencv.git
synced 2025-06-13 04:52:53 +08:00
Merge pull request #23705 from asmorkalov:as/cxx-named-arguments
Re-implement named parameters bindings for Python #23705 Reverted named argument handling from #19156. Ported new solution from #23224 The port is required to harmonize 4.x -> 5.x merges. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
67a3d35b4e
commit
d24ffe9a65
@ -243,6 +243,33 @@ struct CV_EXPORTS_W_SIMPLE ClassWithKeywordProperties {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CV_EXPORTS_W_PARAMS FunctionParams
|
||||||
|
{
|
||||||
|
CV_PROP_RW int lambda = -1;
|
||||||
|
CV_PROP_RW float sigma = 0.0f;
|
||||||
|
|
||||||
|
FunctionParams& setLambda(int value) CV_NOEXCEPT
|
||||||
|
{
|
||||||
|
lambda = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionParams& setSigma(float value) CV_NOEXCEPT
|
||||||
|
{
|
||||||
|
sigma = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CV_WRAP static inline String
|
||||||
|
copyMatAndDumpNamedArguments(InputArray src, OutputArray dst,
|
||||||
|
const FunctionParams& params = FunctionParams())
|
||||||
|
{
|
||||||
|
src.copyTo(dst);
|
||||||
|
return format("lambda=%d, sigma=%.1f", params.lambda,
|
||||||
|
params.sigma);
|
||||||
|
}
|
||||||
|
|
||||||
namespace nested {
|
namespace nested {
|
||||||
CV_WRAP static inline bool testEchoBooleanFunction(bool flag) {
|
CV_WRAP static inline bool testEchoBooleanFunction(bool flag) {
|
||||||
return flag;
|
return flag;
|
||||||
|
@ -241,7 +241,7 @@ class ClassProp(object):
|
|||||||
def __init__(self, decl):
|
def __init__(self, decl):
|
||||||
self.tp = decl[0].replace("*", "_ptr")
|
self.tp = decl[0].replace("*", "_ptr")
|
||||||
self.name = decl[1]
|
self.name = decl[1]
|
||||||
self.defval = decl[2]
|
self.default_value = decl[2]
|
||||||
self.readonly = True
|
self.readonly = True
|
||||||
if "/RW" in decl[3]:
|
if "/RW" in decl[3]:
|
||||||
self.readonly = False
|
self.readonly = False
|
||||||
@ -269,9 +269,9 @@ class ClassInfo(object):
|
|||||||
|
|
||||||
self.cname = name.replace(".", "::")
|
self.cname = name.replace(".", "::")
|
||||||
self.ismap = False
|
self.ismap = False
|
||||||
|
self.is_parameters = False
|
||||||
self.issimple = False
|
self.issimple = False
|
||||||
self.isalgorithm = False
|
self.isalgorithm = False
|
||||||
self.isparams = False
|
|
||||||
self.methods = {}
|
self.methods = {}
|
||||||
self.props = []
|
self.props = []
|
||||||
self.mappables = []
|
self.mappables = []
|
||||||
@ -303,8 +303,8 @@ class ClassInfo(object):
|
|||||||
elif m == "/Simple":
|
elif m == "/Simple":
|
||||||
self.issimple = True
|
self.issimple = True
|
||||||
elif m == "/Params":
|
elif m == "/Params":
|
||||||
self.isparams = True
|
self.is_parameters = True
|
||||||
self.issimple = True # TODO: rework 'params' generation/handling (see #19156 and #22934)
|
self.issimple = True
|
||||||
self.props = [ClassProp(p) for p in decl[3]]
|
self.props = [ClassProp(p) for p in decl[3]]
|
||||||
|
|
||||||
if not self.has_export_alias and self.original_name.startswith("Cv"):
|
if not self.has_export_alias and self.original_name.startswith("Cv"):
|
||||||
@ -425,52 +425,56 @@ def handle_ptr(tp):
|
|||||||
return tp
|
return tp
|
||||||
|
|
||||||
|
|
||||||
def get_named_params_info(all_classes, args):
|
|
||||||
extra_named_params = []
|
|
||||||
params_arg_name = ""
|
|
||||||
if args:
|
|
||||||
last_arg = args[-1]
|
|
||||||
if ("Params" in last_arg.tp) and (last_arg.tp in all_classes):
|
|
||||||
arg_classinfo = all_classes[last_arg.tp]
|
|
||||||
if arg_classinfo.isparams:
|
|
||||||
params_arg_name = last_arg.name
|
|
||||||
extra_named_params = arg_classinfo.props
|
|
||||||
return (params_arg_name, extra_named_params)
|
|
||||||
|
|
||||||
class ArgInfo(object):
|
class ArgInfo(object):
|
||||||
def __init__(self, arg_tuple):
|
def __init__(self, atype, name, default_value, modifiers=(),
|
||||||
self.tp = handle_ptr(arg_tuple[0])
|
enclosing_arg=None):
|
||||||
self.name = arg_tuple[1]
|
# type: (ArgInfo, str, str, str, tuple[str, ...], ArgInfo | None) -> None
|
||||||
if self.name in python_reserved_keywords:
|
self.tp = handle_ptr(atype)
|
||||||
self.name += "_"
|
self.name = name
|
||||||
self.defval = arg_tuple[2]
|
self.defval = default_value
|
||||||
|
self._modifiers = tuple(modifiers)
|
||||||
self.isarray = False
|
self.isarray = False
|
||||||
self.is_smart_ptr = self.tp.startswith('Ptr<') # FIXIT: handle through modifiers - need to modify parser
|
self.is_smart_ptr = self.tp.startswith('Ptr<') # FIXIT: handle through modifiers - need to modify parser
|
||||||
self.arraylen = 0
|
self.arraylen = 0
|
||||||
self.arraycvt = None
|
self.arraycvt = None
|
||||||
self.inputarg = True
|
for m in self._modifiers:
|
||||||
self.outputarg = False
|
if m.startswith("/A"):
|
||||||
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"):
|
|
||||||
self.isarray = True
|
self.isarray = True
|
||||||
self.arraylen = m[2:].strip()
|
self.arraylen = m[2:].strip()
|
||||||
elif m.startswith("/CA"):
|
elif m.startswith("/CA"):
|
||||||
self.isarray = True
|
self.isarray = True
|
||||||
self.arraycvt = m[2:].strip()
|
self.arraycvt = m[2:].strip()
|
||||||
elif m == "/RRef":
|
|
||||||
self.isrvalueref = True
|
|
||||||
self.py_inputarg = False
|
self.py_inputarg = False
|
||||||
self.py_outputarg = 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):
|
def isbig(self):
|
||||||
return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector")
|
return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector")
|
||||||
@ -479,9 +483,62 @@ class ArgInfo(object):
|
|||||||
return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg)
|
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):
|
class FuncVariant(object):
|
||||||
def __init__(self, all_classes, classname, name, decl, isconstructor, isphantom=False):
|
def __init__(self, namespace, classname, name, decl, isconstructor, known_classes, isphantom=False):
|
||||||
self.classname = classname
|
|
||||||
self.name = self.wname = name
|
self.name = self.wname = name
|
||||||
self.isconstructor = isconstructor
|
self.isconstructor = isconstructor
|
||||||
self.isphantom = isphantom
|
self.isphantom = isphantom
|
||||||
@ -493,8 +550,14 @@ class FuncVariant(object):
|
|||||||
self.rettype = ""
|
self.rettype = ""
|
||||||
self.args = []
|
self.args = []
|
||||||
self.array_counters = {}
|
self.array_counters = {}
|
||||||
for a in decl[3]:
|
for arg_decl in decl[3]:
|
||||||
ainfo = ArgInfo(a)
|
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:
|
if ainfo.isarray and not ainfo.arraycvt:
|
||||||
c = ainfo.arraylen
|
c = ainfo.arraylen
|
||||||
c_arrlist = self.array_counters.get(c, [])
|
c_arrlist = self.array_counters.get(c, [])
|
||||||
@ -503,9 +566,9 @@ class FuncVariant(object):
|
|||||||
else:
|
else:
|
||||||
self.array_counters[c] = [ainfo.name]
|
self.array_counters[c] = [ainfo.name]
|
||||||
self.args.append(ainfo)
|
self.args.append(ainfo)
|
||||||
self.init_pyproto(all_classes)
|
self.init_pyproto(namespace, classname, known_classes)
|
||||||
|
|
||||||
def init_pyproto(self, all_classes):
|
def init_pyproto(self, namespace, classname, known_classes):
|
||||||
# string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g.
|
# string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g.
|
||||||
# "src1, src2[, dst[, mask]]" for cv.add
|
# "src1, src2[, dst[, mask]]" for cv.add
|
||||||
argstr = ""
|
argstr = ""
|
||||||
@ -518,7 +581,6 @@ class FuncVariant(object):
|
|||||||
# become the first optional input parameters of the Python function, and thus they are placed right after
|
# become the first optional input parameters of the Python function, and thus they are placed right after
|
||||||
# non-optional input parameters)
|
# non-optional input parameters)
|
||||||
arglist = []
|
arglist = []
|
||||||
proto_arglist = []
|
|
||||||
|
|
||||||
# the list of "heavy" output parameters. Heavy parameters are the parameters
|
# the list of "heavy" output parameters. Heavy parameters are the parameters
|
||||||
# that can be expensive to allocate each time, such as vectors and matrices (see isbig).
|
# that can be expensive to allocate each time, such as vectors and matrices (see isbig).
|
||||||
@ -528,12 +590,44 @@ class FuncVariant(object):
|
|||||||
outlist = []
|
outlist = []
|
||||||
|
|
||||||
firstoptarg = 1000000
|
firstoptarg = 1000000
|
||||||
argno = -1
|
|
||||||
for a in self.args:
|
# Check if there is params structure in arguments
|
||||||
argno += 1
|
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:
|
if a.name in self.array_counters:
|
||||||
continue
|
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:
|
if a.tp in ignored_arg_types:
|
||||||
continue
|
continue
|
||||||
if a.returnarg:
|
if a.returnarg:
|
||||||
@ -545,48 +639,33 @@ class FuncVariant(object):
|
|||||||
continue
|
continue
|
||||||
if not a.defval:
|
if not a.defval:
|
||||||
arglist.append((a.name, argno))
|
arglist.append((a.name, argno))
|
||||||
proto_arglist.append((a.name, argno))
|
|
||||||
else:
|
else:
|
||||||
firstoptarg = min(firstoptarg, len(arglist))
|
firstoptarg = min(firstoptarg, len(arglist))
|
||||||
# if there are some array output parameters before the first default parameter, they
|
# if there are some array output parameters before the first default parameter, they
|
||||||
# are added as optional parameters before the first optional parameter
|
# are added as optional parameters before the first optional parameter
|
||||||
if outarr_list:
|
if outarr_list:
|
||||||
arglist += outarr_list
|
arglist += outarr_list
|
||||||
proto_arglist += [(aname_+"=None",argno_) for (aname_, argno_) in outarr_list]
|
|
||||||
outarr_list = []
|
outarr_list = []
|
||||||
arglist.append((a.name, argno))
|
arglist.append((a.name, argno))
|
||||||
proto_arglist.append((a.name+"="+a.defval, argno))
|
|
||||||
|
|
||||||
# exclude "params" from Python func parameters ==>
|
|
||||||
params_arg_name, extra_named_params = get_named_params_info(all_classes, self.args)
|
|
||||||
if params_arg_name:
|
|
||||||
arglist = arglist[:-1]
|
|
||||||
proto_arglist = proto_arglist[:-1]
|
|
||||||
|
|
||||||
if outarr_list:
|
if outarr_list:
|
||||||
firstoptarg = min(firstoptarg, len(arglist))
|
firstoptarg = min(firstoptarg, len(arglist))
|
||||||
proto_arglist += [(aname+"=None",argno) for (aname, argno) in outarr_list]
|
|
||||||
arglist += outarr_list
|
arglist += outarr_list
|
||||||
|
|
||||||
firstoptarg = min(firstoptarg, len(arglist))
|
firstoptarg = min(firstoptarg, len(arglist))
|
||||||
|
|
||||||
argnamelist = [aname for aname, argno in proto_arglist]
|
noptargs = len(arglist) - firstoptarg
|
||||||
noptargs = len(argnamelist) - firstoptarg
|
argnamelist = [self.args[argno].export_name for _, argno in arglist]
|
||||||
if params_arg_name:
|
|
||||||
# ==> instead, add the individual parameters one by one
|
|
||||||
argnamelist += ["%s=%s" % (a.name, a.defval) for a in extra_named_params]
|
|
||||||
argstr = ", ".join(argnamelist[:firstoptarg])
|
argstr = ", ".join(argnamelist[:firstoptarg])
|
||||||
argstr += "[, " + (", ".join(argnamelist[firstoptarg:]))
|
argstr = "[, ".join([argstr] + argnamelist[firstoptarg:])
|
||||||
argstr += "]"
|
argstr += "]" * noptargs
|
||||||
if self.rettype:
|
if self.rettype:
|
||||||
outlist = [("retval", -1)] + outlist
|
outlist = [("retval", -1)] + outlist
|
||||||
elif self.isconstructor:
|
elif self.isconstructor:
|
||||||
assert outlist == []
|
assert outlist == []
|
||||||
outlist = [("self", -1)]
|
outlist = [("self", -1)]
|
||||||
if self.isconstructor:
|
if self.isconstructor:
|
||||||
classname = self.classname
|
|
||||||
if classname.startswith("Cv"):
|
if classname.startswith("Cv"):
|
||||||
classname=classname[2:]
|
classname = classname[2:]
|
||||||
outstr = "<%s object>" % (classname,)
|
outstr = "<%s object>" % (classname,)
|
||||||
elif outlist:
|
elif outlist:
|
||||||
outstr = ", ".join([o[0] for o in outlist])
|
outstr = ", ".join([o[0] for o in outlist])
|
||||||
@ -598,9 +677,9 @@ class FuncVariant(object):
|
|||||||
self.py_prototype = "%s(%s) -> %s" % (self.wname, argstr, outstr)
|
self.py_prototype = "%s(%s) -> %s" % (self.wname, argstr, outstr)
|
||||||
self.py_noptargs = noptargs
|
self.py_noptargs = noptargs
|
||||||
self.py_arglist = arglist
|
self.py_arglist = arglist
|
||||||
for aname, argno in arglist:
|
for _, argno in arglist:
|
||||||
self.args[argno].py_inputarg = True
|
self.args[argno].py_inputarg = True
|
||||||
for aname, argno in outlist:
|
for _, argno in outlist:
|
||||||
if argno >= 0:
|
if argno >= 0:
|
||||||
self.args[argno].py_outputarg = True
|
self.args[argno].py_outputarg = True
|
||||||
self.py_outlist = outlist
|
self.py_outlist = outlist
|
||||||
@ -616,8 +695,11 @@ class FuncInfo(object):
|
|||||||
self.is_static = is_static
|
self.is_static = is_static
|
||||||
self.variants = []
|
self.variants = []
|
||||||
|
|
||||||
def add_variant(self, all_classes, decl, isphantom=False):
|
def add_variant(self, decl, known_classes, isphantom=False):
|
||||||
self.variants.append(FuncVariant(all_classes, self.classname, self.name, decl, self.isconstructor, isphantom))
|
self.variants.append(
|
||||||
|
FuncVariant(self.namespace, self.classname, self.name, decl,
|
||||||
|
self.isconstructor, known_classes, isphantom)
|
||||||
|
)
|
||||||
|
|
||||||
def get_wrapper_name(self):
|
def get_wrapper_name(self):
|
||||||
name = self.name
|
name = self.name
|
||||||
@ -673,20 +755,20 @@ class FuncInfo(object):
|
|||||||
# their relevant doxygen comment
|
# their relevant doxygen comment
|
||||||
full_docstring = ""
|
full_docstring = ""
|
||||||
for prototype, body in zip(prototype_list, docstring_list):
|
for prototype, body in zip(prototype_list, docstring_list):
|
||||||
full_docstring += Template("$prototype\n$docstring").substitute(
|
full_docstring += Template("$prototype\n$docstring\n\n\n\n").substitute(
|
||||||
prototype="\"" + prototype.replace("\\", "\\\\").replace("\"", "\\\"") + "\\n\\n\"",
|
prototype=prototype,
|
||||||
docstring="\n".join(
|
docstring='\n'.join(
|
||||||
[" \"" + line.replace("\\", "\\\\").replace("\"", "\\\"") + "\\n\""
|
['. ' + line
|
||||||
for line in body.split('\n')]
|
for line in body.split('\n')]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Escape backslashes, newlines, and double quotes
|
# Escape backslashes, newlines, and double quotes
|
||||||
#full_docstring = full_docstring.strip().replace("\\", "\\\\").replace("\"", "\\\"")
|
full_docstring = full_docstring.strip().replace("\\", "\\\\").replace('\n', '\\n').replace("\"", "\\\"")
|
||||||
# Convert unicode chars to xml representation, but keep as string instead of bytes
|
# Convert unicode chars to xml representation, but keep as string instead of bytes
|
||||||
full_docstring = full_docstring.encode('ascii', errors='xmlcharrefreplace').decode()
|
full_docstring = full_docstring.encode('ascii', errors='xmlcharrefreplace').decode()
|
||||||
|
|
||||||
return Template(' {"$py_funcname", CV_PY_FN_WITH_KW_($wrap_funcname, $flags),\n $py_docstring},\n'
|
return Template(' {"$py_funcname", CV_PY_FN_WITH_KW_($wrap_funcname, $flags),\n "$py_docstring"},\n'
|
||||||
).substitute(py_funcname = self.variants[0].wname, wrap_funcname=self.get_wrapper_name(),
|
).substitute(py_funcname = self.variants[0].wname, wrap_funcname=self.get_wrapper_name(),
|
||||||
flags = 'METH_STATIC' if self.is_static else '0', py_docstring = full_docstring)
|
flags = 'METH_STATIC' if self.is_static else '0', py_docstring = full_docstring)
|
||||||
|
|
||||||
@ -726,12 +808,11 @@ class FuncInfo(object):
|
|||||||
if v.isphantom and ismethod and not self.is_static:
|
if v.isphantom and ismethod and not self.is_static:
|
||||||
code_args += "_self_"
|
code_args += "_self_"
|
||||||
|
|
||||||
params_arg_name, extra_named_params = get_named_params_info(all_classes, v.args)
|
|
||||||
|
|
||||||
# declare all the C function arguments,
|
# declare all the C function arguments,
|
||||||
# add necessary conversions from Python objects to code_cvt_list,
|
# add necessary conversions from Python objects to code_cvt_list,
|
||||||
# form the function/method call,
|
# form the function/method call,
|
||||||
# for the list of type mappings
|
# for the list of type mappings
|
||||||
|
instantiated_args = set()
|
||||||
for a in v.args:
|
for a in v.args:
|
||||||
if a.tp in ignored_arg_types:
|
if a.tp in ignored_arg_types:
|
||||||
defval = a.defval
|
defval = a.defval
|
||||||
@ -772,17 +853,29 @@ class FuncInfo(object):
|
|||||||
arg_type_info = ArgTypeInfo(tp, FormatStrings.object, defval0, True)
|
arg_type_info = ArgTypeInfo(tp, FormatStrings.object, defval0, True)
|
||||||
|
|
||||||
parse_name = a.name
|
parse_name = a.name
|
||||||
if a.py_inputarg:
|
if a.py_inputarg and arg_type_info.strict_conversion:
|
||||||
if arg_type_info.strict_conversion:
|
parse_name = "pyobj_" + a.full_name.replace('.', '_')
|
||||||
code_decl += " PyObject* pyobj_%s = NULL;\n" % (a.name,)
|
code_decl += " PyObject* %s = NULL;\n" % (parse_name,)
|
||||||
parse_name = "pyobj_" + a.name
|
if a.tp == 'char':
|
||||||
if a.tp == 'char':
|
code_cvt_list.append("convert_to_char(%s, &%s, %s)" % (parse_name, a.full_name, a.crepr()))
|
||||||
code_cvt_list.append("convert_to_char(pyobj_%s, &%s, %s)" % (a.name, a.name, a.crepr()))
|
else:
|
||||||
else:
|
code_cvt_list.append("pyopencv_to_safe(%s, %s, %s)" % (parse_name, a.full_name, a.crepr()))
|
||||||
code_cvt_list.append("pyopencv_to_safe(pyobj_%s, %s, %s)" % (a.name, a.name, a.crepr()))
|
|
||||||
|
|
||||||
all_cargs.append([arg_type_info, parse_name])
|
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
|
defval = a.defval
|
||||||
if not defval:
|
if not defval:
|
||||||
defval = arg_type_info.default_value
|
defval = arg_type_info.default_value
|
||||||
@ -807,18 +900,9 @@ class FuncInfo(object):
|
|||||||
code_args += ", "
|
code_args += ", "
|
||||||
|
|
||||||
if a.isrvalueref:
|
if a.isrvalueref:
|
||||||
a.name = 'std::move(' + a.name + ')'
|
code_args += amp + 'std::move(' + a.name + ')'
|
||||||
|
else:
|
||||||
code_args += amp + a.name
|
code_args += amp + a.name
|
||||||
|
|
||||||
if params_arg_name:
|
|
||||||
for a in extra_named_params:
|
|
||||||
code_decl += " PyObject* pyobj_kw_%s = NULL;\n" % (a.name,)
|
|
||||||
ainfo = "ArgInfo(\"params.%s\", %d)" % (a.name, 0)
|
|
||||||
if a.tp == 'char':
|
|
||||||
code_cvt_list.append("convert_to_char(pyobj_kw_%s, ¶ms.%s, %s)" % (a.name, a.name, ainfo))
|
|
||||||
else:
|
|
||||||
code_cvt_list.append("pyopencv_to(pyobj_kw_%s, params.%s, %s)" % (a.name, a.name, ainfo))
|
|
||||||
|
|
||||||
code_args += ")"
|
code_args += ")"
|
||||||
|
|
||||||
@ -864,27 +948,22 @@ class FuncInfo(object):
|
|||||||
# form the format spec for PyArg_ParseTupleAndKeywords
|
# form the format spec for PyArg_ParseTupleAndKeywords
|
||||||
fmtspec = "".join([
|
fmtspec = "".join([
|
||||||
get_type_format_string(all_cargs[argno][0])
|
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:
|
if v.py_noptargs > 0:
|
||||||
fmtspec = fmtspec[:-v.py_noptargs] + "|" + fmtspec[-v.py_noptargs:]
|
fmtspec = fmtspec[:-v.py_noptargs] + "|" + fmtspec[-v.py_noptargs:]
|
||||||
fmtspec += "O" * len(extra_named_params)
|
|
||||||
fmtspec += ":" + fullname
|
fmtspec += ":" + fullname
|
||||||
|
|
||||||
# form the argument parse code that:
|
# form the argument parse code that:
|
||||||
# - declares the list of keyword parameters
|
# - declares the list of keyword parameters
|
||||||
# - calls PyArg_ParseTupleAndKeywords
|
# - calls PyArg_ParseTupleAndKeywords
|
||||||
# - converts complex arguments from PyObject's to native OpenCV types
|
# - converts complex arguments from PyObject's to native OpenCV types
|
||||||
kw_list = ['"' + aname + '"' for aname, argno in v.py_arglist]
|
|
||||||
kw_list += ['"' + a.name + '"' for a in extra_named_params]
|
|
||||||
parse_arglist = ["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]
|
|
||||||
parse_arglist += ["&pyobj_kw_" + a.name for a in extra_named_params]
|
|
||||||
|
|
||||||
code_parse = gen_template_parse_args.substitute(
|
code_parse = gen_template_parse_args.substitute(
|
||||||
kw_list = ", ".join(kw_list),
|
kw_list=", ".join(['"' + v.args[argno].export_name + '"' for _, argno in v.py_arglist]),
|
||||||
fmtspec = fmtspec,
|
fmtspec=fmtspec,
|
||||||
parse_arglist = ", ".join(parse_arglist),
|
parse_arglist=", ".join(["&" + all_cargs[argno][1] for _, argno in v.py_arglist]),
|
||||||
code_cvt = " &&\n ".join(code_cvt_list))
|
code_cvt=" &&\n ".join(code_cvt_list))
|
||||||
else:
|
else:
|
||||||
code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))"
|
code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))"
|
||||||
|
|
||||||
@ -1085,7 +1164,7 @@ class PythonWrapperGenerator(object):
|
|||||||
# Add it as a method to the class
|
# Add it as a method to the class
|
||||||
func_map = self.classes[classname].methods
|
func_map = self.classes[classname].methods
|
||||||
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
|
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
|
||||||
func.add_variant(self.classes, decl, isphantom)
|
func.add_variant(decl, self.classes, isphantom)
|
||||||
|
|
||||||
# Add it as global function
|
# Add it as global function
|
||||||
g_name = "_".join(classes+[name])
|
g_name = "_".join(classes+[name])
|
||||||
@ -1102,10 +1181,10 @@ class PythonWrapperGenerator(object):
|
|||||||
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
|
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
|
||||||
# Exports static function with internal name (backward compatibility)
|
# Exports static function with internal name (backward compatibility)
|
||||||
func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace_str, False))
|
func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace_str, False))
|
||||||
func.add_variant(self.classes, decl, isphantom)
|
func.add_variant(decl, self.classes, isphantom)
|
||||||
if g_wname != g_name: # TODO OpenCV 5.0
|
if g_wname != g_name: # TODO OpenCV 5.0
|
||||||
wfunc = func_map.setdefault(g_wname, FuncInfo("", g_wname, cname, isconstructor, namespace_str, False))
|
wfunc = func_map.setdefault(g_wname, FuncInfo("", g_wname, cname, isconstructor, namespace_str, False))
|
||||||
wfunc.add_variant(self.classes, decl, isphantom)
|
wfunc.add_variant(decl, self.classes, isphantom)
|
||||||
else:
|
else:
|
||||||
if classname and not isconstructor:
|
if classname and not isconstructor:
|
||||||
if not isphantom:
|
if not isphantom:
|
||||||
@ -1115,7 +1194,7 @@ class PythonWrapperGenerator(object):
|
|||||||
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
|
func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
|
||||||
|
|
||||||
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
|
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
|
||||||
func.add_variant(self.classes, decl, isphantom)
|
func.add_variant(decl, self.classes, isphantom)
|
||||||
|
|
||||||
if classname and isconstructor:
|
if classname and isconstructor:
|
||||||
self.classes[classname].constructor = func
|
self.classes[classname].constructor = func
|
||||||
@ -1321,7 +1400,7 @@ class PythonWrapperGenerator(object):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
srcfiles = hdr_parser.opencv_hdr_list
|
srcfiles = hdr_parser.opencv_hdr_list
|
||||||
dstdir = "."
|
dstdir = "/Users/vp/tmp"
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
dstdir = sys.argv[1]
|
dstdir = sys.argv[1]
|
||||||
if len(sys.argv) > 2:
|
if len(sys.argv) > 2:
|
||||||
|
@ -258,13 +258,13 @@ class CppHeaderParser(object):
|
|||||||
if "CV_EXPORTS_W_MAP" in l:
|
if "CV_EXPORTS_W_MAP" in l:
|
||||||
l = l.replace("CV_EXPORTS_W_MAP", "")
|
l = l.replace("CV_EXPORTS_W_MAP", "")
|
||||||
modlist.append("/Map")
|
modlist.append("/Map")
|
||||||
|
if "CV_EXPORTS_W_SIMPLE" in l:
|
||||||
|
l = l.replace("CV_EXPORTS_W_SIMPLE", "")
|
||||||
|
modlist.append("/Simple")
|
||||||
if "CV_EXPORTS_W_PARAMS" in l:
|
if "CV_EXPORTS_W_PARAMS" in l:
|
||||||
l = l.replace("CV_EXPORTS_W_PARAMS", "")
|
l = l.replace("CV_EXPORTS_W_PARAMS", "")
|
||||||
modlist.append("/Map")
|
modlist.append("/Map")
|
||||||
modlist.append("/Params")
|
modlist.append("/Params")
|
||||||
if "CV_EXPORTS_W_SIMPLE" in l:
|
|
||||||
l = l.replace("CV_EXPORTS_W_SIMPLE", "")
|
|
||||||
modlist.append("/Simple")
|
|
||||||
npos = l.find("CV_EXPORTS_AS")
|
npos = l.find("CV_EXPORTS_AS")
|
||||||
if npos < 0:
|
if npos < 0:
|
||||||
npos = l.find('CV_WRAP_AS')
|
npos = l.find('CV_WRAP_AS')
|
||||||
@ -782,12 +782,15 @@ class CppHeaderParser(object):
|
|||||||
var_list = [var_name1] + [i.strip() for i in var_list[1:]]
|
var_list = [var_name1] + [i.strip() for i in var_list[1:]]
|
||||||
|
|
||||||
for v in var_list:
|
for v in var_list:
|
||||||
vv = v.split("=")
|
prop_definition = v.split('=')
|
||||||
vname = vv[0].strip()
|
prop_name = prop_definition[0].strip()
|
||||||
vdefval = ""
|
if len(prop_definition) == 1:
|
||||||
if len(vv) > 1:
|
# default value is not provided
|
||||||
vdefval = vv[1].strip()
|
prop_default_value = ''
|
||||||
class_decl[3].append([var_type, vname, vdefval, var_modlist])
|
else:
|
||||||
|
prop_default_value = prop_definition[-1]
|
||||||
|
class_decl[3].append([var_type, prop_name, prop_default_value,
|
||||||
|
var_modlist])
|
||||||
return stmt_type, "", False, None
|
return stmt_type, "", False, None
|
||||||
|
|
||||||
# something unknown
|
# something unknown
|
||||||
|
@ -738,6 +738,29 @@ class Arguments(NewOpenCVTests):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_named_arguments_without_parameters(self):
|
||||||
|
src = np.ones((5, 5, 3), dtype=np.uint8)
|
||||||
|
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(src)
|
||||||
|
np.testing.assert_equal(src, src_copy)
|
||||||
|
self.assertEqual(arguments_dump, 'lambda=-1, sigma=0.0')
|
||||||
|
|
||||||
|
def test_named_arguments_without_output_argument(self):
|
||||||
|
src = np.zeros((2, 2, 3), dtype=np.uint8)
|
||||||
|
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(
|
||||||
|
src, lambda_=15, sigma=3.5
|
||||||
|
)
|
||||||
|
np.testing.assert_equal(src, src_copy)
|
||||||
|
self.assertEqual(arguments_dump, 'lambda=15, sigma=3.5')
|
||||||
|
|
||||||
|
def test_named_arguments_with_output_argument(self):
|
||||||
|
src = np.zeros((3, 3, 3), dtype=np.uint8)
|
||||||
|
dst = np.ones_like(src)
|
||||||
|
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(
|
||||||
|
src, dst, lambda_=25, sigma=5.5
|
||||||
|
)
|
||||||
|
np.testing.assert_equal(src, src_copy)
|
||||||
|
np.testing.assert_equal(dst, src_copy)
|
||||||
|
self.assertEqual(arguments_dump, 'lambda=25, sigma=5.5')
|
||||||
|
|
||||||
|
|
||||||
class CanUsePurePythonModuleFunction(NewOpenCVTests):
|
class CanUsePurePythonModuleFunction(NewOpenCVTests):
|
||||||
|
Loading…
Reference in New Issue
Block a user