//////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this // license. If you do not agree to this license, do not download, install, // copy or use the software. // // // License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2013, OpenCV Foundation, all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of the copyright holders may not be used to endorse or promote // products derived from this software without specific prior written // permission. // // This software is provided by the copyright holders and contributors "as is" // and any express or implied warranties, including, but not limited to, the // implied warranties of merchantability and fitness for a particular purpose // are disclaimed. In no event shall the Intel Corporation or contributors be // liable for any direct, indirect, incidental, special, exemplary, or // consequential damages (including, but not limited to, procurement of // substitute goods or services; loss of use, data, or profits; or business // interruption) however caused and on any theory of liability, whether in // contract, strict liability, or tort (including negligence or otherwise) // arising in any way out of the use of this software, even if advised of the // possibility of such damage. // //////////////////////////////////////////////////////////////////////////////// #ifndef OPENCV_MXARRAY_HPP_ #define OPENCV_MXARRAY_HPP_ #include #include #include #include #include #include #include #if __cplusplus > 201103 #include typedef std::unordered_set StringSet; #else #include typedef std::set StringSet; #endif /* * 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. * */ #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif namespace matlab { // ---------------------------------------------------------------------------- // PREDECLARATIONS // ---------------------------------------------------------------------------- class MxArray; typedef std::vector MxArrayVector; /*! * @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 */ static 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 */ static void error(const std::string& str) { mexErrMsgTxt(str.c_str()); } // ---------------------------------------------------------------------------- // MATLAB TRAITS // ---------------------------------------------------------------------------- class DefaultTraits {}; class InheritType {}; template class Traits { public: static const mxClassID ScalarType = mxUNKNOWN_CLASS; static const mxComplexity Complex = mxCOMPLEX; static const mxComplexity Real = mxREAL; 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"; } }; // char template<> class Traits { public: static const mxClassID ScalarType = mxCHAR_CLASS; static std::string ToString() { return "char"; } }; // inherited type 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(0, 0, matlab::Traits<>::Real)), owns_(true) {} /*! * @brief destructor * * The destructor deallocates any data allocated by mxCreate* methods only * if the object is owned */ virtual ~MxArray() { dealloc(); } /*! * @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) {} MxArray& operator=(const mxArray* ptr) { dealloc(); ptr_ = const_cast(ptr); owns_ = false; return *this; } /*! * @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) : ptr_(NULL), 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(ScalarType value = 0) { MxArray s(1, 1, 1, matlab::Traits::ScalarType); s.real()[0] = value; return s; } /*! * @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 = MxArray::Matrix(5, 5); // allocates memory * MxArray B = MxArray::Matrix(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_; } 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(mxGetImagData(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(size(), '\0'); mxGetString(ptr_, const_cast(str.data()), str.size()+1); return str; } size_t size() const { return mxGetNumberOfElements(ptr_); } bool empty() const { return size() == 0; } size_t rows() const { return mxGetDimensions(ptr_)[0]; } size_t cols() const { return mxGetDimensions(ptr_)[1]; } 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_); } }; // ---------------------------------------------------------------------------- // ARGUMENT PARSER // ---------------------------------------------------------------------------- /*! @class ArgumentParser * @brief parses inputs to a method and resolves the argument names. * * The ArgumentParser resolves the inputs to a method. It checks that all * required arguments are specified and also allows named optional arguments. * For example, the C++ function: * void randn(Mat& mat, Mat& mean=Mat(), Mat& std=Mat()); * could be called in Matlab using any of the following signatures: * \code * out = randn(in); * out = randn(in, 0, 1); * out = randn(in, 'mean', 0, 'std', 1); * \endcode * * ArgumentParser also enables function overloading by allowing users * to add variants to a method. For example, there may be two C++ sum() methods: * \code * double sum(Mat& mat); % sum elements of a matrix * Mat sum(Mat& A, Mat& B); % add two matrices * \endcode * * by adding two variants to ArgumentParser, the correct underlying sum * method can be called. If the function call is ambiguous, the * ArgumentParser will fail with an error message. * * The previous example could be parsed as: * \code * // set up the Argument parser * ArgumentParser arguments; * arguments.addVariant("elementwise", 1); * arguments.addVariant("matrix", 2); * * // parse the arguments * std::vector inputs; * inputs = arguments.parse(std::vector(prhs, prhs+nrhs)); * * // if we get here, one unique variant is valid * if (arguments.variantIs("elementwise")) { * // call elementwise sum() * } * \endcode */ class ArgumentParser { private: struct Variant; typedef std::string String; typedef std::vector StringVector; typedef std::vector IndexVector; typedef std::vector VariantVector; /* @class Variant * @brief Describes a variant of arguments to a method * * When addVariant() is called on an instance to ArgumentParser, this class * holds the the information that decribes that variant. The parse() method * of ArgumentParser then attempts to match a Variant, given a set of * inputs for a method invocation. */ class Variant { private: String name_; size_t Nreq_; size_t Nopt_; StringVector keys_; IndexVector order_; bool valid_; size_t nparsed_; size_t nkeys_; size_t working_opt_; bool expecting_val_; bool using_named_; size_t find(const String& key) const { return std::find(keys_.begin(), keys_.end(), key) - keys_.begin(); } public: /*! @brief default constructor */ Variant() : Nreq_(0), Nopt_(0), valid_(false) {} /*! @brief construct a new variant spec */ Variant(const String& name, size_t Nreq, size_t Nopt, const StringVector& keys) : name_(name), Nreq_(Nreq), Nopt_(Nopt), keys_(keys), order_(Nreq+Nopt, Nreq+2*Nopt), valid_(true), nparsed_(0), nkeys_(0), working_opt_(0), expecting_val_(false), using_named_(false) {} /*! @brief the name of the variant */ String name() const { return name_; } /*! @brief return the total number of arguments the variant can take */ size_t size() const { return Nreq_ + Nopt_; } /*! @brief has the variant been fulfilled? */ bool fulfilled() const { return (valid_ && nparsed_ >= Nreq_ && !expecting_val_); } /*! @brief is the variant in a valid state (though not necessarily fulfilled) */ bool valid() const { return valid_; } /*! @brief check if the named argument exists in the variant */ bool exist(const String& key) const { return find(key) != keys_.size(); } /*! @brief retrieve the order mapping raw inputs to their position in the variant */ const IndexVector& order() const { return order_; } size_t order(size_t n) const { return order_[n]; } /*! @brief attempt to parse the next argument as a value */ bool parseNextAsValue() { if (!valid_) {} else if ((using_named_ && !expecting_val_) || (nparsed_-nkeys_ == Nreq_+Nopt_)) { valid_ = false; } else if (nparsed_ < Nreq_) { order_[nparsed_] = nparsed_; } else if (!using_named_) { order_[nparsed_] = nparsed_; } else if (using_named_ && expecting_val_) { order_[Nreq_ + working_opt_] = nparsed_; } nparsed_++; expecting_val_ = false; return valid_; } /*! @biref attempt to parse the next argument as a name (key) */ bool parseNextAsKey(const String& key) { if (!valid_) {} else if ((nparsed_ < Nreq_) || (nparsed_-nkeys_ == Nreq_+Nopt_)) { valid_ = false; } else if (using_named_ && expecting_val_) { valid_ = false; } else if ((working_opt_ = find(key)) == keys_.size()) { valid_ = false; } else { using_named_ = true; expecting_val_ = true; nkeys_++; nparsed_++; } return valid_; } String toString(const String& method_name="f") const { int req_begin = 0, req_end = 0, opt_begin = 0, opt_end = 0; std::ostringstream s; // f(...) s << method_name << "("; // required arguments req_begin = s.str().size(); for (size_t n = 0; n < Nreq_; ++n) { s << "src" << n+1 << (n != Nreq_-1 ? ", " : ""); } req_end = s.str().size(); if (Nreq_ && Nopt_) s << ", "; // optional arguments opt_begin = s.str().size(); for (size_t n = 0; n < keys_.size(); ++n) { s << "'" << keys_[n] << "', " << keys_[n] << (n != Nopt_-1 ? ", " : ""); } opt_end = s.str().size(); s << ");"; if (Nreq_ + Nopt_ == 0) return s.str(); // underscores String under = String(req_begin, ' ') + String(req_end-req_begin, '-') + String(std::max(opt_begin-req_end,0), ' ') + String(opt_end-opt_begin, '-'); s << "\n" << under; // required and optional sets String req_set(req_end-req_begin, ' '); String opt_set(opt_end-opt_begin, ' '); if (!req_set.empty() && req_set.size() < 8) req_set.replace((req_set.size()-3)/2, 3, "req"); if (req_set.size() > 7) req_set.replace((req_set.size()-8)/2, 8, "required"); if (!opt_set.empty() && opt_set.size() < 8) opt_set.replace((opt_set.size()-3)/2, 3, "opt"); if (opt_set.size() > 7) opt_set.replace((opt_set.size()-8)/2, 8, "optional"); String set = String(req_begin, ' ') + req_set + String(std::max(opt_begin-req_end,0), ' ') + opt_set; s << "\n" << set; return s.str(); } }; /*! @brief given an input and output vector of arguments, and a variant spec, sort */ void sortArguments(Variant& v, MxArrayVector& in, MxArrayVector& out) { // allocate the output array with ALL arguments out.resize(v.size()); // reorder the inputs based on the variant ordering for (size_t n = 0; n < v.size(); ++n) { if (v.order(n) >= in.size()) continue; swap(in[v.order(n)], out[n]); } } VariantVector variants_; String valid_; String method_name_; public: ArgumentParser(const String& method_name) : method_name_(method_name) {} /*! @brief add a function call variant to the parser * * Adds a function-call signature to the parser. The function call *must* be * unique either in its number of arguments, or in the named-syntax. * Currently this function does not check whether that invariant stands true. * * This function is variadic. If should be called as follows: * addVariant(2, 2, 'opt_1_name', 'opt_2_name'); */ void addVariant(const String& name, size_t nreq, size_t nopt = 0, ...) { StringVector keys; va_list opt; va_start(opt, nopt); for (size_t n = 0; n < nopt; ++n) keys.push_back(va_arg(opt, const char*)); addVariant(name, nreq, nopt, keys); } void addVariant(const String& name, size_t nreq, size_t nopt, StringVector keys) { variants_.push_back(Variant(name, nreq, nopt, keys)); } /*! @brief check if the valid variant is the key name */ bool variantIs(const String& name) { return name.compare(valid_) == 0; } /*! @brief parse a vector of input arguments * * This method parses a vector of input arguments, attempting to match them * to a Variant spec. For each input, the method attempts to cull any * Variants which don't match the given inputs so far. * * Once all inputs have been parsed, if there is one unique spec remaining, * the output MxArray vector gets populated with the arguments, with named * arguments removed. Any optional arguments that have not been encountered * are set to an empty array. * * If multiple variants or no variants match the given call, an error * message is emitted */ MxArrayVector parse(const MxArrayVector& inputs) { // allocate the outputs String variant_string; MxArrayVector outputs; VariantVector candidates = variants_; // iterate over the inputs, attempting to match a variant for (MxArrayVector::const_iterator input = inputs.begin(); input != inputs.end(); ++input) { String name = input->isString() ? input->toString() : String(); for (VariantVector::iterator candidate = candidates.begin(); candidate < candidates.end(); ++candidate) { candidate->exist(name) ? candidate->parseNextAsKey(name) : candidate->parseNextAsValue(); } } // make sure the candidates have been fulfilled for (VariantVector::iterator candidate = candidates.begin(); candidate < candidates.end(); ++candidate) { if (!candidate->fulfilled()) candidate = candidates.erase(candidate)--; } // if there is not a unique candidate, throw an error for (VariantVector::iterator variant = variants_.begin(); variant != variants_.end(); ++variant) { variant_string += "\n" + variant->toString(method_name_); } // if there is not a unique candidate, throw an error if (candidates.size() > 1) { error(String("Call to method is ambiguous. Valid variants are:") .append(variant_string).append("\nUse named arguments to disambiguate call")); } if (candidates.size() == 0) { error(String("No matching method signatures for given arguments. Valid variants are:").append(variant_string)); } // Unique candidate! valid_ = candidates[0].name(); sortArguments(candidates[0], const_cast(inputs), outputs); return outputs; } }; } // namespace matlab #endif