From baa946c3906caef8c963c48d166e99ef37106118 Mon Sep 17 00:00:00 2001 From: hbristow Date: Tue, 2 Jul 2013 16:42:37 -0700 Subject: [PATCH] Split MxArray out of bridge into standalone file. Does not depend on bridge. Working to make it API complete with respect to mxArray, but with better calling syntax --- modules/matlab/generator/gen_matlab.py | 6 +- modules/matlab/generator/parse_tree.py | 33 +- .../matlab/generator/templates/functional.cpp | 70 ++- .../templates/template_class_base.cpp | 31 +- .../templates/template_function_base.cpp | 6 +- modules/matlab/include/bridge.hpp | 447 +------------- modules/matlab/include/mxarray.hpp | 547 ++++++++++++++++++ 7 files changed, 642 insertions(+), 498 deletions(-) create mode 100644 modules/matlab/include/mxarray.hpp diff --git a/modules/matlab/generator/gen_matlab.py b/modules/matlab/generator/gen_matlab.py index f53c5ae53c..34b306cb61 100644 --- a/modules/matlab/generator/gen_matlab.py +++ b/modules/matlab/generator/gen_matlab.py @@ -58,9 +58,9 @@ class MatlabWrapperGenerator(object): # populate templates for namespace in parse_tree.namespaces: # functions - for function in namespace.functions: - populated = tfunction.render(fun=function, time=time, includes=namespace.name) - with open(output_source_dir+'/'+function.name+'.cpp', 'wb') as f: + 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) # classes for clss in namespace.classes: diff --git a/modules/matlab/generator/parse_tree.py b/modules/matlab/generator/parse_tree.py index c038994057..e70bc30cad 100644 --- a/modules/matlab/generator/parse_tree.py +++ b/modules/matlab/generator/parse_tree.py @@ -13,7 +13,7 @@ class ParseTree(object): babel = Translator() for name, definitions in namespaces.items(): class_tree = {} - functions = [] + methods = [] constants = [] for defn in definitions: obj = babel.translate(defn) @@ -21,13 +21,13 @@ class ParseTree(object): continue if type(obj) is Class or obj.clss: self.insertIntoClassTree(obj, class_tree) - elif type(obj) is Function: - functions.append(obj) + elif type(obj) is Method: + methods.append(obj) elif type(obj) is Constant: constants.append(obj) else: raise TypeError('Unexpected object type: '+str(type(obj))) - self.namespaces.append(Namespace(name, constants, class_tree.values(), functions)) + self.namespaces.append(Namespace(name, constants, class_tree.values(), methods)) def insertIntoClassTree(self, obj, class_tree): cname = obj.name if type(obj) is Class else obj.clss @@ -38,8 +38,8 @@ class ParseTree(object): class_tree[cname] = Class(cname) # insert the definition into the class val = class_tree[cname] - if type(obj) is Function: - val.functions.append(obj) + if type(obj) is Method: + val.methods.append(obj) elif type(obj) is Constant: val.constants.append(obj) else: @@ -63,7 +63,7 @@ class Translator(object): # --- function --- # functions either need to have input arguments, or not uppercase names elif defn[3] or not self.translateName(defn[0]).split('_')[0].isupper(): - return self.translateFunction(defn) + return self.translateMethod(defn) # --- constant --- else: return self.translateConstant(defn) @@ -71,7 +71,7 @@ class Translator(object): def translateClass(self, defn): return Class() - def translateFunction(self, defn, class_tree=None): + def translateMethod(self, defn, class_tree=None): name = self.translateName(defn[0]) clss = self.translateClassName(defn[0]) rtp = defn[1] @@ -83,7 +83,7 @@ class Translator(object): if arg: a = self.translateArgument(arg) opt.append(a) if a.default else req.append(a) - return Function(name, clss, static, '', rtp, False, req, opt) + return Method(name, clss, static, '', rtp, False, req, opt) def translateConstant(self, defn): const = True if 'const' in defn[0] else False @@ -116,34 +116,35 @@ class Translator(object): class Namespace(object): - def __init__(self, name='', constants=None, classes=None, functions=None): + def __init__(self, name='', constants=None, classes=None, methods=None): self.name = name self.constants = constants if constants else [] self.classes = classes if classes else [] - self.functions = functions if functions else [] + self.methods = methods if methods else [] def __str__(self): return 'namespace '+self.name+' {\n\n'+\ (join((c.__str__() for c in self.constants), '\n')+'\n\n' if self.constants else '')+\ - (join((f.__str__() for f in self.functions), '\n')+'\n\n' if self.functions else '')+\ + (join((f.__str__() for f in self.methods), '\n')+'\n\n' if self.methods else '')+\ (join((o.__str__() for o in self.classes), '\n\n') if self.classes else '')+'\n};' class Class(object): - def __init__(self, name='', namespace='', constants=None, functions=None): + def __init__(self, name='', namespace='', constants=None, methods=None): self.name = name self.namespace = namespace self.constants = constants if constants else [] - self.functions = functions if functions else [] + self.methods = methods if methods else [] def __str__(self): return 'class '+self.name+' {\n\t'+\ (join((c.__str__() for c in self.constants), '\n\t')+'\n\n\t' if self.constants else '')+\ - (join((f.__str__() for f in self.functions), '\n\t') if self.functions else '')+'\n};' + (join((f.__str__() for f in self.methods), '\n\t') if self.methods else '')+'\n};' -class Function(object): +class Method(object): def __init__(self, name='', clss='', static=False, namespace='', rtp='', const=False, req=None, opt=None): self.name = name self.clss = clss + self.constructor = True if name == clss else False self.static = static self.const = const self.namespace = namespace diff --git a/modules/matlab/generator/templates/functional.cpp b/modules/matlab/generator/templates/functional.cpp index a478a73a82..8e2e9d0a10 100644 --- a/modules/matlab/generator/templates/functional.cpp +++ b/modules/matlab/generator/templates/functional.cpp @@ -1,14 +1,14 @@ /* * compose * compose a function call - * This macro takes as input a Function object and composes + * This macro takes as input a Method object and composes * a function call by inspecting the types and argument names */ -/ {% macro compose(fun) %} {# ----------- Return type ------------- #} - {%- if not fun.rtp|void -%} retval = {% endif -%} - {%- if fun.clss -%}inst.{%- else -%} cv:: {%- endif -%} + {%- if not fun.rtp|void and not fun.constructor -%} retval = {% endif -%} + {%- if fun.constructor -%}{{fun.clss}} obj = {% endif -%} + {%- if fun.clss and not fun.constructor -%}inst.{%- else -%} cv:: {%- endif -%} {{fun.name}}( {#- ----------- Required ------------- -#} {%- for arg in fun.req -%} @@ -26,10 +26,39 @@ ); {%- endmacro %} -// create a full function invocation -{%- macro generate(fun) -%} - {% if fun|ninputs or fun|noutputs %} +/* + * composeWithExceptionHandler + * compose a function call wrapped in exception traps + * This macro takes an input a Method object and composes a function + * call through the compose() macro, then wraps the return in traps + * for cv::Exceptions, std::exceptions, and all generic exceptions + * and returns a useful error message to the Matlab interpreter + */ +{%- macro composeWithExceptionHandler(fun) -%} + // call the opencv function + // [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn); + try { + {{ compose(fun) }} + } catch(cv::Exception& e) { + error(std::string("cv::exception caught: ").append(e.what()).c_str()); + } catch(std::exception& e) { + error(std::string("std::exception caught: ").append(e.what()).c_str()); + } catch(...) { + error("Uncaught exception occurred in {{fun.name}}"); + } +{%- endmacro %} + + +/* + * handleInputs + * unpack input arguments from the Bridge + * Given an input Bridge object, this unpacks the object from the Bridge and + * casts them into the correct type + */ +{%- macro handleInputs(fun) %} + + {% if fun|ninputs or (fun|noutputs and not fun.constructor) %} // unpack the arguments {# ----------- Inputs ------------- #} {% for arg in fun.req|inputs %} @@ -45,26 +74,24 @@ {% for opt in fun.opt|only|outputs %} {{opt.tp}} {{opt.name}}; {% endfor %} - {% if not fun.rtp|void %} + {% if not fun.rtp|void and not fun.constructor %} {{fun.rtp}} retval; {% endif %} {% endif %} - // 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}}"); - } +{%- endmacro %} + +/* + * handleOutputs + * pack outputs into the bridge + * Given a set of outputs, this methods assigns them into the bridge for + * return to the calling method + */ +{%- macro handleOutputs(fun) %} {% if fun|noutputs %} // assign the outputs into the bridge - {% if not fun.rtp|void %} + {% if not fun.rtp|void and not fun.constructor %} outputs[0] = retval; {% endif %} {% for arg in fun.req|outputs %} @@ -74,5 +101,4 @@ outputs[{{loop.index0 + fun.rtp|void|not + fun.req|outputs|length}}] = {{opt.name}}; {% endfor %} {% endif %} - -{% endmacro %} +{%- endmacro %} diff --git a/modules/matlab/generator/templates/template_class_base.cpp b/modules/matlab/generator/templates/template_class_base.cpp index 872f563b00..f353f75bb9 100644 --- a/modules/matlab/generator/templates/template_class_base.cpp +++ b/modules/matlab/generator/templates/template_class_base.cpp @@ -9,30 +9,41 @@ * Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation */ #include "mex.h" +#include "map.hpp" #include "bridge.hpp" #include -//TODO: Standard C++ does not have an unordered_map (only C++11 and Boost) -#include #include #include using namespace cv; namespace { -typedef std::unordered_map Map; typedef std::vector (*)({{clss.name}}&, const std::vector&) MethodSignature; -{% for function in clss.functions %} +{% for function in clss.methods %} + +{% if function.constructor %} +// wrapper for {{function.name}}() constructor +{{ function.clss }} {{function.name}}(const std::vector& inputs) { + {{ functional.handleInputs(function) }} + {{ functional.compose(function) }} + return obj; +} +{% else %} // wrapper for {{function.name}}() method -std::vector {{function.name}}({{clss.name}}& inst, const std::vector& args) { - {{ functional.generate(function) }} +std::vector {{function.name}}({{clss.name}}& inst, const std::vector& inputs) { + std::vector outputs{% if function|noutputs %}({{function|noutputs}}){% endif %}; + {{ functional.handleInputs(function) }} + {{ functional.composeWithExceptionHandler(function) }} + {{ functional.handleOutputs(function) }} + return outputs; } - +{% endif %} {% endfor %} -map createMethodMap() { +Map createMethodMap() { Map m; - {% for function in clss.functions -%} + {% for function in clss.methods %} m["{{function.name}}"] = &{{function.name}}; {% endfor %} @@ -82,4 +93,4 @@ void mexFunction(int nlhs, mxArray* plhs[], } -}; // end namespace +} // end namespace diff --git a/modules/matlab/generator/templates/template_function_base.cpp b/modules/matlab/generator/templates/template_function_base.cpp index 3fb41fe298..5089455dfd 100644 --- a/modules/matlab/generator/templates/template_function_base.cpp +++ b/modules/matlab/generator/templates/template_function_base.cpp @@ -44,9 +44,11 @@ void mexFunction(int nlhs, mxArray*{% if fun|noutputs %} plhs[]{% else %}*{% end {% endif %} {% endif %} - {{ functional.generate(fun) }} + {{ functional.handleInputs(fun) }} + {{ functional.composeWithExceptionHandler(fun) }} + {{ functional.handleOutputs(fun) }} - {%- if fun|noutputs %} + {% if fun|noutputs %} // push the outputs back to matlab for (size_t n = 0; n < static_cast(nlhs); ++n) { plhs[n] = outputs[n].toMxArray().releaseOwnership(); diff --git a/modules/matlab/include/bridge.hpp b/modules/matlab/include/bridge.hpp index f976ef657b..8333f0e288 100644 --- a/modules/matlab/include/bridge.hpp +++ b/modules/matlab/include/bridge.hpp @@ -1,34 +1,12 @@ #ifndef OPENCV_BRIDGE_HPP_ #define OPENCV_BRIDGE_HPP_ -#include "mex.h" -#include "map.hpp" +#include "mxarray.hpp" #include #include #include #include -/* - * All recent versions of Matlab ship with the MKL library which contains - * a blas extension called mkl_?omatcopy(). This defines an out-of-place - * copy and transpose operation. - * - * The mkl library is in ${MATLAB_ROOT}/bin/${MATLAB_MEXEXT}/libmkl... - * Matlab does not ship headers for the mkl functions, so we define them - * here. - * - * This operation is used extensively to copy between Matlab's column-major - * format and OpenCV's row-major format. - */ -#ifdef __cplusplus -extern "C" { -#endif - void mkl_somatcopy(char, char, size_t, size_t, const float, const float*, size_t, float*, size_t); - void mkl_domatcopy(char, char, size_t, size_t, const double, const double*, size_t, double*, size_t); -#ifdef __cplusplus -} -#endif - /* * Custom typedefs * Parsed names from the hdr_parser @@ -46,427 +24,6 @@ typedef cv::Ptr Ptr_StereoSGBM; typedef cv::Ptr Ptr_FeatureDetector; -/*! - * @brief raise error if condition fails - * - * This is a conditional wrapper for mexErrMsgTxt. If the conditional - * expression fails, an error is raised and the mex function returns - * to Matlab, otherwise this function does nothing - */ -void conditionalError(bool expr, const std::string& str) { - if (!expr) mexErrMsgTxt(std::string("condition failed: ").append(str).c_str()); -} - -/*! - * @brief raise an error - * - * This function is a wrapper around mexErrMsgTxt - */ -void error(const std::string& str) { - mexErrMsgTxt(str.c_str()); -} - - -// ---------------------------------------------------------------------------- -// PREDECLARATIONS -// ---------------------------------------------------------------------------- -class MxArray; -class Bridge; - -template -void deepCopyAndTranspose(const cv::Mat& src, MxArray& dst); - -template -void deepCopyAndTranspose(const MxArray& src, cv::Mat& dst); - - - - -// ---------------------------------------------------------------------------- -// MATLAB TRAITS -// ---------------------------------------------------------------------------- -namespace Matlab { - class DefaultTraits {}; - class InheritType {}; - static const int Dynamic = -1; - - template class Traits { - public: - static const mxClassID ScalarType = mxUNKNOWN_CLASS; - static const mxComplexity Complex = mxCOMPLEX; - static const mxComplexity Real = mxCOMPLEX; - static std::string ToString() { return "Unknown/Unsupported"; } - }; - // bool - template<> class Traits { - public: - static const mxClassID ScalarType = mxLOGICAL_CLASS; - static std::string ToString() { return "boolean"; } - }; - // uint8_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxUINT8_CLASS; - static std::string ToString() { return "uint8_t"; } - }; - // int8_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxINT8_CLASS; - static std::string ToString() { return "int8_t"; } - }; - // uint16_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxUINT16_CLASS; - static std::string ToString() { return "uint16_t"; } - }; - // int16_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxINT16_CLASS; - static std::string ToString() { return "int16_t"; } - }; - // uint32_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxUINT32_CLASS; - static std::string ToString() { return "uint32_t"; } - }; - // int32_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxINT32_CLASS; - static std::string ToString() { return "int32_t"; } - }; - // uint64_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxUINT64_CLASS; - static std::string ToString() { return "uint64_t"; } - }; - // int64_t - template<> class Traits { - public: - static const mxClassID ScalarType = mxINT64_CLASS; - static std::string ToString() { return "int64_t"; } - }; - // float - template<> class Traits { - public: - static const mxClassID ScalarType = mxSINGLE_CLASS; - static std::string ToString() { return "float"; } - }; - // double - template<> class Traits { - public: - static const mxClassID ScalarType = mxDOUBLE_CLASS; - static std::string ToString() { return "double"; } - }; - // size_t - template<> class Traits { - public: - static const mxClassID ScalarType = (sizeof(size_t) == 4) ? mxUINT32_CLASS : mxUINT64_CLASS; - static std::string ToString() { return "size_t"; } - }; - // char - template<> class Traits { - public: - static const mxClassID ScalarType = mxCHAR_CLASS; - static std::string ToString() { return "char"; } - }; - // char - template<> class Traits { - public: - static std::string ToString() { return "Inherited type"; } - }; -} - - - -// ---------------------------------------------------------------------------- -// MXARRAY -// ---------------------------------------------------------------------------- - - -/*! - * @class MxArray - * @brief A thin wrapper around Matlab's mxArray types - * - * MxArray provides a thin object oriented wrapper around Matlab's - * native mxArray type which exposes most of the functionality of the - * Matlab interface, but in a more C++ manner. MxArray objects are scoped, - * so you can freely create and destroy them without worrying about memory - * management. If you wish to pass the underlying mxArray* representation - * back to Matlab as an lvalue, see the releaseOwnership() method - */ -class MxArray { -private: - mxArray* ptr_; - bool owns_; - - void dealloc() { - if (owns_ && ptr_) { mxDestroyArray(ptr_); ptr_ = NULL; owns_ = false; } - } -public: - // constructors and destructor - MxArray() : ptr_(mxCreateDoubleMatrix(1, 1, Matlab::Traits<>::Real)), owns_(true) {} - MxArray(const mxArray* ptr) : ptr_(const_cast(ptr)), owns_(false) {} - virtual ~MxArray() { - dealloc(); - } - // copy constructor - // all copies are deep copies - MxArray(const MxArray& other) : ptr_(mxDuplicateArray(other.ptr_)), owns_(true) {} - // swap - friend void swap(MxArray& first, MxArray& second) { - using std::swap; - swap(first.ptr_, second.ptr_); - swap(first.owns_, second.owns_); - } - // assignment operator - // copy-and-swap idiom - // all copies are deep copies - MxArray& operator=(MxArray other) { - swap(*this, other); - return *this; - } -#if __cplusplus >= 201103L - // move constructor, if C++11 - // default construct and swap - MxArray(MxArray&& other) : MxArray() { - swap(*this, other); - } -#endif - - /* - * @brief release ownership to allow return into Matlab workspace - * - * MxArray is not directly convertible back to mxArray types through assignment - * because the MxArray may have been allocated on the free store, making it impossible - * to know whether the returned pointer will be released by someone else or not. - * - * Since Matlab requires mxArrays be passed back into the workspace, the only way - * to achieve that is through this function, which explicitly releases ownership - * of the object, assuming the Matlab interpreter receving the object will delete - * it at a later time - * - * e.g. - * { - * MxArray A(5, 5); // allocates memory - * MxArray B(5, 5); // ditto - * plhs[0] = A; // not allowed!! - * plhs[0] = A.releaseOwnership(); // makes explicit that ownership is being released - * } // end of scope. B is released, A isn't - * - */ - mxArray* releaseOwnership() { - owns_ = false; - return ptr_; - } - - - template - explicit MxArray(size_t m, size_t n, size_t k=1) : owns_(true) { - mwSize dims[] = { static_cast(m), static_cast(n), static_cast(k) }; - ptr_ = mxCreateNumericArray(3, dims, Matlab::Traits::ScalarType, Matlab::Traits<>::Real); - } - - // this function is called exclusively from constructors!! - template - MxArray& fromMat(const cv::Mat& mat) { - // dealloc any existing storage before reallocating - dealloc(); - mwSize dims[] = { static_cast(mat.rows), static_cast(mat.cols), static_cast(mat.channels()) }; - ptr_ = mxCreateNumericArray(3, dims, Matlab::Traits::ScalarType, Matlab::Traits<>::Real); - owns_ = true; - switch (mat.depth()) { - case CV_8U: deepCopyAndTranspose(mat, *this); break; - case CV_8S: deepCopyAndTranspose(mat, *this); break; - case CV_16U: deepCopyAndTranspose(mat, *this); break; - case CV_16S: deepCopyAndTranspose(mat, *this); break; - case CV_32S: deepCopyAndTranspose(mat, *this); break; - case CV_32F: deepCopyAndTranspose(mat, *this); break; - case CV_64F: deepCopyAndTranspose(mat, *this); break; - default: error("Attempted to convert from unknown class"); - } - return *this; - } - - template - cv::Mat toMat() const { - cv::Mat mat(cols(), rows(), CV_MAKETYPE(cv::DataType::type, channels())); - switch (ID()) { - case mxINT8_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxUINT8_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxINT16_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxUINT16_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxINT32_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxUINT32_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxINT64_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxUINT64_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxSINGLE_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxDOUBLE_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxCHAR_CLASS: deepCopyAndTranspose(*this, mat); break; - case mxLOGICAL_CLASS: deepCopyAndTranspose(*this, mat); break; - default: error("Attempted to convert from unknown class"); - } - return mat; - } - - MxArray field(const std::string& name) { return MxArray(mxGetField(ptr_, 0, name.c_str())); } - - template - Scalar* real() { return static_cast(mxGetData(ptr_)); } - - template - Scalar* imag() { return static_cast(mxGetData(ptr_)); } - - template - const Scalar* real() const { return static_cast(mxGetData(ptr_)); } - - template - const Scalar* imag() const { return static_cast(mxGetData(ptr_)); } - - template - Scalar scalar() const { return static_cast(mxGetData(ptr_))[0]; } - - std::string toString() const { - conditionalError(isString(), "Attempted to convert non-string type to string"); - std::string str; - str.reserve(size()+1); - mxGetString(ptr_, const_cast(str.data()), str.size()); - return str; - } - - size_t size() const { return mxGetNumberOfElements(ptr_); } - size_t rows() const { return mxGetM(ptr_); } - size_t cols() const { return mxGetN(ptr_); } - size_t channels() const { return (mxGetNumberOfDimensions(ptr_) > 2) ? mxGetDimensions(ptr_)[2] : 1; } - bool isComplex() const { return mxIsComplex(ptr_); } - bool isNumeric() const { return mxIsNumeric(ptr_); } - bool isLogical() const { return mxIsLogical(ptr_); } - bool isString() const { return mxIsChar(ptr_); } - bool isCell() const { return mxIsCell(ptr_); } - bool isStructure() const { return mxIsStruct(ptr_); } - bool isClass(const std::string& name) const { return mxIsClass(ptr_, name.c_str()); } - std::string className() const { return std::string(mxGetClassName(ptr_)); } - mxClassID ID() const { return mxGetClassID(ptr_); } - -}; - - -/*! - * @brief template specialization for inheriting types - * - * This template specialization attempts to preserve the best mapping - * between OpenCV and Matlab types. Matlab uses double types almost universally, so - * all floating float types are converted to doubles. - * Unfortunately OpenCV does not have a native logical type, so - * that gets mapped to an unsigned 8-bit value - */ -template <> -MxArray& MxArray::fromMat(const cv::Mat& mat) { - switch (mat.depth()) { - case CV_8U: return fromMat(mat); break; - case CV_8S: return fromMat(mat); break; - case CV_16U: return fromMat(mat); break; - case CV_16S: return fromMat(mat); break; - case CV_32S: return fromMat(mat); break; - case CV_32F: return fromMat(mat); break; //NOTE: Matlab uses double as native type! - case CV_64F: return fromMat(mat); break; - default: error("Attempted to convert from unknown class"); - } - return *this; -} - -/*! - * @brief template specialization for inheriting types - * - * This template specialization attempts to preserve the best mapping - * between Matlab and OpenCV types. OpenCV has poor support for double precision - * types, so all floating point types are cast to float. Logicals get cast - * to unsignd 8-bit value. - */ -template <> -cv::Mat MxArray::toMat() const { - switch (ID()) { - case mxINT8_CLASS: return toMat(); - case mxUINT8_CLASS: return toMat();; - case mxINT16_CLASS: return toMat(); - case mxUINT16_CLASS: return toMat(); - case mxINT32_CLASS: return toMat(); - case mxUINT32_CLASS: return toMat(); - case mxINT64_CLASS: return toMat(); - case mxUINT64_CLASS: return toMat(); - case mxSINGLE_CLASS: return toMat(); - case mxDOUBLE_CLASS: return toMat(); //NOTE: OpenCV uses float as native type! - case mxCHAR_CLASS: return toMat(); - case mxLOGICAL_CLASS: return toMat(); - default: error("Attempted to convert from unknown class"); - } - return cv::Mat(); -} - - - -// ---------------------------------------------------------------------------- -// MATRIX TRANSPOSE -// ---------------------------------------------------------------------------- - -template -void deepCopyAndTranspose(const cv::Mat& in, MxArray& out) { - conditionalError(static_cast(in.rows) == out.rows(), "Matrices must have the same number of rows"); - conditionalError(static_cast(in.cols) == out.cols(), "Matrices must have the same number of cols"); - conditionalError(static_cast(in.channels()) == out.channels(), "Matrices must have the same number of channels"); - OutputScalar* outp = out.real(); - const size_t M = out.rows(); - const size_t N = out.cols(); - for (size_t m = 0; m < M; ++m) { - const InputScalar* inp = in.ptr(m); - for (size_t n = 0; n < N; ++n) { - // copy and transpose - outp[m + n*M] = inp[n]; - } - } -} - -template -void deepCopyAndTranspose(const MxArray& in, cv::Mat& out) { - conditionalError(in.rows() == static_cast(out.rows), "Matrices must have the same number of rows"); - conditionalError(in.cols() == static_cast(out.cols), "Matrices must have the same number of cols"); - conditionalError(in.channels() == static_cast(out.channels()), "Matrices must have the same number of channels"); - const InputScalar* inp = in.real(); - const size_t M = in.rows(); - const size_t N = in.cols(); - for (size_t m = 0; m < M; ++m) { - OutputScalar* outp = out.ptr(m); - for (size_t n = 0; n < N; ++n) { - // copy and transpose - outp[n] = inp[m + n*M]; - } - } -} - - -template <> -void deepCopyAndTranspose(const cv::Mat&, MxArray&) { -} - -template <> -void deepCopyAndTranspose(const cv::Mat&, MxArray&) { -} - -template <> -void deepCopyAndTranspose(const MxArray&, cv::Mat&) { - // use mkl -} - -template <> -void deepCopyAndTranspose(const MxArray&, cv::Mat& ) { - // use mkl -} - // ---------------------------------------------------------------------------- @@ -613,7 +170,7 @@ public: // -------------------------------------------------------------------------- // --------------------------- cv::Mat -------------------------------------- - Bridge& operator=(const cv::Mat& mat) { ptr_ = MxArray().fromMat(mat); return *this; } + Bridge& operator=(const cv::Mat& mat) { ptr_ = MxArray::FromMat(mat); return *this; } cv::Mat toMat() const { return ptr_.toMat(); } operator cv::Mat() const { return toMat(); } diff --git a/modules/matlab/include/mxarray.hpp b/modules/matlab/include/mxarray.hpp new file mode 100644 index 0000000000..65ec4ec995 --- /dev/null +++ b/modules/matlab/include/mxarray.hpp @@ -0,0 +1,547 @@ +#ifndef OPENCV_MXARRAY_HPP_ +#define OPENCV_MXARRAY_HPP_ + +#include "mex.h" +#include +#include +#include + +/* + * All recent versions of Matlab ship with the MKL library which contains + * a blas extension called mkl_?omatcopy(). This defines an out-of-place + * copy and transpose operation. + * + * The mkl library is in ${MATLAB_ROOT}/bin/${MATLAB_MEXEXT}/libmkl... + * Matlab does not ship headers for the mkl functions, so we define them + * here. + * + * This operation is used extensively to copy between Matlab's column-major + * format and OpenCV's row-major format. + */ +#ifdef __cplusplus +extern "C" { +#endif + void mkl_somatcopy(char, char, size_t, size_t, const float, const float*, size_t, float*, size_t); + void mkl_domatcopy(char, char, size_t, size_t, const double, const double*, size_t, double*, size_t); +#ifdef __cplusplus +} +#endif + + +/*! + * @brief raise error if condition fails + * + * This is a conditional wrapper for mexErrMsgTxt. If the conditional + * expression fails, an error is raised and the mex function returns + * to Matlab, otherwise this function does nothing + */ +void conditionalError(bool expr, const std::string& str) { + if (!expr) mexErrMsgTxt(std::string("condition failed: ").append(str).c_str()); +} + +/*! + * @brief raise an error + * + * This function is a wrapper around mexErrMsgTxt + */ +void error(const std::string& str) { + mexErrMsgTxt(str.c_str()); +} + + +// ---------------------------------------------------------------------------- +// PREDECLARATIONS +// ---------------------------------------------------------------------------- +class MxArray; + +template +void deepCopyAndTranspose(const cv::Mat& src, MxArray& dst); + +template +void deepCopyAndTranspose(const MxArray& src, cv::Mat& dst); + + + + +// ---------------------------------------------------------------------------- +// MATLAB TRAITS +// ---------------------------------------------------------------------------- +namespace Matlab { + class DefaultTraits {}; + class InheritType {}; + static const int Dynamic = -1; + + template class Traits { + public: + static const mxClassID ScalarType = mxUNKNOWN_CLASS; + static const mxComplexity Complex = mxCOMPLEX; + static const mxComplexity Real = mxCOMPLEX; + static std::string ToString() { return "Unknown/Unsupported"; } + }; + // bool + template<> class Traits { + public: + static const mxClassID ScalarType = mxLOGICAL_CLASS; + static std::string ToString() { return "boolean"; } + }; + // uint8_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxUINT8_CLASS; + static std::string ToString() { return "uint8_t"; } + }; + // int8_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxINT8_CLASS; + static std::string ToString() { return "int8_t"; } + }; + // uint16_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxUINT16_CLASS; + static std::string ToString() { return "uint16_t"; } + }; + // int16_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxINT16_CLASS; + static std::string ToString() { return "int16_t"; } + }; + // uint32_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxUINT32_CLASS; + static std::string ToString() { return "uint32_t"; } + }; + // int32_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxINT32_CLASS; + static std::string ToString() { return "int32_t"; } + }; + // uint64_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxUINT64_CLASS; + static std::string ToString() { return "uint64_t"; } + }; + // int64_t + template<> class Traits { + public: + static const mxClassID ScalarType = mxINT64_CLASS; + static std::string ToString() { return "int64_t"; } + }; + // float + template<> class Traits { + public: + static const mxClassID ScalarType = mxSINGLE_CLASS; + static std::string ToString() { return "float"; } + }; + // double + template<> class Traits { + public: + static const mxClassID ScalarType = mxDOUBLE_CLASS; + static std::string ToString() { return "double"; } + }; + // size_t + template<> class Traits { + public: + static const mxClassID ScalarType = (sizeof(size_t) == 4) ? mxUINT32_CLASS : mxUINT64_CLASS; + static std::string ToString() { return "size_t"; } + }; + // char + template<> class Traits { + public: + static const mxClassID ScalarType = mxCHAR_CLASS; + static std::string ToString() { return "char"; } + }; + // char + template<> class Traits { + public: + static std::string ToString() { return "Inherited type"; } + }; +} + + + +// ---------------------------------------------------------------------------- +// MXARRAY +// ---------------------------------------------------------------------------- + + +/*! + * @class MxArray + * @brief A thin wrapper around Matlab's mxArray types + * + * MxArray provides a thin object oriented wrapper around Matlab's + * native mxArray type which exposes most of the functionality of the + * Matlab interface, but in a more C++ manner. MxArray objects are scoped, + * so you can freely create and destroy them without worrying about memory + * management. If you wish to pass the underlying mxArray* representation + * back to Matlab as an lvalue, see the releaseOwnership() method + * + * MxArrays can be directly converted into OpenCV mat objects and std::string + * objects, since there is a natural mapping between these types. More + * complex types are mapped through the Bridge which does custom conversions + * such as MxArray --> cv::Keypoints, etc + */ +class MxArray { +private: + mxArray* ptr_; + bool owns_; + + /*! + * @brief swap all members of this and other + * + * the swap method is used by the assignment and move constructors + * to swap the members of two MxArrays, leaving both in destructible states + */ + friend void swap(MxArray& first, MxArray& second) { + using std::swap; + swap(first.ptr_, second.ptr_); + swap(first.owns_, second.owns_); + } + + void dealloc() { + if (owns_ && ptr_) { mxDestroyArray(ptr_); ptr_ = NULL; owns_ = false; } + } +public: + // -------------------------------------------------------------------------- + // CONSTRUCTORS + // -------------------------------------------------------------------------- + /*! + * @brief default constructor + * + * Construct a valid 0x0 matrix (so all other methods do not need validity checks + */ + MxArray() : ptr_(mxCreateDoubleMatrix(1, 1, Matlab::Traits<>::Real)), owns_(true) {} + + /*! + * @brief inheriting constructor + * + * Inherit an mxArray from Matlab. Don't claim ownership of the array, + * just encapsulate it + */ + MxArray(const mxArray* ptr) : ptr_(const_cast(ptr)), owns_(false) {} + + /*! + * @brief explicit typed constructor + * + * This constructor explicitly creates an MxArray of the given size and type. + */ + MxArray(size_t m, size_t n, size_t k, mxClassID id, mxComplexity com = Matlab::Traits<>::Real) : owns_(true) { + mwSize dims[] = { static_cast(m), static_cast(n), static_cast(k) }; + ptr_ = mxCreateNumericArray(3, dims, id, com); + } + + /*! + * @brief explicit tensor constructor + * + * Explicitly construct a tensor of given size and type. Since constructors cannot + * be explicitly templated, this is a static factory method + */ + template + static MxArray Tensor(size_t m, size_t n, size_t k=1) { + return MxArray(m, n, k, Matlab::Traits::ScalarType); + } + + /*! + * @brief explicit matrix constructor + * + * Explicitly construct a matrix of given size and type. Since constructors cannot + * be explicitly templated, this is a static factory method + */ + template + static MxArray Matrix(size_t m, size_t n) { + return MxArray(m, n, 1, Matlab::Traits::ScalarType); + } + + /*! + * @brief explicit vector constructor + * + * Explicitly construct a vector of given size and type. Since constructors cannot + * be explicitly templated, this is a static factory method + */ + template + static MxArray Vector(size_t m) { + return MxArray(m, 1, 1, Matlab::Traits::ScalarType); + } + + /*! + * @brief explicit scalar constructor + * + * Explicitly construct a scalar of given type. Since constructors cannot + * be explicitly templated, this is a static factory method + */ + template + static MxArray Scalar(Scalar value = 0) { + MxArray s(1, 1, 1, Matlab::Traits::ScalarType); + s.real()[0] = value; + return s; + } + + /*! + * @brief destructor + * + * The destructor deallocates any data allocated by mxCreate* methods only + * if the object is owned + */ + virtual ~MxArray() { + dealloc(); + } + + /*! + * @brief copy constructor + * + * All copies are deep copies. If you have a C++11 compatible compiler, prefer + * move construction to copy construction + */ + MxArray(const MxArray& other) : ptr_(mxDuplicateArray(other.ptr_)), owns_(true) {} + + /*! + * @brief copy-and-swap assignment + * + * This assignment operator uses the copy and swap idiom to provide a strong + * exception guarantee when swapping two objects. + * + * Note in particular that the other MxArray is passed by value, thus invoking + * the copy constructor which performs a deep copy of the input. The members of + * this and other are then swapped + */ + MxArray& operator=(MxArray other) { + swap(*this, other); + return *this; + } +#if __cplusplus >= 201103L + /* + * @brief C++11 move constructor + * + * When C++11 support is available, move construction is used to move returns + * out of functions, etc. This is much fast than copy construction, since the + * move constructed object replaced itself with a default constructed MxArray, + * which is of size 0 x 0. + */ + MxArray(MxArray&& other) : MxArray() { + swap(*this, other); + } +#endif + + /* + * @brief release ownership to allow return into Matlab workspace + * + * MxArray is not directly convertible back to mxArray types through assignment + * because the MxArray may have been allocated on the free store, making it impossible + * to know whether the returned pointer will be released by someone else or not. + * + * Since Matlab requires mxArrays be passed back into the workspace, the only way + * to achieve that is through this function, which explicitly releases ownership + * of the object, assuming the Matlab interpreter receving the object will delete + * it at a later time + * + * e.g. + * { + * MxArray A(5, 5); // allocates memory + * MxArray B(5, 5); // ditto + * plhs[0] = A; // not allowed!! + * plhs[0] = A.releaseOwnership(); // makes explicit that ownership is being released + * } // end of scope. B is released, A isn't + * + */ + mxArray* releaseOwnership() { + owns_ = false; + return ptr_; + } + + + template + static MxArray FromMat(const cv::Mat& mat) { + MxArray arr(mat.rows, mat.cols, mat.channels(), Matlab::Traits::ScalarType); + switch (mat.depth()) { + case CV_8U: deepCopyAndTranspose(mat, arr); break; + case CV_8S: deepCopyAndTranspose(mat, arr); break; + case CV_16U: deepCopyAndTranspose(mat, arr); break; + case CV_16S: deepCopyAndTranspose(mat, arr); break; + case CV_32S: deepCopyAndTranspose(mat, arr); break; + case CV_32F: deepCopyAndTranspose(mat, arr); break; + case CV_64F: deepCopyAndTranspose(mat, arr); break; + default: error("Attempted to convert from unknown class"); + } + return arr; + } + + template + cv::Mat toMat() const { + cv::Mat mat(cols(), rows(), CV_MAKETYPE(cv::DataType::type, channels())); + switch (ID()) { + case mxINT8_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxUINT8_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxINT16_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxUINT16_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxINT32_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxUINT32_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxINT64_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxUINT64_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxSINGLE_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxDOUBLE_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxCHAR_CLASS: deepCopyAndTranspose(*this, mat); break; + case mxLOGICAL_CLASS: deepCopyAndTranspose(*this, mat); break; + default: error("Attempted to convert from unknown class"); + } + return mat; + } + + MxArray field(const std::string& name) { return MxArray(mxGetField(ptr_, 0, name.c_str())); } + + template + Scalar* real() { return static_cast(mxGetData(ptr_)); } + + template + Scalar* imag() { return static_cast(mxGetData(ptr_)); } + + template + const Scalar* real() const { return static_cast(mxGetData(ptr_)); } + + template + const Scalar* imag() const { return static_cast(mxGetData(ptr_)); } + + template + Scalar scalar() const { return static_cast(mxGetData(ptr_))[0]; } + + std::string toString() const { + conditionalError(isString(), "Attempted to convert non-string type to string"); + std::string str; + str.reserve(size()+1); + mxGetString(ptr_, const_cast(str.data()), str.size()); + return str; + } + + size_t size() const { return mxGetNumberOfElements(ptr_); } + size_t rows() const { return mxGetM(ptr_); } + size_t cols() const { return mxGetN(ptr_); } + size_t channels() const { return (mxGetNumberOfDimensions(ptr_) > 2) ? mxGetDimensions(ptr_)[2] : 1; } + bool isComplex() const { return mxIsComplex(ptr_); } + bool isNumeric() const { return mxIsNumeric(ptr_); } + bool isLogical() const { return mxIsLogical(ptr_); } + bool isString() const { return mxIsChar(ptr_); } + bool isCell() const { return mxIsCell(ptr_); } + bool isStructure() const { return mxIsStruct(ptr_); } + bool isClass(const std::string& name) const { return mxIsClass(ptr_, name.c_str()); } + std::string className() const { return std::string(mxGetClassName(ptr_)); } + mxClassID ID() const { return mxGetClassID(ptr_); } + +}; + + +/*! + * @brief template specialization for inheriting types + * + * This template specialization attempts to preserve the best mapping + * between OpenCV and Matlab types. Matlab uses double types almost universally, so + * all floating float types are converted to doubles. + * Unfortunately OpenCV does not have a native logical type, so + * that gets mapped to an unsigned 8-bit value + */ +template <> +MxArray MxArray::FromMat(const cv::Mat& mat) { + switch (mat.depth()) { + case CV_8U: return FromMat(mat); + case CV_8S: return FromMat(mat); + case CV_16U: return FromMat(mat); + case CV_16S: return FromMat(mat); + case CV_32S: return FromMat(mat); + case CV_32F: return FromMat(mat); //NOTE: Matlab uses double as native type! + case CV_64F: return FromMat(mat); + default: error("Attempted to convert from unknown class"); + } + return MxArray(); +} + +/*! + * @brief template specialization for inheriting types + * + * This template specialization attempts to preserve the best mapping + * between Matlab and OpenCV types. OpenCV has poor support for double precision + * types, so all floating point types are cast to float. Logicals get cast + * to unsignd 8-bit value. + */ +template <> +cv::Mat MxArray::toMat() const { + switch (ID()) { + case mxINT8_CLASS: return toMat(); + case mxUINT8_CLASS: return toMat();; + case mxINT16_CLASS: return toMat(); + case mxUINT16_CLASS: return toMat(); + case mxINT32_CLASS: return toMat(); + case mxUINT32_CLASS: return toMat(); + case mxINT64_CLASS: return toMat(); + case mxUINT64_CLASS: return toMat(); + case mxSINGLE_CLASS: return toMat(); + case mxDOUBLE_CLASS: return toMat(); //NOTE: OpenCV uses float as native type! + case mxCHAR_CLASS: return toMat(); + case mxLOGICAL_CLASS: return toMat(); + default: error("Attempted to convert from unknown class"); + } + return cv::Mat(); +} + + + +// ---------------------------------------------------------------------------- +// MATRIX TRANSPOSE +// ---------------------------------------------------------------------------- + +template +void deepCopyAndTranspose(const cv::Mat& in, MxArray& out) { + conditionalError(static_cast(in.rows) == out.rows(), "Matrices must have the same number of rows"); + conditionalError(static_cast(in.cols) == out.cols(), "Matrices must have the same number of cols"); + conditionalError(static_cast(in.channels()) == out.channels(), "Matrices must have the same number of channels"); + OutputScalar* outp = out.real(); + const size_t M = out.rows(); + const size_t N = out.cols(); + for (size_t m = 0; m < M; ++m) { + const InputScalar* inp = in.ptr(m); + for (size_t n = 0; n < N; ++n) { + // copy and transpose + outp[m + n*M] = inp[n]; + } + } +} + +template +void deepCopyAndTranspose(const MxArray& in, cv::Mat& out) { + conditionalError(in.rows() == static_cast(out.rows), "Matrices must have the same number of rows"); + conditionalError(in.cols() == static_cast(out.cols), "Matrices must have the same number of cols"); + conditionalError(in.channels() == static_cast(out.channels()), "Matrices must have the same number of channels"); + const InputScalar* inp = in.real(); + const size_t M = in.rows(); + const size_t N = in.cols(); + for (size_t m = 0; m < M; ++m) { + OutputScalar* outp = out.ptr(m); + for (size_t n = 0; n < N; ++n) { + // copy and transpose + outp[n] = inp[m + n*M]; + } + } +} + + +template <> +void deepCopyAndTranspose(const cv::Mat&, MxArray&) { +} + +template <> +void deepCopyAndTranspose(const cv::Mat&, MxArray&) { +} + +template <> +void deepCopyAndTranspose(const MxArray&, cv::Mat&) { + // use mkl +} + +template <> +void deepCopyAndTranspose(const MxArray&, cv::Mat& ) { + // use mkl +} + +#endif