mirror of
https://github.com/opencv/opencv_contrib.git
synced 2025-10-19 11:14:32 +08:00

[GSoC] Julia Bindings Phase 3 * Add Julia Bindings Phase 3 * Code style and warning fixes for Julia bindings * Add copyright notice to .py files * fix typo
532 lines
18 KiB
Python
532 lines
18 KiB
Python
#!/usr/bin/env python
|
|
|
|
# This file is part of OpenCV project.
|
|
# It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
# of this distribution and at http://opencv.org/license.html
|
|
# Copyright (C) 2020 by Archit Rungta
|
|
|
|
|
|
import hdr_parser, sys, re, os
|
|
from string import Template
|
|
from pprint import pprint
|
|
from collections import namedtuple
|
|
import json
|
|
import os, shutil
|
|
from io import StringIO
|
|
|
|
|
|
forbidden_arg_types = ["void*"]
|
|
|
|
ignored_arg_types = ["RNG*"]
|
|
|
|
pass_by_val_types = ["Point*", "Point2f*", "Rect*", "String*", "double*", "float*", "int*"]
|
|
|
|
|
|
def get_char(c):
|
|
if c.isalpha():
|
|
return c
|
|
if ord(c)%52 < 26:
|
|
return chr(ord('a')+ord(c)%26)
|
|
return chr(ord('A')+ord(c)%26)
|
|
|
|
|
|
def get_var(inp):
|
|
out = ''
|
|
for c in inp:
|
|
out = out+get_char(c)
|
|
return out
|
|
|
|
def normalize_name(name):
|
|
return name.replace('.', '::')
|
|
|
|
def normalize_class_name(name):
|
|
_, classes, name = split_decl_name(normalize_name(name))
|
|
return "_".join(classes+[name])
|
|
|
|
def normalize_full_name(name):
|
|
ns, classes, name = split_decl_name(normalize_name(name))
|
|
return "::".join(ns)+'::'+'_'.join(classes+[name])
|
|
|
|
|
|
|
|
def split_decl_name(name):
|
|
chunks = name.split('::')
|
|
namespace = chunks[:-1]
|
|
classes = []
|
|
while namespace and '::'.join(namespace) not in namespaces:
|
|
classes.insert(0, namespace.pop())
|
|
|
|
ns = '::'.join(namespace)
|
|
if ns not in namespaces and ns:
|
|
assert(0)
|
|
|
|
return namespace, classes, chunks[-1]
|
|
|
|
|
|
def handle_cpp_arg(inp):
|
|
def handle_vector(match):
|
|
return handle_cpp_arg("%svector<%s>" % (match.group(1), match.group(2)))
|
|
def handle_ptr(match):
|
|
return handle_cpp_arg("%sPtr<%s>" % (match.group(1), match.group(2)))
|
|
inp = re.sub("(.*)vector_(.*)", handle_vector, inp)
|
|
inp = re.sub("(.*)Ptr_(.*)", handle_ptr, inp)
|
|
|
|
|
|
return inp.replace("String", "string")
|
|
|
|
def get_template_arg(inp):
|
|
inp = inp.replace(' ','').replace('*', '').replace('cv::', '').replace('std::', '')
|
|
def handle_vector(match):
|
|
return get_template_arg("%s" % (match.group(1)))
|
|
def handle_ptr(match):
|
|
return get_template_arg("%s" % (match.group(1)))
|
|
inp = re.sub("vector<(.*)>", handle_vector, inp)
|
|
inp = re.sub("Ptr<(.*)>", handle_ptr, inp)
|
|
ns, cl, n = split_decl_name(inp)
|
|
inp = "::".join(cl+[n])
|
|
# print(inp)
|
|
return inp.replace("String", "string")
|
|
|
|
def registered_tp_search(tp):
|
|
found = False
|
|
if not tp:
|
|
return True
|
|
for tpx in registered_types:
|
|
if re.findall(tpx, tp):
|
|
found = True
|
|
break
|
|
return found
|
|
|
|
namespaces = {}
|
|
type_paths = {}
|
|
enums = {}
|
|
classes = {}
|
|
functions = {}
|
|
registered_types = ["int", "Size.*", "Rect.*", "Scalar", "RotatedRect", "Point.*", "explicit", "string", "bool", "uchar",
|
|
"Vec.*", "float", "double", "char", "Mat", "size_t", "RNG", "DescriptorExtractor", "FeatureDetector", "TermCriteria"]
|
|
|
|
class ClassProp(object):
|
|
"""
|
|
Helper class to store field information(type, name and flags) of classes and structs
|
|
"""
|
|
def __init__(self, decl):
|
|
self.tp = decl[0]
|
|
self.name = decl[1]
|
|
self.readonly = True
|
|
if "/RW" in decl[3]:
|
|
self.readonly = False
|
|
|
|
class ClassInfo(object):
|
|
def __init__(self, name, decl=None):
|
|
self.name = name
|
|
self.mapped_name = normalize_class_name(name)
|
|
self.ismap = False #CV_EXPORTS_W_MAP
|
|
self.isalgorithm = False #if class inherits from cv::Algorithm
|
|
self.methods = {} #Dictionary of methods
|
|
self.props = [] #Collection of ClassProp associated with this class
|
|
self.base = None #name of base class if current class inherits another class
|
|
self.constructors = [] #Array of constructors for this class
|
|
self.add_decl(decl)
|
|
classes[name] = self
|
|
|
|
def add_decl(self, decl):
|
|
if decl:
|
|
# print(decl)
|
|
bases = decl[1].split(',')
|
|
if len(bases[0].split()) > 1:
|
|
bases[0] = bases[0].split()[1]
|
|
|
|
bases = [x.replace(' ','') for x in bases]
|
|
# print(bases)
|
|
if len(bases) > 1:
|
|
# Clear the set a bit
|
|
bases = list(set(bases))
|
|
bases.remove('cv::class')
|
|
bases_clear = []
|
|
for bb in bases:
|
|
if self.name not in bb:
|
|
bases_clear.append(bb)
|
|
bases = bases_clear
|
|
if len(bases) > 1:
|
|
print("Note: Class %s has more than 1 base class (not supported by CxxWrap)" % (self.name,))
|
|
print(" Bases: ", " ".join(bases))
|
|
print(" Only the first base class will be used")
|
|
if len(bases) >= 1:
|
|
self.base = bases[0].replace('.', '::')
|
|
if "cv::Algorithm" in bases:
|
|
self.isalgorithm = True
|
|
|
|
for m in decl[2]:
|
|
if m.startswith("="):
|
|
self.mapped_name = m[1:]
|
|
# if m == "/Map":
|
|
# self.ismap = True
|
|
self.props = [ClassProp(p) for p in decl[3]]
|
|
# return code for functions and setters and getters if simple class or functions and map type
|
|
|
|
def get_prop_func_cpp(self, mode, propname):
|
|
return "jlopencv_" + self.mapped_name + "_"+mode+"_"+propname
|
|
|
|
argumentst = []
|
|
default_values = []
|
|
class ArgInfo(object):
|
|
"""
|
|
Helper class to parse and contain information about function arguments
|
|
"""
|
|
|
|
def sec(self, arg_tuple):
|
|
self.isbig = arg_tuple[0] in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector")
|
|
|
|
self.tp = handle_cpp_arg(arg_tuple[0]) #C++ Type of argument
|
|
argumentst.append(self.tp)
|
|
self.name = arg_tuple[1] #Name of argument
|
|
# TODO: Handle default values nicely
|
|
self.default_value = arg_tuple[2] #Default value
|
|
self.inputarg = True #Input argument
|
|
self.outputarg = False #output argument
|
|
self.ref = False
|
|
|
|
for m in arg_tuple[3]:
|
|
if m == "/O":
|
|
self.inputarg = False
|
|
self.outputarg = True
|
|
elif m == "/IO":
|
|
self.inputarg = True
|
|
self.outputarg = True
|
|
elif m == '/Ref':
|
|
self.ref = True
|
|
|
|
if self.tp in pass_by_val_types:
|
|
self.outputarg = True
|
|
|
|
|
|
|
|
def __init__(self, name, tp = None):
|
|
if not tp:
|
|
self.sec(name)
|
|
else:
|
|
self.name = name
|
|
self.tp = tp
|
|
|
|
|
|
class FuncVariant(object):
|
|
"""
|
|
Helper class to parse and contain information about different overloaded versions of same function
|
|
"""
|
|
def __init__(self, classname, name, mapped_name, decl, namespace, istatic=False):
|
|
self.classname = classname
|
|
self.name = name
|
|
self.mapped_name = mapped_name
|
|
|
|
self.isconstructor = name.split('::')[-1]==classname.split('::')[-1]
|
|
self.isstatic = istatic
|
|
self.namespace = namespace
|
|
|
|
self.rettype = decl[4]
|
|
if self.rettype == "void" or not self.rettype:
|
|
self.rettype = ""
|
|
else:
|
|
self.rettype = handle_cpp_arg(self.rettype)
|
|
|
|
self.args = []
|
|
|
|
for ainfo in decl[3]:
|
|
a = ArgInfo(ainfo)
|
|
if a.default_value and ('(' in a.default_value or ':' in a.default_value):
|
|
default_values.append(a.default_value)
|
|
assert not a.tp 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
|
|
|
|
self.args.append(a)
|
|
self.init_proto()
|
|
|
|
if name not in functions:
|
|
functions[name]= []
|
|
functions[name].append(self)
|
|
|
|
if not registered_tp_search(get_template_arg(self.rettype)):
|
|
namespaces[namespace].register_types.append(get_template_arg(self.rettype))
|
|
for arg in self.args:
|
|
if not registered_tp_search(get_template_arg(arg.tp)):
|
|
namespaces[namespace].register_types.append(get_template_arg(arg.tp))
|
|
|
|
|
|
def get_wrapper_name(self):
|
|
"""
|
|
Return wrapping function name
|
|
"""
|
|
name = self.name.replace('::', '_')
|
|
if self.classname:
|
|
classname = self.classname.replace('::', '_') + "_"
|
|
else:
|
|
classname = ""
|
|
return "jlopencv_" + self.namespace.replace('::','_') + '_' + classname + name
|
|
|
|
|
|
def init_proto(self):
|
|
# string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g.
|
|
# "src1, src2[, dst[, mask]]" for cv.add
|
|
prototype = ""
|
|
|
|
inlist = []
|
|
optlist = []
|
|
outlist = []
|
|
deflist = []
|
|
biglist = []
|
|
|
|
# This logic can almost definitely be simplified
|
|
|
|
for a in self.args:
|
|
if a.isbig and not (a.inputarg and not a.default_value):
|
|
optlist.append(a)
|
|
if a.outputarg:
|
|
outlist.append(a)
|
|
if a.inputarg and not a.default_value:
|
|
inlist.append(a)
|
|
elif a.inputarg and a.default_value and not a.isbig:
|
|
optlist.append(a)
|
|
elif not (a.isbig and not (a.inputarg and not a.default_value)):
|
|
deflist.append(a)
|
|
|
|
if self.rettype:
|
|
outlist = [ArgInfo("retval", self.rettype)] + outlist
|
|
|
|
if self.isconstructor:
|
|
assert outlist == [] or outlist[0].tp == "explicit"
|
|
outlist = [ArgInfo("retval", self.classname)]
|
|
|
|
|
|
self.outlist = outlist
|
|
self.optlist = optlist
|
|
self.deflist = deflist
|
|
|
|
self.inlist = inlist
|
|
|
|
self.prototype = prototype
|
|
|
|
class NameSpaceInfo(object):
|
|
def __init__(self, name):
|
|
self.funcs = {}
|
|
self.classes = {} #Dictionary of classname : ClassInfo objects
|
|
self.enums = {}
|
|
self.consts = {}
|
|
self.register_types = []
|
|
self.name = name
|
|
|
|
def add_func(decl):
|
|
"""
|
|
Creates functions based on declaration and add to appropriate classes and/or namespaces
|
|
"""
|
|
decl[0] = decl[0].replace('.', '::')
|
|
namespace, classes, barename = split_decl_name(decl[0])
|
|
name = "::".join(namespace+classes+[barename])
|
|
full_classname = "::".join(namespace + classes)
|
|
classname = "::".join(classes)
|
|
namespace = '::'.join(namespace)
|
|
is_static = False
|
|
isphantom = False
|
|
mapped_name = ''
|
|
|
|
for m in decl[2]:
|
|
if m == "/S":
|
|
is_static = True
|
|
elif m == "/phantom":
|
|
print("phantom not supported yet ")
|
|
return
|
|
elif m.startswith("="):
|
|
mapped_name = m[1:]
|
|
elif m.startswith("/mappable="):
|
|
print("Mappable not supported yet")
|
|
return
|
|
# if m == "/V":
|
|
# print("skipping ", name)
|
|
# return
|
|
|
|
if classname and full_classname not in namespaces[namespace].classes:
|
|
# print("HH1")
|
|
# print(namespace, classname)
|
|
namespaces[namespace].classes[full_classname] = ClassInfo(full_classname)
|
|
assert(0)
|
|
|
|
|
|
if is_static:
|
|
# Add it as global function
|
|
func_map = namespaces[namespace].funcs
|
|
if name not in func_map:
|
|
func_map[name] = []
|
|
if not mapped_name:
|
|
mapped_name = "_".join(classes + [barename])
|
|
func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, True))
|
|
else:
|
|
if classname:
|
|
func = FuncVariant(full_classname, name, barename, decl, namespace, False)
|
|
if func.isconstructor:
|
|
namespaces[namespace].classes[full_classname].constructors.append(func)
|
|
else:
|
|
func_map = namespaces[namespace].classes[full_classname].methods
|
|
if name not in func_map:
|
|
func_map[name] = []
|
|
func_map[name].append(func)
|
|
else:
|
|
func_map = namespaces[namespace].funcs
|
|
if name not in func_map:
|
|
func_map[name] = []
|
|
if not mapped_name:
|
|
mapped_name = barename
|
|
func_map[name].append(FuncVariant("", name, mapped_name, decl, namespace, False))
|
|
|
|
|
|
def add_class(stype, name, decl):
|
|
"""
|
|
Creates class based on name and declaration. Add it to list of classes and to JSON file
|
|
"""
|
|
# print("n", name)
|
|
name = name.replace('.', '::')
|
|
classinfo = ClassInfo(name, decl)
|
|
namespace, classes, barename = split_decl_name(name)
|
|
namespace = '::'.join(namespace)
|
|
|
|
if classinfo.name in classes:
|
|
namespaces[namespace].classes[name].add_decl(decl)
|
|
else:
|
|
namespaces[namespace].classes[name] = classinfo
|
|
|
|
|
|
|
|
def add_const(name, decl, tp = ''):
|
|
name = name.replace('.','::')
|
|
namespace, classes, barename = split_decl_name(name)
|
|
namespace = '::'.join(namespace)
|
|
mapped_name = '_'.join(classes+[barename])
|
|
ns = namespaces[namespace]
|
|
if mapped_name in ns.consts:
|
|
print("Generator error: constant %s (name=%s) already exists" \
|
|
% (name, name))
|
|
sys.exit(-1)
|
|
ns.consts[name] = mapped_name
|
|
|
|
def add_enum(name, decl):
|
|
name = name.replace('.', '::')
|
|
mapped_name = normalize_class_name(name)
|
|
# print(name)
|
|
if mapped_name.endswith("<unnamed>"):
|
|
mapped_name = None
|
|
else:
|
|
enums[name.replace(".", "::")] = mapped_name
|
|
const_decls = decl[3]
|
|
|
|
if mapped_name:
|
|
namespace, classes, name2 = split_decl_name(name)
|
|
namespace = '::'.join(namespace)
|
|
mapped_name = '_'.join(classes+[name2])
|
|
# print(mapped_name)
|
|
namespaces[namespace].enums[name] = (name.replace(".", "::"),mapped_name)
|
|
|
|
for decl in const_decls:
|
|
name = decl[0]
|
|
add_const(name.replace("const ", "", ).strip(), decl, "int")
|
|
|
|
|
|
|
|
def gen_tree(srcfiles):
|
|
parser = hdr_parser.CppHeaderParser(generate_umat_decls=False, generate_gpumat_decls=False)
|
|
|
|
allowed_func_list = []
|
|
|
|
with open("funclist.csv", "r") as f:
|
|
allowed_func_list = f.readlines()
|
|
allowed_func_list = [x[:-1] for x in allowed_func_list]
|
|
|
|
|
|
count = 0
|
|
# step 1: scan the headers and build more descriptive maps of classes, consts, functions
|
|
for hdr in srcfiles:
|
|
decls = parser.parse(hdr)
|
|
for ns in parser.namespaces:
|
|
ns = ns.replace('.', '::')
|
|
if ns not in namespaces:
|
|
namespaces[ns] = NameSpaceInfo(ns)
|
|
count += len(decls)
|
|
if len(decls) == 0:
|
|
continue
|
|
if hdr.find('opencv2/') >= 0: #Avoid including the shadow files
|
|
# code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) )
|
|
pass
|
|
for decl in decls:
|
|
name = decl[0]
|
|
if name.startswith("struct") or name.startswith("class"):
|
|
# class/struct
|
|
p = name.find(" ")
|
|
stype = name[:p]
|
|
name = name[p+1:].strip()
|
|
add_class(stype, name, decl)
|
|
elif name.startswith("const"):
|
|
# constant
|
|
assert(0)
|
|
add_const(name.replace("const ", "").strip(), decl)
|
|
elif name.startswith("enum"):
|
|
# enum
|
|
add_enum(name.rsplit(" ", 1)[1], decl)
|
|
else:
|
|
# function
|
|
if decl[0] in allowed_func_list:
|
|
add_func(decl)
|
|
# step 1.5 check if all base classes exist
|
|
# print(classes)
|
|
for name, classinfo in classes.items():
|
|
if classinfo.base:
|
|
base = classinfo.base
|
|
# print(base)
|
|
if base not in classes:
|
|
print("Generator error: unable to resolve base %s for %s"
|
|
% (classinfo.base, classinfo.name))
|
|
sys.exit(-1)
|
|
base_instance = classes[base]
|
|
classinfo.base = base
|
|
classinfo.isalgorithm |= base_instance.isalgorithm # wrong processing of 'isalgorithm' flag:
|
|
# doesn't work for trees(graphs) with depth > 2
|
|
classes[name] = classinfo
|
|
|
|
# tree-based propagation of 'isalgorithm'
|
|
processed = dict()
|
|
def process_isalgorithm(classinfo):
|
|
if classinfo.isalgorithm or classinfo in processed:
|
|
return classinfo.isalgorithm
|
|
res = False
|
|
if classinfo.base:
|
|
res = process_isalgorithm(classes[classinfo.base])
|
|
#assert not (res == True or classinfo.isalgorithm is False), "Internal error: " + classinfo.name + " => " + classinfo.base
|
|
classinfo.isalgorithm |= res
|
|
res = classinfo.isalgorithm
|
|
processed[classinfo] = True
|
|
return res
|
|
for name, classinfo in classes.items():
|
|
process_isalgorithm(classinfo)
|
|
|
|
for name, ns in namespaces.items():
|
|
if name.split('.')[-1] == '':
|
|
continue
|
|
ns.registered = []
|
|
for name, cl in ns.classes.items():
|
|
registered_types.append(get_template_arg(name))
|
|
ns.registered.append(cl.mapped_name)
|
|
nss, clss, bs = split_decl_name(name)
|
|
type_paths[bs] = [name.replace("::", ".")]
|
|
type_paths["::".join(clss+[bs])] = [name.replace("::", ".")]
|
|
|
|
|
|
for e1,e2 in ns.enums.items():
|
|
registered_types.append(get_template_arg(e2[0]))
|
|
registered_types.append(get_template_arg(e2[0]).replace('::', '_')) #whyyy typedef
|
|
ns.registered.append(e2[1])
|
|
|
|
ns.register_types = list(set(ns.register_types))
|
|
ns.register_types = [tp for tp in ns.register_types if not registered_tp_search(tp) and not tp in ns.registered]
|
|
for tp in ns.register_types:
|
|
registered_types.append(get_template_arg(tp))
|
|
ns.registered.append(get_template_arg(tp))
|
|
default_valuesr = list(set(default_values))
|
|
# registered_types = registered_types + ns.register_types
|
|
return namespaces, default_valuesr
|