diff --git a/modules/matlab/generator/build_info.py b/modules/matlab/generator/build_info.py
index e02354974..50d3ffe45 100644
--- a/modules/matlab/generator/build_info.py
+++ b/modules/matlab/generator/build_info.py
@@ -1,4 +1,4 @@
-#/usr/bin/env python
+#!/usr/bin/env python
def substitute(build, output_dir):
@@ -18,12 +18,32 @@ def substitute(build, output_dir):
if not os.path.isdir(output_dir):
os.mkdir(output_dir)
- # populate templates
+ # populate template
populated = template.render(build=build)
with open(os.path.join(output_dir, 'buildInformation.m'), 'wb') as f:
f.write(populated)
if __name__ == "__main__":
+ """
+ Usage: python build_info.py --os os_version_string
+ --arch [bitness processor]
+ --compiler [id version]
+ --mex_arch arch_string
+ --mex_script /path/to/mex/script
+ --cxx_flags [-list -of -flags -to -passthrough]
+ --opencv_version version_string
+ --commit commit_hash_if_using_git
+ --modules [core imgproc highgui etc]
+ --configuration Debug/Release
+ --outdir /path/to/write/build/info
+
+ build_info.py generates a Matlab function that can be invoked with a call to
+ >> cv.buildInformation();
+
+ This function prints a summary of the user's OS, OpenCV and Matlab build
+ given the information passed to this module. build_info.py invokes Jinja2
+ on the template_build_info.m template.
+ """
# parse the input options
import sys, re, os
diff --git a/modules/matlab/generator/filters.py b/modules/matlab/generator/filters.py
index 0ae371c4c..e908fe8ff 100644
--- a/modules/matlab/generator/filters.py
+++ b/modules/matlab/generator/filters.py
@@ -1,6 +1,7 @@
from textwrap import TextWrapper
from string import split, join
import re, os
+# precompile a URL matching regular expression
urlexpr = re.compile(r"((https?):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.MULTILINE|re.UNICODE)
def inputs(args):
@@ -35,9 +36,11 @@ def only(args):
return d
def void(arg):
+ '''Is the input 'void' '''
return arg == 'void'
def flip(arg):
+ '''flip the sign of the input'''
return not arg
def noutputs(fun):
@@ -45,6 +48,7 @@ def noutputs(fun):
return int(not void(fun.rtp)) + len(outputs(fun.req)) + len(outputs(fun.opt))
def convertibleToInt(string):
+ '''Can the input string be evaluated to an integer?'''
salt = '1+'
try:
exec(salt+string)
@@ -53,12 +57,21 @@ def convertibleToInt(string):
return False
def binaryToDecimal(string):
+ '''Attempt to convert the input string to floating point representation'''
try:
return str(eval(string))
except:
return string
def formatMatlabConstant(string, table):
+ '''
+ Given a string representing a Constant, and a table of all Constants,
+ attempt to resolve the Constant into a valid Matlab expression
+ For example, the input
+ DEPENDENT_VALUE = 1 << FIXED_VALUE
+ needs to be converted to
+ DEPENDENT_VALUE = bitshift(1, cv.FIXED_VALUE);
+ '''
# split the string into expressions
words = re.split('(\W+)', string)
# add a 'cv' prefix if an expression is also a key in the lookup table
@@ -76,20 +89,33 @@ def matlabURL(string):
return re.sub(urlexpr, '\\1', string)
def capitalizeFirst(text):
+ '''Capitalize only the first character of the text string'''
return text[0].upper() + text[1:]
def toUpperCamelCase(text):
+ '''variable_name --> VariableName'''
return ''.join([capitalizeFirst(word) for word in text.split('_')])
def toLowerCamelCase(text):
+ '''variable_name --> variableName'''
upper_camel = toUpperCamelCase(test)
return upper_camel[0].lower() + upper_camel[1:]
def toUnderCase(text):
+ '''VariableName --> variable_name'''
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def stripTags(text):
+ '''
+ strip or convert html tags from a text string
+ content
--> content
+ --> ''
+ < --> <
+ > --> >
+ &le --> <=
+ &ge --> >=
+ '''
upper = lambda pattern: pattern.group(1).upper()
text = re.sub('(.*?)
', upper, text)
text = re.sub('<([^=\s].*?)>', '', text)
@@ -100,18 +126,26 @@ def stripTags(text):
return text
def qualify(text, name):
+ '''Adds uppercase 'CV.' qualification to any occurrences of name in text'''
return re.sub(name.upper(), 'CV.'+name.upper(), text)
def slugify(text):
+ '''A_Function_name --> a-function-name'''
return text.lower().replace('_', '-')
def filename(fullpath):
+ '''Returns only the filename without an extension from a file path
+ eg. /path/to/file.txt --> file
+ '''
return os.path.splitext(os.path.basename(fullpath))[0]
def csv(items, sep=', '):
+ '''format a list with a separator (comma if not specified)'''
return sep.join(item for item in items)
def stripExtraSpaces(text):
+ '''Removes superfluous whitespace from a string, including the removal
+ of all leading and trailing whitespace'''
return ' '.join(text.split())
def comment(text, wrap=80, escape='% ', escape_first='', escape_last=''):
diff --git a/modules/matlab/generator/gen_matlab.py b/modules/matlab/generator/gen_matlab.py
index 86c5ff6d9..8acc6facb 100644
--- a/modules/matlab/generator/gen_matlab.py
+++ b/modules/matlab/generator/gen_matlab.py
@@ -1,8 +1,27 @@
-#/usr/bin/env python
+#!/usr/bin/env python
class MatlabWrapperGenerator(object):
+ """
+ MatlabWrapperGenerator is a class for generating Matlab mex sources from
+ a set of C++ headers. MatlabWrapperGenerator objects can be default
+ constructed. Given an instance, the gen() method performs the translation.
+ """
def gen(self, module_root, modules, extras, output_dir):
+ """
+ Generate a set of Matlab mex source files by parsing exported symbols
+ in a set of C++ headers. The headers can be input in one (or both) of
+ two methods:
+ 1. specify module_root and modules
+ Given a path to the OpenCV module root and a list of module names,
+ the headers to parse are implicitly constructed.
+ 2. specifiy header locations explicitly in extras
+ Each element in the list of extras must be of the form:
+ 'namespace=/full/path/to/extra/header.hpp' where 'namespace' is
+ the namespace in which the definitions should be added.
+ The output_dir specifies the directory to write the generated sources
+ to.
+ """
# parse each of the files and store in a dictionary
# as a separate "namespace"
parser = CppHeaderParser()
@@ -109,8 +128,41 @@ class MatlabWrapperGenerator(object):
f.write(populated)
-
if __name__ == "__main__":
+ """
+ Usage: python gen_matlab.py --hdrparser /path/to/hdr_parser/dir
+ --rstparser /path/to/rst_parser/dir
+ --moduleroot /path/to/opencv/modules
+ --modules [core imgproc objdetect etc]
+ --extra namespace=/path/to/extra/header.hpp
+ --outdir /path/to/output/generated/srcs
+
+ gen_matlab.py is the main control script for generating matlab source
+ files from given set of headers. Internally, gen_matlab:
+ 1. constructs the headers to parse from the module root and list of modules
+ 2. parses the headers using CppHeaderParser
+ 3. refactors the definitions using ParseTree
+ 4. parses .rst docs using RstParser
+ 5. populates the templates for classes, function, enums and docs from the
+ definitions
+
+ gen_matlab.py requires the following inputs:
+ --hdrparser the path to the header parser directory
+ (opencv/modules/python/src2)
+ --rstparser the path to the rst parser directory
+ (opencv/modules/java/generator)
+ --moduleroot (optional) path to the opencv directory containing the modules
+ --modules (optional - required if --moduleroot specified) the modules
+ to produce bindings for. The path to the include directories
+ as well as the namespaces are constructed from the modules
+ and the moduleroot
+ --extra extra headers explicitly defined to parse. This must be in
+ the format "namepsace=/path/to/extra/header.hpp". For example,
+ the core module requires the extra header:
+ "core=/opencv/modules/core/include/opencv2/core/core/base.hpp"
+ --outdir the output directory to put the generated matlab sources. In
+ the OpenCV build this is "${CMAKE_CURRENT_BUILD_DIR}/src"
+ """
# parse the input options
import sys, re, os, time
diff --git a/modules/matlab/generator/parse_tree.py b/modules/matlab/generator/parse_tree.py
index e70bc30ca..4362f89f2 100644
--- a/modules/matlab/generator/parse_tree.py
+++ b/modules/matlab/generator/parse_tree.py
@@ -3,6 +3,73 @@ from textwrap import fill
from filters import *
class ParseTree(object):
+ """
+ The ParseTree class produces a semantic tree of C++ definitions given
+ the output of the CppHeaderParser (from opencv/modules/python/src2/hdr_parser.py)
+
+ The full hierarchy is as follows:
+
+ Namespaces
+ |
+ |- name
+ |- Classes
+ |
+ |- name
+ |- Methods
+ |- Constants
+ |- Methods
+ |
+ |- name
+ |- static (T/F)
+ |- return type
+ |- required Arguments
+ |
+ |- name
+ |- const (T/F)
+ |- reference ('&'/'*')
+ |- type
+ |- input
+ |- output (pass return by reference)
+ |- default value
+ |- optional Arguments
+ |- Constants
+ |
+ |- name
+ |- const (T/F)
+ |- reference ('&'/'*')
+ |- type
+ |- value
+
+ The semantic tree contains substantial information for easily introspecting
+ information about objects. How many methods does the 'core' namespace have?
+ Does the 'randn' method have any return by reference (output) arguments?
+ How many required and optional arguments does the 'add' method have? Is the
+ variable passed by reference or raw pointer?
+
+ Individual definitions from the parse tree (Classes, Functions, Constants)
+ are passed to the Jinja2 template engine where they are manipulated to
+ produce Matlab mex sources.
+
+ A common call tree for constructing and using a ParseTree object is:
+
+ # parse a set of definitions into a dictionary of namespaces
+ parser = CppHeaderParser()
+ ns['core'] = parser.parse('path/to/opencv/core.hpp')
+
+ # refactor into a semantic tree
+ parse_tree = ParseTree()
+ parse_tree.build(ns)
+
+ # iterate over the tree
+ for namespace in parse_tree.namespaces:
+ for clss in namespace.classes:
+ # do stuff
+ for method in namespace.methods:
+ # do stuff
+
+ Calling 'print' on a ParseTree object will reconstruct the definitions
+ to produce an output resembling the original C++ code.
+ """
def __init__(self, namespaces=None):
self.namespaces = namespaces if namespaces else []
@@ -48,6 +115,15 @@ class ParseTree(object):
class Translator(object):
+ """
+ The Translator class does the heavy lifting of translating the nested
+ list representation of the hdr_parser into individual definitions that
+ are inserted into the ParseTree.
+ Translator consists of a top-level method: translate()
+ along with a number of helper methods: translateClass(), translateMethod(),
+ translateArgument(), translateConstant(), translateName(), and
+ translateClassName()
+ """
def translate(self, defn):
# --- class ---
# classes have 'class' prefixed on their name
@@ -116,6 +192,14 @@ class Translator(object):
class Namespace(object):
+ """
+ Namespace
+ |
+ |- name
+ |- Constants
+ |- Methods
+ |- Constants
+ """
def __init__(self, name='', constants=None, classes=None, methods=None):
self.name = name
self.constants = constants if constants else []
@@ -129,6 +213,13 @@ class Namespace(object):
(join((o.__str__() for o in self.classes), '\n\n') if self.classes else '')+'\n};'
class Class(object):
+ """
+ Class
+ |
+ |- name
+ |- Methods
+ |- Constants
+ """
def __init__(self, name='', namespace='', constants=None, methods=None):
self.name = name
self.namespace = namespace
@@ -141,6 +232,21 @@ class Class(object):
(join((f.__str__() for f in self.methods), '\n\t') if self.methods else '')+'\n};'
class Method(object):
+ """
+ Method
+ int VideoWriter::read( cv::Mat& frame, const cv::Mat& mask=cv::Mat() );
+ --- ----- ---- -------- ----------------
+ rtp class name required optional
+
+ name the method name
+ clss the class the method belongs to ('' if free)
+ static static?
+ namespace the namespace the method belongs to ('' if free)
+ rtp the return type
+ const const?
+ req list of required arguments
+ opt list of optional arguments
+ """
def __init__(self, name='', clss='', static=False, namespace='', rtp='', const=False, req=None, opt=None):
self.name = name
self.clss = clss
@@ -158,6 +264,20 @@ class Method(object):
')'+(' const' if self.const else '')+';'
class Argument(object):
+ """
+ Argument
+ const cv::Mat& mask=cv::Mat()
+ ----- ---- --- ---- -------
+ const tp ref name default
+
+ name the argument name
+ tp the argument type
+ const const?
+ I is the argument treated as an input?
+ O is the argument treated as an output (return by reference)
+ ref is the argument passed by reference? ('*'/'&')
+ default the default value of the argument ('' if required)
+ """
def __init__(self, name='', tp='', const=False, I=True, O=False, ref='', default=''):
self.name = name
self.tp = tp
@@ -172,6 +292,19 @@ class Argument(object):
' '+self.name+('='+self.default if self.default else '')
class Constant(object):
+ """
+ Constant
+ DFT_COMPLEX_OUTPUT = 12;
+ ---- -------
+ name default
+
+ name the name of the constant
+ clss the class that the constant belongs to ('' if free)
+ tp the type of the constant ('' if int)
+ const const?
+ ref is the constant a reference? ('*'/'&')
+ default default value, required for constants
+ """
def __init__(self, name='', clss='', tp='', const=False, ref='', default=''):
self.name = name
self.clss = clss
@@ -185,6 +318,10 @@ class Constant(object):
' '+self.name+('='+self.default if self.default else '')+';'
def constants(tree):
+ """
+ recursive generator to strip all Constant objects from the ParseTree
+ and place them into a flat dictionary of { name, value (default) }
+ """
if isinstance(tree, dict) and 'constants' in tree and isinstance(tree['constants'], list):
for node in tree['constants']:
yield (node['name'], node['default'])
@@ -198,6 +335,10 @@ def constants(tree):
yield gen
def todict(obj, classkey=None):
+ """
+ Convert the ParseTree to a dictionary, stripping all objects of their
+ methods and converting class names to strings
+ """
if isinstance(obj, dict):
for k in obj.keys():
obj[k] = todict(obj[k], classkey)