diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index f44e3df8d..b84512875 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -311,7 +311,9 @@ macro(ocv_glob_modules) endif() list(APPEND __directories_observed "${__path}") - file(GLOB __ocvmodules RELATIVE "${__path}" "${__path}/*") + # TODO: Undo this change to build all modules + #file(GLOB __ocvmodules RELATIVE "${__path}" "${__path}/*") + file(GLOB __ocvmodules RELATIVE "${__path}" "${__path}/core" "${__path}/matlab") if(__ocvmodules) list(SORT __ocvmodules) foreach(mod ${__ocvmodules}) diff --git a/modules/matlab/CMakeLists.txt b/modules/matlab/CMakeLists.txt index 8038ef991..b2246ab35 100644 --- a/modules/matlab/CMakeLists.txt +++ b/modules/matlab/CMakeLists.txt @@ -40,13 +40,16 @@ if (IOS OR ANDROID OR NOT MATLAB_FOUND OR NOT PYTHONLIBS_FOUND) return() endif() +# TODO: matlab bindings should depend on python (maybe) set(the_description "The Matlab/Octave bindings") -ocv_add_module(matlab BINDINGS opencv_core opencv_imgproc +ocv_add_module(matlab BINDINGS opencv_core #TODO: does it actually NEED to depend on core? OPTIONAL opencv_objdetect opencv_features2d opencv_video opencv_highgui opencv_ml opencv_calib3d opencv_photo - opencv_nonfree opencv_calib) + opencv_nonfree opencv_calib opencv_imgproc) -set(HDR_PARSER_PATH ${OPENCV_MODULE_opencv_python_LOCATION}/src2) +# TODO: Undo this when building all modules to find python properly +#set(HDR_PARSER_PATH ${OPENCV_MODULE_opencv_python_LOCATION}/src2) +set(HDR_PARSER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../python/src2) prepend("-I" MEX_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include) if (BUILD_TESTS) @@ -106,14 +109,14 @@ foreach(module ${OPENCV_MATLAB_MODULES}) endforeach() # synthesise the matlab sources -add_custom_target(opencv_matlab_sources ALL +add_custom_target(opencv_matlab_sources COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generator/gen_matlab_caller.py ${HDR_PARSER_PATH} ${opencv_hdrs} ${CMAKE_CURRENT_BINARY_DIR} ) # compile the matlab sources to mex -add_custom_target(opencv_matlab ALL) +add_custom_target(opencv_matlab ALL DEPENDS opencv_matlab_sources) file(GLOB SOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/src/*.cpp") foreach(SOURCE_FILE ${SOURCE_FILES}) get_filename_component(FILENAME ${SOURCE_FILE} NAME_WE) @@ -122,7 +125,6 @@ foreach(SOURCE_FILE ${SOURCE_FILES}) COMMAND echo ${MATLAB_MEX_SCRIPT} ${MEX_INCLUDES} ${SOURCE_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/src - DEPENDS opencv_matlab_sources ) endforeach() diff --git a/modules/matlab/generator/filters.py b/modules/matlab/generator/filters.py index b69ef72d9..9b0f7074f 100644 --- a/modules/matlab/generator/filters.py +++ b/modules/matlab/generator/filters.py @@ -2,6 +2,40 @@ from textwrap import TextWrapper from string import split, join import re +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' + ''' + out = [] + for arg in args: + if not arg.name.startswith('dst'): + out.append(arg) + return out + +def ninputs(args): + '''Counts the number of input arguments in the input list''' + return len(inputs(args)) + +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' + ''' + out = [] + for arg in args: + if arg.name.startswith('dst'): + out.append(arg) + return out + +def output(arg): + return True if arg.name.startswith('dst') else False + +def noutputs(args): + '''Counts the number of output arguments in the input list''' + return len(outputs(args)) + + def toUpperCamelCase(text): return text[0].upper() + text[1:] diff --git a/modules/matlab/generator/gen_matlab.py b/modules/matlab/generator/gen_matlab.py index 3e4b60c62..3f2858e7a 100644 --- a/modules/matlab/generator/gen_matlab.py +++ b/modules/matlab/generator/gen_matlab.py @@ -30,7 +30,12 @@ class MatlabWrapperGenerator(object): jtemplate.filters['toUpperCamelCase'] = toUpperCamelCase jtemplate.filters['toLowerCamelCase'] = toLowerCamelCase jtemplate.filters['toUnderCase'] = toUnderCase - jtemplate.filters['comment'] = comment + jtemplate.filters['comment'] = comment + jtemplate.filters['inputs'] = inputs + jtemplate.filters['outputs'] = outputs + jtemplate.filters['output'] = output + jtemplate.filters['noutputs'] = noutputs + jtemplate.filters['ninputs'] = ninputs # load the templates tfunction = jtemplate.get_template('template_function_base.cpp') @@ -53,7 +58,7 @@ class MatlabWrapperGenerator(object): for namespace in parse_tree.namespaces: # functions for function in namespace.functions: - populated = tfunction.render(fun=function, time=time) + populated = tfunction.render(fun=function, time=time, includes=namespace.name) with open(output_source_dir+'/'+function.name+'.cpp', 'wb') as f: f.write(populated) # classes diff --git a/modules/matlab/generator/parse_tree.py b/modules/matlab/generator/parse_tree.py index b092cdc0f..f922bd4d7 100644 --- a/modules/matlab/generator/parse_tree.py +++ b/modules/matlab/generator/parse_tree.py @@ -85,7 +85,7 @@ class Translator(object): def translateArgument(self, defn): tp = defn[0] name = defn[1] - default = tp+'()' if defn[2] else '' + default = defn[2] if defn[2] else '' return Argument(name, tp, False, '', default) def translateName(self, name): diff --git a/modules/matlab/generator/templates/functional.cpp b/modules/matlab/generator/templates/functional.cpp new file mode 100644 index 000000000..f928780ab --- /dev/null +++ b/modules/matlab/generator/templates/functional.cpp @@ -0,0 +1,57 @@ +// compose a function +{% macro compose(fun, retname="ret") %} + {%- if not fun.rtp == "void" -%} {{fun.rtp}} retname = {% endif -%} + {{fun.name}}( + {%- for arg in fun.req -%} + {{arg.name}} + {%- if not loop.last %}, {% endif %} + {% endfor %} + {% if fun.req and fun.opt %}, {% endif %} + {%- for opt in fun.opt -%} + {{opt.name}} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); +{%- endmacro %} + +// create a full function invocation +{%- macro generate(fun) -%} + + // unpack the arguments + // inputs + {% for arg in fun.req|inputs %} + {{arg.tp}} {{arg.name}} = inputs[{{ loop.index0 }}]; + {% endfor %} + {% for opt in fun.opt|inputs %} + {{opt.tp}} {{opt.name}} = (nrhs > {{loop.index0 + fun.req|ninputs}}) ? inputs[{{loop.index0 + fun.req|ninputs}}] : {{opt.default}}; + {% endfor %} + + // outputs + {% for arg in fun.req|outputs %} + {{arg.tp}} {{arg.name}}; + {% endfor %} + {% for opt in fun.opt|outputs %} + {{opt.tp}} {{opt.name}}; + {% endfor %} + + // call the opencv function + // [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn); + try { + {{ compose(fun) }} + } catch(cv::Exception& e) { + mexErrMsgTxt(std::string("cv::exception caught: ").append(e.what()).c_str()); + } catch(std::exception& e) { + mexErrMsgTxt(std::string("std::exception caught: ").append(e.what()).c_str()); + } catch(...) { + mexErrMsgTxt("Uncaught exception occurred in {{fun.name}}"); + } + + // assign the outputs into the bridge + {% for arg in fun.req|outputs %} + outputs[{{loop.index0}}] = {{arg.name}}; + {% endfor %} + {% for opt in fun.opt|outputs %} + outputs[{{loop.index0 + fun.req|noutputs}}] = {{opt.name}}; + {% endfor %} + +{%- endmacro -%} diff --git a/modules/matlab/generator/templates/template_class_base.cpp b/modules/matlab/generator/templates/template_class_base.cpp index 970d0395a..48ebc5083 100644 --- a/modules/matlab/generator/templates/template_class_base.cpp +++ b/modules/matlab/generator/templates/template_class_base.cpp @@ -1,3 +1,4 @@ +{% import 'functional.cpp' as functional %} /* * file: {{clss.name}}Bridge.cpp * author: A trusty code generator @@ -27,16 +28,7 @@ std::vector {{function.name}}({{clss.name}}& inst, const std::vector #include #include -#include +#include {% block includes %} {% endblock %} +using namespace std; +using namespace cv; /* * {{ fun.name }} @@ -27,34 +30,18 @@ void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { - {% block argcheck %} - {% endblock %} + // assertions + mxAssert(nrhs >= {{fun.req|length - fun.req|noutputs}}, "Too few required input arguments specified"); + mxAssert(nrhs <= {{fun.req|length + fun.opt|length - fun.req|noutputs - fun.opt|noutputs}}, "Too many input arguments specified"); + mxAssert(nlhs <= {{fun.ret|length + fun.req|noutputs + fun.opt|noutputs}}, "Too many output arguments specified"); - {% block prebridge %} - {% endblock %} + // setup + vector inputs(plhs, plhs+nrhs); + vector outputs(nlhs); - // parse the inputs and outputs + {{ fun }} - {% block postbridge %} - {% endblock %} + {{ functional.generate(fun) }} - // call the opencv function - // [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn); - try { - {{fun.name}}(); - } catch(cv::Exception& e) { - mexErrMsgTxt(std::string("cv::exception caught: ").append(e.what()).c_str()); - } catch(std::exception& e) { - mexErrMsgTxt(std::string("std::exception caught: ").append(e.what()).c_str()); - } catch(...) { - mexErrMsgTxt("Uncaught exception occurred in {{fun.name}}"); - } - {% block fcall %} - {% endblock %} - - {% block postfun %} - {% endblock %} - - {% block cleanup %} - {% endblock %} + // setdown } diff --git a/modules/matlab/test/CMakeLists.txt b/modules/matlab/test/CMakeLists.txt index 8a3043870..dd773a5ed 100644 --- a/modules/matlab/test/CMakeLists.txt +++ b/modules/matlab/test/CMakeLists.txt @@ -1,6 +1,6 @@ # compile the test sources file(GLOB SOURCE_FILES "*.cpp") -add_custom_target(opencv_test_matlab_sources ALL) +add_custom_target(opencv_test_matlab_sources) foreach(SOURCE_FILE ${SOURCE_FILES}) get_filename_component(FILENAME ${SOURCE_FILE} NAME_WE) # compile the source file using mex