From 9ef878f3bf25c02b5556b58443a6f7791b79e85f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sat, 8 Sep 2018 22:03:00 +0000 Subject: [PATCH] matlab: fix bindings generator --- modules/matlab/CMakeLists.txt | 7 ++++ modules/matlab/compile.cmake | 9 ++++- modules/matlab/generator/filters.py | 4 +-- modules/matlab/generator/gen_matlab.py | 32 +++++++++++++----- modules/matlab/generator/parse_tree.py | 33 +++++++++++++++---- .../matlab/generator/templates/functional.cpp | 8 +++-- .../templates/template_class_base.cpp | 3 +- .../templates/template_function_base.cpp | 3 +- 8 files changed, 74 insertions(+), 25 deletions(-) diff --git a/modules/matlab/CMakeLists.txt b/modules/matlab/CMakeLists.txt index 698644f51..65b6eee7f 100644 --- a/modules/matlab/CMakeLists.txt +++ b/modules/matlab/CMakeLists.txt @@ -259,6 +259,13 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/test/help.m ${CMAKE_CURRENT_BINARY_DIR}/+cv COMMAND ${CMAKE_COMMAND} -E touch ${GENERATE_PROXY} COMMENT "Generating Matlab source files" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/build_info.py" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/cvmex.py" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/filters.py" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/gen_matlab.py" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/parse_tree.py" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/templates/functional.cpp" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generator/templates/template_function_base.cpp" ) # compile diff --git a/modules/matlab/compile.cmake b/modules/matlab/compile.cmake index 2fb087f87..6801ca2f0 100644 --- a/modules/matlab/compile.cmake +++ b/modules/matlab/compile.cmake @@ -28,11 +28,18 @@ endif() # 2. attempt compile if required # 3. if the compile fails, throw an error and cancel compilation file(GLOB SOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/src/*.cpp") +list(LENGTH SOURCE_FILES __size) +message("Matlab: compiling ${__size} files") +set(__index 0) foreach(SOURCE_FILE ${SOURCE_FILES}) + MATH(EXPR __index "${__index}+1") # strip out the filename get_filename_component(FILENAME ${SOURCE_FILE} NAME_WE) + message("[${__index}/${__size}] Compiling: ${FILENAME}") # compile the source file using mex - if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/+cv/${FILENAME}.${MATLAB_MEXEXT}) + if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/+cv/${FILENAME}.${MATLAB_MEXEXT}" OR + "${SOURCE_FILE}" IS_NEWER_THAN "${CMAKE_CURRENT_BINARY_DIR}/+cv/${FILENAME}.${MATLAB_MEXEXT}" + ) execute_process( COMMAND ${MATLAB_MEX_SCRIPT} ${MEX_OPTS} "CXXFLAGS=\$CXXFLAGS ${MEX_CXXFLAGS}" ${MEX_INCLUDE_DIRS_LIST} ${MEX_LIB_DIR} ${MEX_LIBS_LIST} ${SOURCE_FILE} diff --git a/modules/matlab/generator/filters.py b/modules/matlab/generator/filters.py index de69ff7e4..745392e0b 100644 --- a/modules/matlab/generator/filters.py +++ b/modules/matlab/generator/filters.py @@ -5,8 +5,6 @@ urlexpr = re.compile(r"((https?):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)", r def inputs(args): '''Keeps only the input arguments in a list of elements. - In OpenCV input arguments are all arguments with names - not beginning with 'dst' ''' try: return [arg for arg in args['only'] if arg.I and not arg.O] @@ -20,7 +18,7 @@ def ninputs(fun): def outputs(args): '''Determines whether any of the given arguments is an output reference, and returns a list of only those elements. - In OpenCV, output references are preceeded by 'dst' + In OpenCV, output references are preceeded by CV_OUT or has *OutputArray* type ''' try: return [arg for arg in args['only'] if arg.O and not arg.I] diff --git a/modules/matlab/generator/gen_matlab.py b/modules/matlab/generator/gen_matlab.py index 513b300a6..d3bd96652 100644 --- a/modules/matlab/generator/gen_matlab.py +++ b/modules/matlab/generator/gen_matlab.py @@ -4,6 +4,26 @@ from string import Template from parse_tree import ParseTree, todict, constants from filters import * +updated_files = [] + +def update_file(fname, content): + if fname in updated_files: + print('ERROR(gen_matlab.py): attemption to write file multiple times: {}'.format(fname)) + return + updated_files.append(fname) + if os.path.exists(fname): + with open(fname, 'rb') as f: + old_content = f.read() + if old_content == content: + #print('Up-to-date: {}'.format(fname)) + return + print('Updating: {}'.format(fname)) + else: + print('Writing: {}'.format(fname)) + with open(fname, 'wb') as f: + f.write(content) + + class MatlabWrapperGenerator(object): """ MatlabWrapperGenerator is a class for generating Matlab mex sources from @@ -107,24 +127,20 @@ class MatlabWrapperGenerator(object): # functions for method in namespace.methods: populated = tfunction.render(fun=method, time=time, includes=namespace.name) - with open(output_source_dir+'/'+method.name+'.cpp', 'wb') as f: - f.write(populated.encode('utf-8')) + update_file(output_source_dir+'/'+method.name+'.cpp', populated.encode('utf-8')) # classes for clss in namespace.classes: # cpp converter populated = tclassc.render(clss=clss, time=time) - with open(output_private_dir+'/'+clss.name+'Bridge.cpp', 'wb') as f: - f.write(populated.encode('utf-8')) + update_file(output_private_dir+'/'+clss.name+'Bridge.cpp', populated.encode('utf-8')) # matlab classdef populated = tclassm.render(clss=clss, time=time) - with open(output_class_dir+'/'+clss.name+'.m', 'wb') as f: - f.write(populated.encode('utf-8')) + update_file(output_class_dir+'/'+clss.name+'.m', populated.encode('utf-8')) # create a global constants lookup table const = dict(constants(todict(parse_tree.namespaces))) populated = tconst.render(constants=const, time=time) - with open(output_dir+'/cv.m', 'wb') as f: - f.write(populated.encode('utf-8')) + update_file(output_dir+'/cv.m', populated.encode('utf-8')) if __name__ == "__main__": diff --git a/modules/matlab/generator/parse_tree.py b/modules/matlab/generator/parse_tree.py index a6a146a55..0a7ef3648 100644 --- a/modules/matlab/generator/parse_tree.py +++ b/modules/matlab/generator/parse_tree.py @@ -8,6 +8,21 @@ except NameError: # Python 3.3+ basestring = str + +valid_types = ( + 'int', 'bool', 'float', 'double', 'size_t', 'char', + 'Mat', 'Scalar', 'String', + 'TermCriteria', 'Size', 'Point', 'Point2f', 'Point2d', 'Rect', 'RotatedRect', + 'RNG', 'DMatch', 'Moments', + 'vector_Mat', 'vector_Point', 'vector_int', 'vector_float', 'vector_double', 'vector_String', 'vector_uchar', 'vector_Rect', 'vector_DMatch', 'vector_KeyPoint', + 'vector_Point2f', 'vector_vector_char', 'vector_vector_DMatch', 'vector_vector_KeyPoint', + 'Ptr_StereoBM', 'Ptr_StereoSGBM', 'Ptr_FeatureDetector', 'Ptr_CLAHE', 'Ptr_LineSegmentDetector', 'Ptr_AlignMTB', 'Ptr_CalibrateDebevec', + 'Ptr_CalibrateRobertson', 'Ptr_DenseOpticalFlow', 'Ptr_DualTVL1OpticalFlow', 'Ptr_MergeDebevec', 'Ptr_MergeMertens', 'Ptr_MergeRobertson', + 'Ptr_Stitcher', 'Ptr_Tonemap', 'Ptr_TonemapDrago', 'Ptr_TonemapDurand', 'Ptr_TonemapMantiuk', 'Ptr_TonemapReinhard', 'Ptr_float', + # Not supported: + #vector_vector_KeyPoint +) + class ParseTree(object): """ The ParseTree class produces a semantic tree of C++ definitions given @@ -89,7 +104,11 @@ class ParseTree(object): methods = [] constants = [] for defn in definitions: - obj = babel.translate(defn) + try: + obj = babel.translate(defn) + except Exception as e: + print(e) + obj = None if obj is None: continue if type(obj) is Class or obj.clss: @@ -176,15 +195,15 @@ class Translator(object): return Constant(name, clss, tp, const, '', val) def translateArgument(self, defn): + modifiers = defn[3] ref = '*' if '*' in defn[0] else '' - ref = '&' if '&' in defn[0] else ref - const = ' const ' in ' '+defn[0]+' ' + ref = '&' if '&' in defn[0] or '/Ref' in modifiers else ref + const = '/C' in modifiers tp = " ".join([word for word in defn[0].replace(ref, '').split() if not ' const ' in ' '+word+' ']) name = defn[1] default = defn[2] if defn[2] else '' - modifiers = ''.join(defn[3]) - I = True if not modifiers or 'I' in modifiers else False - O = True if 'O' in modifiers else False + I = True if '/I' in modifiers or not '/O' in modifiers else False + O = True if '/O' in modifiers else False return Argument(name, tp, const, I, O, ref, default) def translateName(self, name): @@ -292,6 +311,8 @@ class Argument(object): self.O = O self.const = const self.default = default + if not tp in valid_types: + raise Exception("Non-supported argument type: {} (name: {})".format(tp, name)) def __str__(self): return ('const ' if self.const else '')+self.tp+self.ref+\ diff --git a/modules/matlab/generator/templates/functional.cpp b/modules/matlab/generator/templates/functional.cpp index b019a1300..dc048f269 100644 --- a/modules/matlab/generator/templates/functional.cpp +++ b/modules/matlab/generator/templates/functional.cpp @@ -104,21 +104,23 @@ addVariant("{{ fun.name }}", {{ fun.req|inputs|length }}, {{ fun.opt|inputs|leng {%- macro handleInputs(fun) %} {% if fun|ninputs or (fun|noutputs and not fun.constructor) %} - // unpack the arguments - {# ----------- Inputs ------------- #} + // - inputs {% for arg in fun.req|inputs %} {{arg.tp}} {{arg.name}} = inputs[{{ loop.index0 }}].to{{arg.tp|toUpperCamelCase}}(); {% endfor %} + // - inputs (opt) {% for opt in fun.opt|inputs %} {{opt.tp}} {{opt.name}} = inputs[{{loop.index0 + fun.req|inputs|length}}].empty() ? ({{opt.tp}}) {% if opt.ref == '*' -%} {{opt.tp}}() {%- else -%} {{opt.default}} {%- endif %} : inputs[{{loop.index0 + fun.req|inputs|length}}].to{{opt.tp|toUpperCamelCase}}(); {% endfor %} - {# ----------- Outputs ------------ #} + // - outputs {% for arg in fun.req|only|outputs %} {{arg.tp}} {{arg.name}}; {% endfor %} + // - outputs (opt) {% for opt in fun.opt|only|outputs %} {{opt.tp}} {{opt.name}}; {% endfor %} + // - return {% if not fun.rtp|void and not fun.constructor %} {{fun.rtp}} retval; {% endif %} diff --git a/modules/matlab/generator/templates/template_class_base.cpp b/modules/matlab/generator/templates/template_class_base.cpp index 09e0a0870..4a03c4b1c 100644 --- a/modules/matlab/generator/templates/template_class_base.cpp +++ b/modules/matlab/generator/templates/template_class_base.cpp @@ -2,11 +2,10 @@ /* * file: {{clss.name}}Bridge.cpp * author: A trusty code generator - * date: {{time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())}} * * This file was autogenerated, do not modify. * See LICENSE for full modification and redistribution details. - * Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation + * Copyright 2018 The OpenCV Foundation */ #include #include diff --git a/modules/matlab/generator/templates/template_function_base.cpp b/modules/matlab/generator/templates/template_function_base.cpp index 9d12ac2d7..4b7413b55 100644 --- a/modules/matlab/generator/templates/template_function_base.cpp +++ b/modules/matlab/generator/templates/template_function_base.cpp @@ -2,11 +2,10 @@ /* * file: {{fun.name}}.cpp * author: A trusty code generator - * date: {{time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())}} * * This file was autogenerated, do not modify. * See LICENSE for full modification and redistribution details. - * Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation + * Copyright 2018 The OpenCV Foundation */ #include #include