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

feature: submodule or a class scope for exported classes

All classes are registered in the scope that corresponds to C++
namespace or exported class.

Example:
`cv::ml::Boost` is exported as `cv.ml.Boost`
`cv::SimpleBlobDetector::Params` is exported as
`cv.SimpleBlobDetector.Params`

For backward compatibility all classes are registered in the global
module with their mangling name containing scope information.
Example:
`cv::ml::Boost` has `cv.ml_Boost` alias to `cv.ml.Boost` type
This commit is contained in:
Vadim Levin
2022-01-20 15:21:47 +03:00
parent 5cc27fd3b5
commit ccebbbc0ac
5 changed files with 484 additions and 93 deletions
+82 -28
View File
@@ -243,10 +243,20 @@ class ClassProp(object):
self.readonly = False
class ClassInfo(object):
def __init__(self, name, decl=None):
def __init__(self, name, decl=None, codegen=None):
# Scope name can be a module or other class e.g. cv::SimpleBlobDetector::Params
scope_name, self.original_name = name.rsplit(".", 1)
# In case scope refer the outer class exported with different name
if codegen:
scope_name = codegen.get_export_scope_name(scope_name)
self.scope_name = re.sub(r"^cv\.?", "", scope_name)
self.export_name = self.original_name
self.class_id = normalize_class_name(name)
self.cname = name.replace(".", "::")
self.name = self.wname = normalize_class_name(name)
self.sname = name[name.rfind('.') + 1:]
self.ismap = False
self.issimple = False
self.isalgorithm = False
@@ -261,7 +271,7 @@ class ClassInfo(object):
if decl:
bases = decl[1].split()[1:]
if len(bases) > 1:
print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.name,))
print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.cname,))
print(" Bases: ", " ".join(bases))
print(" Only the first base class will be used")
#return sys.exit(-1)
@@ -275,22 +285,47 @@ class ClassInfo(object):
for m in decl[2]:
if m.startswith("="):
wname = m[1:]
npos = name.rfind('.')
if npos >= 0:
self.wname = normalize_class_name(name[:npos] + '.' + wname)
else:
self.wname = wname
customname = True
# Aliasing only affects the exported class name, not class identifier
self.export_name = m[1:]
elif m == "/Map":
self.ismap = True
elif m == "/Simple":
self.issimple = True
self.props = [ClassProp(p) for p in decl[3]]
if not self.has_export_alias and self.original_name.startswith("Cv"):
self.export_name = self.export_name[2:]
if not customname and self.wname.startswith("Cv"):
self.wname = self.wname[2:]
@property
def wname(self):
if len(self.scope_name) > 0:
return self.scope_name.replace(".", "_") + "_" + self.export_name
return self.export_name
@property
def name(self):
return self.class_id
@property
def full_scope_name(self):
return "cv." + self.scope_name if len(self.scope_name) else "cv"
@property
def full_export_name(self):
return self.full_scope_name + "." + self.export_name
@property
def full_original_name(self):
return self.full_scope_name + "." + self.original_name
@property
def has_export_alias(self):
return self.export_name != self.original_name
def gen_map_code(self, codegen):
all_classes = codegen.classes
code = "static bool pyopencv_to(PyObject* src, %s& dst, const ArgInfo& info)\n{\n PyObject* tmp;\n bool ok;\n" % (self.cname)
@@ -343,9 +378,11 @@ class ClassInfo(object):
methods_code.write(m.gen_code(codegen))
methods_inits.write(m.get_tab_entry())
code = gen_template_type_impl.substitute(name=self.name, wname=self.wname, cname=self.cname,
getset_code=getset_code.getvalue(), getset_inits=getset_inits.getvalue(),
methods_code=methods_code.getvalue(), methods_inits=methods_inits.getvalue())
code = gen_template_type_impl.substitute(name=self.name,
getset_code=getset_code.getvalue(),
getset_inits=getset_inits.getvalue(),
methods_code=methods_code.getvalue(),
methods_inits=methods_inits.getvalue())
return code
@@ -359,13 +396,15 @@ class ClassInfo(object):
if self.constructor is not None:
constructor_name = self.constructor.get_wrapper_name()
return "CVPY_TYPE({}, {}, {}, {}, {}, {});\n".format(
self.wname,
self.name,
return 'CVPY_TYPE({}, {}, {}, {}, {}, {}, "{}");\n'.format(
self.export_name,
self.class_id,
self.cname if self.issimple else "Ptr<{}>".format(self.cname),
self.sname if self.issimple else "Ptr",
self.original_name if self.issimple else "Ptr",
baseptr,
constructor_name
constructor_name,
# Leading dot is required to provide correct class naming
"." + self.scope_name if len(self.scope_name) > 0 else self.scope_name
)
@@ -815,12 +854,12 @@ class FuncInfo(object):
classinfo = all_classes[self.classname]
#if dump: pprint(vars(classinfo))
if self.isconstructor:
py_name = 'cv.' + classinfo.wname
elif self.is_static:
py_name = '.'.join([self.namespace, classinfo.sname + '_' + self.variants[0].wname])
py_name = classinfo.full_export_name
else:
py_name = classinfo.full_export_name + "." + self.variants[0].wname
if not self.is_static:
cname = classinfo.cname + '::' + cname
py_name = 'cv.' + classinfo.wname + '.' + self.variants[0].wname
else:
py_name = '.'.join([self.namespace, self.variants[0].wname])
#if dump: print(cname + " => " + py_name)
@@ -862,7 +901,7 @@ class PythonWrapperGenerator(object):
self.class_idx = 0
def add_class(self, stype, name, decl):
classinfo = ClassInfo(name, decl)
classinfo = ClassInfo(name, decl, self)
classinfo.decl_idx = self.class_idx
self.class_idx += 1
@@ -872,16 +911,30 @@ class PythonWrapperGenerator(object):
sys.exit(-1)
self.classes[classinfo.name] = classinfo
# Add Class to json file.
namespace, classes, name = self.split_decl_name(name)
namespace, _, _ = self.split_decl_name(name)
namespace = '.'.join(namespace)
name = '_'.join(classes+[name])
# Registering a namespace if it is not already handled or
# doesn't have anything except classes defined in it
self.namespaces.setdefault(namespace, Namespace())
py_name = 'cv.' + classinfo.wname # use wrapper name
# Add Class to json file.
py_name = classinfo.full_export_name # use wrapper name
py_signatures = self.py_signatures.setdefault(classinfo.cname, [])
py_signatures.append(dict(name=py_name))
#print('class: ' + classinfo.cname + " => " + py_name)
def get_export_scope_name(self, original_scope_name):
# Outer classes should be registered before their content - inner classes in this case
class_scope = self.classes.get(normalize_class_name(original_scope_name), None)
if class_scope:
return class_scope.full_export_name
# Otherwise it is a namespace.
# If something is messed up at this point - it will be revelead during
# library import
return original_scope_name
def split_decl_name(self, name):
chunks = name.split('.')
namespace = chunks[:-1]
@@ -971,6 +1024,7 @@ class PythonWrapperGenerator(object):
w_classes.append(w_classname)
g_wname = "_".join(w_classes+[name])
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)
if g_wname != g_name: # TODO OpenCV 5.0