From f5105bac653423900f255c5f2666478c2fdc27b7 Mon Sep 17 00:00:00 2001 From: Zhuo Zhang Date: Mon, 21 Feb 2022 20:39:41 +0800 Subject: [PATCH 1/3] remove const in seamless_cloding APIs for better semantics --- modules/photo/src/seamless_cloning.hpp | 6 +++--- modules/photo/src/seamless_cloning_impl.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/photo/src/seamless_cloning.hpp b/modules/photo/src/seamless_cloning.hpp index 92b24e7b09..4d43970d2d 100644 --- a/modules/photo/src/seamless_cloning.hpp +++ b/modules/photo/src/seamless_cloning.hpp @@ -53,7 +53,7 @@ namespace cv class Cloning { public: - void normalClone(const cv::Mat& destination, const cv::Mat &mask, const cv::Mat &wmask, cv::Mat &cloned, int flag); + void normalClone(const cv::Mat& destination, const cv::Mat &mask, cv::Mat &wmask, cv::Mat &cloned, int flag); void illuminationChange(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, cv::Mat &cloned, float alpha, float beta); void localColorChange(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, cv::Mat &cloned, float red_mul, float green_mul, float blue_mul); void textureFlatten(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, float low_threshold, float high_threhold, int kernel_size, cv::Mat &cloned); @@ -61,10 +61,10 @@ namespace cv protected: void initVariables(const cv::Mat &destination, const cv::Mat &binaryMask); - void computeDerivatives(const cv::Mat &destination, const cv::Mat &patch, const cv::Mat &binaryMask); + void computeDerivatives(const cv::Mat &destination, const cv::Mat &patch, cv::Mat &binaryMask); void scalarProduct(cv::Mat mat, float r, float g, float b); void poisson(const cv::Mat &destination); - void evaluate(const cv::Mat &I, const cv::Mat &wmask, const cv::Mat &cloned); + void evaluate(const cv::Mat &I, cv::Mat &wmask, const cv::Mat &cloned); void dst(const Mat& src, Mat& dest, bool invert = false); void solve(const Mat &img, Mat& mod_diff, Mat &result); diff --git a/modules/photo/src/seamless_cloning_impl.cpp b/modules/photo/src/seamless_cloning_impl.cpp index 8fd4bc7865..4b3258a1d9 100644 --- a/modules/photo/src/seamless_cloning_impl.cpp +++ b/modules/photo/src/seamless_cloning_impl.cpp @@ -246,7 +246,7 @@ void Cloning::initVariables(const Mat &destination, const Mat &binaryMask) filter_Y[j] = 2.0f * (float)std::cos(scale * (j + 1)); } -void Cloning::computeDerivatives(const Mat& destination, const Mat &patch, const Mat &binaryMask) +void Cloning::computeDerivatives(const Mat& destination, const Mat &patch, Mat &binaryMask) { initVariables(destination, binaryMask); @@ -306,7 +306,7 @@ void Cloning::poisson(const Mat &destination) } } -void Cloning::evaluate(const Mat &I, const Mat &wmask, const Mat &cloned) +void Cloning::evaluate(const Mat &I, Mat &wmask, const Mat &cloned) { bitwise_not(wmask,wmask); @@ -320,7 +320,7 @@ void Cloning::evaluate(const Mat &I, const Mat &wmask, const Mat &cloned) merge(output,cloned); } -void Cloning::normalClone(const Mat &destination, const Mat &patch, const Mat &binaryMask, Mat &cloned, int flag) +void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryMask, Mat &cloned, int flag) { const int w = destination.cols; const int h = destination.rows; From 375fe81311171349f4d1572286eabdeb126b69b0 Mon Sep 17 00:00:00 2001 From: Egor Smirnov Date: Thu, 17 Feb 2022 20:30:44 +0300 Subject: [PATCH 2/3] fix slice and expand --- modules/dnn/src/darknet/darknet_io.cpp | 2 +- modules/dnn/src/layers/slice_layer.cpp | 52 +++++++++++++++------- modules/dnn/src/onnx/onnx_importer.cpp | 23 +++++----- modules/dnn/src/tensorflow/tf_importer.cpp | 13 +++--- modules/dnn/src/torch/torch_importer.cpp | 2 +- modules/dnn/test/test_layers.cpp | 2 +- 6 files changed, 58 insertions(+), 36 deletions(-) diff --git a/modules/dnn/src/darknet/darknet_io.cpp b/modules/dnn/src/darknet/darknet_io.cpp index 99715df829..11aad453e3 100644 --- a/modules/dnn/src/darknet/darknet_io.cpp +++ b/modules/dnn/src/darknet/darknet_io.cpp @@ -376,7 +376,7 @@ namespace cv { int begin[] = {0, split_size * group_id, 0, 0}; cv::dnn::DictValue paramBegin = cv::dnn::DictValue::arrayInt(begin, 4); - int end[] = {-1, begin[1] + split_size, -1, -1}; + int end[] = {INT_MAX, begin[1] + split_size, INT_MAX, INT_MAX}; cv::dnn::DictValue paramEnd = cv::dnn::DictValue::arrayInt(end, 4); darknet::LayerParameter lp; diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index a470772813..71de70e93f 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -58,12 +58,32 @@ namespace cv namespace dnn { -void sliceRangesFromShape(const MatShape& inpShape, int& axis, std::vector >& sliceRanges) +Range normalizeRange(const Range& input_range, int n) { + Range range = input_range; + + range.start = std::min(std::max(range.start, -n), n - 1); + if (range.start < 0) + { + range.start += n; + } + + range.end = std::min(std::max(range.end, -n), n); + if (range.end < 0) + { + range.end += n; + } + + return range; +} + +std::vector > finalizeSliceRange(const MatShape& inpShape, int& axis, + const std::vector >& inputSliceRanges) +{ + std::vector > sliceRanges = inputSliceRanges; CV_Assert(inpShape.size() > 0); bool axisNeg = (axis < 0); axis = (axis + static_cast(inpShape.size())) % inpShape.size(); - int n = inpShape[axis]; for (size_t i = 0; i < sliceRanges.size(); ++i){ std::vector& ranges = sliceRanges[i]; @@ -71,16 +91,20 @@ void sliceRangesFromShape(const MatShape& inpShape, int& axis, std::vector= 0) + for (size_t j = 0; j < ranges.size(); ++j) { - continue; - } + int n = inpShape[j]; + if (n <= 0) + { + continue; + } - CV_Assert(n != 0); - range.start = (n + range.start) % n; + ranges[j] = normalizeRange(ranges[j], n); + } } + + return sliceRanges; } class SliceLayerImpl : public SliceLayer @@ -130,7 +154,7 @@ public: { int size = sizeOrEnd; CV_Assert(size == -1 || size > 0); // -1 value means range [start, axis_size). - sliceRanges[0][i].end = size > 0 ? (start + size) : -1; // We'll finalize a negative value later. + sliceRanges[0][i].end = size > 0 ? (start + size) : INT_MAX; // We'll finalize a negative value later. } else { @@ -181,8 +205,7 @@ public: MatShape inpShape = inputs[0]; int axis_rw = axis; - std::vector > sliceRanges_rw = sliceRanges; - sliceRangesFromShape(inpShape, axis_rw, sliceRanges_rw); + std::vector > sliceRanges_rw = finalizeSliceRange(inpShape, axis_rw, sliceRanges); if (!sliceRanges_rw.empty()) { @@ -193,7 +216,7 @@ public: for (int j = 0; j < sliceRanges_rw[i].size(); ++j) { if (shapesInitialized || inpShape[j] > 0) - outputs[i][j] = normalize_axis_range(sliceRanges_rw[i][j], inpShape[j]).size(); + outputs[i][j] = normalizeRange(sliceRanges_rw[i][j], inpShape[j]).size(); if (!sliceSteps.empty() && (i < sliceSteps.size()) && (j < sliceSteps[i].size()) && (sliceSteps[i][j] > 1)) outputs[i][j] = (outputs[i][j] + sliceSteps[i][j] - 1) / sliceSteps[i][j]; @@ -230,8 +253,7 @@ public: CV_Assert(inputs.size() == 1); const MatSize& inpShape = inputs[0].size; - sliceRangesFromShape(shape(inputs[0]), axis, sliceRanges); - finalSliceRanges = sliceRanges; + finalSliceRanges = finalizeSliceRange(shape(inputs[0]), axis, sliceRanges); if (sliceRanges.empty()) { @@ -261,7 +283,7 @@ public: // Clamp. for (int j = 0; j < finalSliceRanges[i].size(); ++j) { - finalSliceRanges[i][j] = normalize_axis_range(finalSliceRanges[i][j], inpShape[j]); + finalSliceRanges[i][j] = normalizeRange(finalSliceRanges[i][j], inpShape[j]); } } diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 736f3a27de..e753fbb103 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -1013,13 +1013,12 @@ void ONNXImporter::parseSlice(LayerParams& layerParams, const opencv_onnx::NodeP if (axis > 0) { begin.resize(axis, 0); - end.resize(axis, -1); + end.resize(axis, INT_MAX); } for (int i = 0; i < starts.size(); ++i) { begin.push_back(starts.get(i)); - int finish = ends.get(i); - end.push_back((finish < 0) ? --finish : finish); // numpy doesn't include last dim + end.push_back(ends.get(i)); } } else { // inp_size > 1 CV_Assert(inp_size >= 3); @@ -1043,14 +1042,10 @@ void ONNXImporter::parseSlice(LayerParams& layerParams, const opencv_onnx::NodeP const int* ends = end_blob.ptr(); if (axis > 0) { begin.resize(axis, 0); - end.resize(axis, -1); + end.resize(axis, INT_MAX); } std::copy(starts, starts + start_blob.total(), std::back_inserter(begin)); - for (int i = 0; i < end_blob.total(); ++i) - { - int finish = ends[i]; - end.push_back((finish < 0) ? --finish : finish); // numpy doesn't include last dim - } + std::copy(ends, ends + end_blob.total(), std::back_inserter(end)); if (inp_size == 5) { CV_Assert(constBlobs.find(node_proto.input(4)) != constBlobs.end()); @@ -2133,9 +2128,15 @@ void ONNXImporter::parseExpand(LayerParams& layerParams, const opencv_onnx::Node if (!haveVariables) { - if (broadcast_axes.size() != 1) + if (broadcast_axes.size() > 1) CV_Error(Error::StsNotImplemented, "Expand op doesn't support multiple axes for constant input"); + if (broadcast_axes.empty()) + { + addConstant(output_name, getBlob(node_proto, 0)); + return; + } + Mat input = getBlob(node_proto, 0); input = input.reshape(0, total(inpShape, 0, broadcast_axes[0])); Mat output = cv::repeat(input, 1, targetShape[broadcast_axes[0]]); @@ -2354,7 +2355,7 @@ void ONNXImporter::parseGather(LayerParams& layerParams, const opencv_onnx::Node sliceLp.type = "Slice"; sliceLp.name = inpShape.size() > 1 ? layerParams.name + "/slice" : layerParams.name; std::vector begin(inpShape.size(), 0); - std::vector end(inpShape.size(), -1); + std::vector end(inpShape.size(), INT_MAX); begin[axis] = index; end[axis] = index + 1; diff --git a/modules/dnn/src/tensorflow/tf_importer.cpp b/modules/dnn/src/tensorflow/tf_importer.cpp index 8cbe1c4b23..f9e129622c 100644 --- a/modules/dnn/src/tensorflow/tf_importer.cpp +++ b/modules/dnn/src/tensorflow/tf_importer.cpp @@ -1490,10 +1490,8 @@ void TFImporter::parseStridedSlice(tensorflow::GraphDef& net, const tensorflow:: int end_mask = getLayerAttr(layer, "end_mask").i(); for (int i = 0; i < num; ++i) { - if (ends.at(i) < 0) - ends.at(i) -= 1; if (end_mask & (1 << i)) - ends.at(i) = -1; + ends.at(i) = INT_MAX; if (strides.at(i) != 1) CV_Error(Error::StsNotImplemented, format("StridedSlice with stride %d", strides.at(i))); @@ -1791,15 +1789,16 @@ void TFImporter::parseConv2DBackpropInput(tensorflow::GraphDef& net, const tenso int64_t pads[8]; bool explicit_pads = getExplicitPadding(layerParams, layer, pads); int64_t begs[4] = {}; - int64_t ends[4] = {-1, -1, -1, -1}; + int64_t ends[4] = {}; if (explicit_pads) { name += "/deconv"; layerParams.set("pad_mode", "VALID"); + ends[0] = ends[1] = INT_MAX; for (int i = 2; i < 4; ++i) // begins=[0, 0, a, b], ends=[-1, -1, c, d] { begs[i] = pads[2*i]; - ends[i] = -1 - pads[2*i + 1]; + ends[i] = -pads[2*i + 1]; } } @@ -1819,8 +1818,8 @@ void TFImporter::parseConv2DBackpropInput(tensorflow::GraphDef& net, const tenso const int strideX = layerParams.get("stride_w"); Mat outShape = getTensorContent(getConstBlob(layer, value_id, 0)); int shift = (getDataLayout(layer) == DATA_LAYOUT_NCHW); - const int outH = outShape.at(1 + shift) + begs[2] - 1 - ends[2]; - const int outW = outShape.at(2 + shift) + begs[3] - 1 - ends[3]; + const int outH = outShape.at(1 + shift) + begs[2] - ends[2]; + const int outW = outShape.at(2 + shift) + begs[3] - ends[3]; if (layerParams.get("pad_mode") == "SAME") { layerParams.set("adj_w", (outW - 1) % strideX); diff --git a/modules/dnn/src/torch/torch_importer.cpp b/modules/dnn/src/torch/torch_importer.cpp index 5dd9e3e290..e595e993ef 100644 --- a/modules/dnn/src/torch/torch_importer.cpp +++ b/modules/dnn/src/torch/torch_importer.cpp @@ -949,7 +949,7 @@ struct TorchImporter int size = scalarParams.get("size"); int begins[] = {0, 0, size, size}; - int ends[] = {-1, -1, -size - 1, -size - 1}; + int ends[] = {INT_MAX, INT_MAX, -size, -size}; newModule->apiType = "Slice"; layerParams.set("begin", DictValue::arrayInt(&begins[0], 4)); diff --git a/modules/dnn/test/test_layers.cpp b/modules/dnn/test/test_layers.cpp index 2d4f78c88c..2b17e6fa24 100644 --- a/modules/dnn/test/test_layers.cpp +++ b/modules/dnn/test/test_layers.cpp @@ -2028,7 +2028,7 @@ TEST_P(Layer_Test_Slice, variable_input_shape) int targetId = get<1>(GetParam()); int begin[] = {0, 0, 0, 0}; - int end[] = {-1, -1, -1, -1}; + int end[] = {INT_MAX, INT_MAX, INT_MAX, INT_MAX}; Net net; LayerParams lp; From ccebbbc0acf3d0c3666eb1b2f79753f33ec8b374 Mon Sep 17 00:00:00 2001 From: Vadim Levin Date: Thu, 20 Jan 2022 15:21:47 +0300 Subject: [PATCH 3/3] feature: submodule or a class scope for exported classes All classes are registered in the scope that corresponds to C++ namespace or exported class. Example: `cv::ml::Boost` is exported as `cv.ml.Boost` `cv::SimpleBlobDetector::Params` is exported as `cv.SimpleBlobDetector.Params` For backward compatibility all classes are registered in the global module with their mangling name containing scope information. Example: `cv::ml::Boost` has `cv.ml_Boost` alias to `cv.ml.Boost` type --- .../include/opencv2/core/bindings_utils.hpp | 47 +++++ modules/python/src2/cv2.cpp | 191 +++++++++++++++++- modules/python/src2/gen2.py | 110 +++++++--- modules/python/src2/pycompat.hpp | 144 +++++++------ modules/python/test/test_misc.py | 85 ++++++++ 5 files changed, 484 insertions(+), 93 deletions(-) diff --git a/modules/core/include/opencv2/core/bindings_utils.hpp b/modules/core/include/opencv2/core/bindings_utils.hpp index f091606c4a..6e825ec816 100644 --- a/modules/core/include/opencv2/core/bindings_utils.hpp +++ b/modules/core/include/opencv2/core/bindings_utils.hpp @@ -217,6 +217,53 @@ namespace nested { CV_WRAP static inline bool testEchoBooleanFunction(bool flag) { return flag; } + +class CV_EXPORTS_W CV_WRAP_AS(ExportClassName) OriginalClassName +{ +public: + struct CV_EXPORTS_W_SIMPLE Params + { + CV_PROP_RW int int_value; + CV_PROP_RW float float_value; + + CV_WRAP explicit Params(int int_param = 123, float float_param = 3.5f) + { + int_value = int_param; + float_value = float_param; + } + }; + + explicit OriginalClassName(const OriginalClassName::Params& params = OriginalClassName::Params()) + { + params_ = params; + } + + CV_WRAP int getIntParam() const + { + return params_.int_value; + } + + CV_WRAP float getFloatParam() const + { + return params_.float_value; + } + + CV_WRAP static std::string originalName() + { + return "OriginalClassName"; + } + + CV_WRAP static Ptr + create(const OriginalClassName::Params& params = OriginalClassName::Params()) + { + return makePtr(params); + } + +private: + OriginalClassName::Params params_; +}; + +typedef OriginalClassName::Params OriginalClassName_Params; } // namespace nested //! @} // core_utils diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index a82086f315..f41cd6f389 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -2066,9 +2066,9 @@ static int convert_to_char(PyObject *o, char *dst, const ArgInfo& info) #include "pyopencv_custom_headers.h" #ifdef CVPY_DYNAMIC_INIT -#define CVPY_TYPE(WNAME, NAME, STORAGE, SNAME, _1, _2) CVPY_TYPE_DECLARE_DYNAMIC(WNAME, NAME, STORAGE, SNAME) +#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, _1, _2, SCOPE) CVPY_TYPE_DECLARE_DYNAMIC(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) #else -#define CVPY_TYPE(WNAME, NAME, STORAGE, SNAME, _1, _2) CVPY_TYPE_DECLARE(WNAME, NAME, STORAGE, SNAME) +#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, _1, _2, SCOPE) CVPY_TYPE_DECLARE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) #endif #include "pyopencv_generated_types.h" #undef CVPY_TYPE @@ -2251,6 +2251,189 @@ static bool init_submodule(PyObject * root, const char * name, PyMethodDef * met return true; } +static inline +bool registerTypeInModuleScope(PyObject* module, const char* type_name, PyObject* type_obj) +{ + if (PyModule_AddObject(module, type_name, type_obj) < 0) + { + PyErr_Format(PyExc_ImportError, + "Failed to register type '%s' in module scope '%s'", + type_name, PyModule_GetName(module) + ); + Py_DECREF(type_obj); + return false; + } + return true; +} + +static inline +bool registerTypeInClassScope(PyObject* cls, const char* type_name, PyObject* type_obj) +{ + if (!PyType_CheckExact(cls)) { + PyErr_Format(PyExc_ImportError, + "Failed to register type '%s' in class scope. " + "Scope class object has a wrong type", type_name + ); + return false; + } + if (PyObject_SetAttrString(cls, type_name, type_obj) < 0) + { + #ifndef Py_LIMITED_API + PyObject* cls_dict = reinterpret_cast(cls)->tp_dict; + if (PyDict_SetItemString(cls_dict, type_name, type_obj) >= 0) { + /// Clearing the error set by PyObject_SetAttrString: + /// TypeError: can't set attributes of built-in/extension type NAME + PyErr_Clear(); + return true; + } + #endif + const std::string cls_name = getPyObjectNameAttr(cls); + PyErr_Format(PyExc_ImportError, + "Failed to register type '%s' in '%s' class scope. Can't update scope dictionary", + type_name, cls_name.c_str() + ); + return false; + } + return true; +} + +static inline +PyObject* getScopeFromTypeObject(PyObject* obj, const std::string& scope_name) +{ + if (!PyType_CheckExact(obj)) { + const std::string type_name = getPyObjectNameAttr(obj); + return PyErr_Format(PyExc_ImportError, + "Failed to get scope from type '%s' " + "Scope class object has a wrong type", type_name.c_str() + ); + } + /// When using LIMITED API all classes are registered in the heap +#if defined(Py_LIMITED_API) + return PyObject_GetAttrString(obj, scope_name.c_str()); +#else + /// Otherwise classes may be registed on the stack or heap + PyObject* type_dict = reinterpret_cast(obj)->tp_dict; + if (!type_dict) { + const std::string type_name = getPyObjectNameAttr(obj); + return PyErr_Format(PyExc_ImportError, + "Failed to get scope from type '%s' " + "Type dictionary is not available", type_name.c_str() + ); + } + return PyDict_GetItemString(type_dict, scope_name.c_str()); +#endif // Py_LIMITED_API +} + +static inline +PyObject* findTypeScope(PyObject* root_module, const std::string& scope_name) +{ + PyObject* scope = root_module; + if (scope_name.empty()) + { + return scope; + } + /// Starting with 1 to omit leading dot in the scope name + size_t name_end = scope_name.find('.', 1); + if (name_end == std::string::npos) + { + name_end = scope_name.size(); + } + for (size_t name_start = 1; name_start < scope_name.size() && scope; ) + { + const std::string current_scope_name = scope_name.substr(name_start, + name_end - name_start); + + if (PyModule_CheckExact(scope)) + { + PyObject* scope_dict = PyModule_GetDict(scope); + if (!scope_dict) + { + return PyErr_Format(PyExc_ImportError, + "Scope '%s' dictionary is not available during the search for " + " the '%s' scope object", current_scope_name.c_str(), + scope_name.c_str() + ); + } + + scope = PyDict_GetItemString(scope_dict, current_scope_name.c_str()); + } + else if (PyType_CheckExact(scope)) + { + scope = getScopeFromTypeObject(scope, current_scope_name); + } + else + { + return PyErr_Format(PyExc_ImportError, + "Can't find scope '%s'. '%s' doesn't reference a module or a class", + scope_name.c_str(), current_scope_name.c_str() + ); + } + + + name_start = name_end + 1; + name_end = scope_name.find('.', name_start); + if (name_end == std::string::npos) + { + name_end = scope_name.size(); + } + } + if (!scope) + { + return PyErr_Format(PyExc_ImportError, + "Module or class with name '%s' can't be found in '%s' module", + scope_name.c_str(), PyModule_GetName(root_module) + ); + } + return scope; +} + +static bool registerNewType(PyObject* root_module, const char* type_name, + PyObject* type_obj, const std::string& scope_name) +{ + PyObject* scope = findTypeScope(root_module, scope_name); + + /// If scope can't be found it means that there is an error during + /// bindings generation + if (!scope) { + return false; + } + + if (PyModule_CheckExact(scope)) + { + if (!registerTypeInModuleScope(scope, type_name, type_obj)) + { + return false; + } + } + else + { + /// In Python 2 it is disallowed to register an inner classes + /// via modifing dictionary of the built-in type. + if (!registerTypeInClassScope(scope, type_name, type_obj)) + { + return false; + } + } + + /// Expose all classes that are defined in the submodules as aliases in the + /// root module for backward compatibility + /// If submodule and root module are same than no aliases registration are + /// required + if (scope != root_module) + { + std::string type_name_str(type_name); + + std::string alias_name; + alias_name.reserve(scope_name.size() + type_name_str.size()); + std::replace_copy(scope_name.begin() + 1, scope_name.end(), std::back_inserter(alias_name), '.', '_'); + alias_name += '_'; + alias_name += type_name_str; + + return registerTypeInModuleScope(root_module, alias_name.c_str(), type_obj); + } + return true; +} + #include "pyopencv_generated_modules_content.h" static bool init_body(PyObject * m) @@ -2264,10 +2447,10 @@ static bool init_body(PyObject * m) #undef CVPY_MODULE #ifdef CVPY_DYNAMIC_INIT -#define CVPY_TYPE(WNAME, NAME, _1, _2, BASE, CONSTRUCTOR) CVPY_TYPE_INIT_DYNAMIC(WNAME, NAME, return false, BASE, CONSTRUCTOR) +#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, _1, _2, BASE, CONSTRUCTOR, SCOPE) CVPY_TYPE_INIT_DYNAMIC(EXPORT_NAME, CLASS_ID, return false, BASE, CONSTRUCTOR, SCOPE) PyObject * pyopencv_NoBase_TypePtr = NULL; #else -#define CVPY_TYPE(WNAME, NAME, _1, _2, BASE, CONSTRUCTOR) CVPY_TYPE_INIT_STATIC(WNAME, NAME, return false, BASE, CONSTRUCTOR) +#define CVPY_TYPE(EXPORT_NAME, CLASS_ID, _1, _2, BASE, CONSTRUCTOR, SCOPE) CVPY_TYPE_INIT_STATIC(EXPORT_NAME, CLASS_ID, return false, BASE, CONSTRUCTOR, SCOPE) PyTypeObject * pyopencv_NoBase_TypePtr = NULL; #endif #include "pyopencv_generated_types.h" diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 51566fc248..79853648c5 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -243,10 +243,20 @@ class ClassProp(object): self.readonly = False class ClassInfo(object): - def __init__(self, name, decl=None): + def __init__(self, name, decl=None, codegen=None): + # Scope name can be a module or other class e.g. cv::SimpleBlobDetector::Params + scope_name, self.original_name = name.rsplit(".", 1) + + # In case scope refer the outer class exported with different name + if codegen: + scope_name = codegen.get_export_scope_name(scope_name) + self.scope_name = re.sub(r"^cv\.?", "", scope_name) + + self.export_name = self.original_name + + self.class_id = normalize_class_name(name) + self.cname = name.replace(".", "::") - self.name = self.wname = normalize_class_name(name) - self.sname = name[name.rfind('.') + 1:] self.ismap = False self.issimple = False self.isalgorithm = False @@ -261,7 +271,7 @@ class ClassInfo(object): if decl: bases = decl[1].split()[1:] if len(bases) > 1: - print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.name,)) + print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.cname,)) print(" Bases: ", " ".join(bases)) print(" Only the first base class will be used") #return sys.exit(-1) @@ -275,22 +285,47 @@ class ClassInfo(object): for m in decl[2]: if m.startswith("="): - wname = m[1:] - npos = name.rfind('.') - if npos >= 0: - self.wname = normalize_class_name(name[:npos] + '.' + wname) - else: - self.wname = wname - customname = True + # Aliasing only affects the exported class name, not class identifier + self.export_name = m[1:] elif m == "/Map": self.ismap = True elif m == "/Simple": self.issimple = True self.props = [ClassProp(p) for p in decl[3]] + if not self.has_export_alias and self.original_name.startswith("Cv"): + self.export_name = self.export_name[2:] + if not customname and self.wname.startswith("Cv"): self.wname = self.wname[2:] + @property + def wname(self): + if len(self.scope_name) > 0: + return self.scope_name.replace(".", "_") + "_" + self.export_name + + return self.export_name + + @property + def name(self): + return self.class_id + + @property + def full_scope_name(self): + return "cv." + self.scope_name if len(self.scope_name) else "cv" + + @property + def full_export_name(self): + return self.full_scope_name + "." + self.export_name + + @property + def full_original_name(self): + return self.full_scope_name + "." + self.original_name + + @property + def has_export_alias(self): + return self.export_name != self.original_name + def gen_map_code(self, codegen): all_classes = codegen.classes code = "static bool pyopencv_to(PyObject* src, %s& dst, const ArgInfo& info)\n{\n PyObject* tmp;\n bool ok;\n" % (self.cname) @@ -343,9 +378,11 @@ class ClassInfo(object): methods_code.write(m.gen_code(codegen)) methods_inits.write(m.get_tab_entry()) - code = gen_template_type_impl.substitute(name=self.name, wname=self.wname, cname=self.cname, - getset_code=getset_code.getvalue(), getset_inits=getset_inits.getvalue(), - methods_code=methods_code.getvalue(), methods_inits=methods_inits.getvalue()) + code = gen_template_type_impl.substitute(name=self.name, + getset_code=getset_code.getvalue(), + getset_inits=getset_inits.getvalue(), + methods_code=methods_code.getvalue(), + methods_inits=methods_inits.getvalue()) return code @@ -359,13 +396,15 @@ class ClassInfo(object): if self.constructor is not None: constructor_name = self.constructor.get_wrapper_name() - return "CVPY_TYPE({}, {}, {}, {}, {}, {});\n".format( - self.wname, - self.name, + return 'CVPY_TYPE({}, {}, {}, {}, {}, {}, "{}");\n'.format( + self.export_name, + self.class_id, self.cname if self.issimple else "Ptr<{}>".format(self.cname), - self.sname if self.issimple else "Ptr", + self.original_name if self.issimple else "Ptr", baseptr, - constructor_name + constructor_name, + # Leading dot is required to provide correct class naming + "." + self.scope_name if len(self.scope_name) > 0 else self.scope_name ) @@ -815,12 +854,12 @@ class FuncInfo(object): classinfo = all_classes[self.classname] #if dump: pprint(vars(classinfo)) if self.isconstructor: - py_name = 'cv.' + classinfo.wname - elif self.is_static: - py_name = '.'.join([self.namespace, classinfo.sname + '_' + self.variants[0].wname]) + py_name = classinfo.full_export_name else: + py_name = classinfo.full_export_name + "." + self.variants[0].wname + + if not self.is_static: cname = classinfo.cname + '::' + cname - py_name = 'cv.' + classinfo.wname + '.' + self.variants[0].wname else: py_name = '.'.join([self.namespace, self.variants[0].wname]) #if dump: print(cname + " => " + py_name) @@ -862,7 +901,7 @@ class PythonWrapperGenerator(object): self.class_idx = 0 def add_class(self, stype, name, decl): - classinfo = ClassInfo(name, decl) + classinfo = ClassInfo(name, decl, self) classinfo.decl_idx = self.class_idx self.class_idx += 1 @@ -872,16 +911,30 @@ class PythonWrapperGenerator(object): sys.exit(-1) self.classes[classinfo.name] = classinfo - # Add Class to json file. - namespace, classes, name = self.split_decl_name(name) + namespace, _, _ = self.split_decl_name(name) namespace = '.'.join(namespace) - name = '_'.join(classes+[name]) + # Registering a namespace if it is not already handled or + # doesn't have anything except classes defined in it + self.namespaces.setdefault(namespace, Namespace()) - py_name = 'cv.' + classinfo.wname # use wrapper name + # Add Class to json file. + py_name = classinfo.full_export_name # use wrapper name py_signatures = self.py_signatures.setdefault(classinfo.cname, []) py_signatures.append(dict(name=py_name)) #print('class: ' + classinfo.cname + " => " + py_name) + def get_export_scope_name(self, original_scope_name): + # Outer classes should be registered before their content - inner classes in this case + class_scope = self.classes.get(normalize_class_name(original_scope_name), None) + + if class_scope: + return class_scope.full_export_name + + # Otherwise it is a namespace. + # If something is messed up at this point - it will be revelead during + # library import + return original_scope_name + def split_decl_name(self, name): chunks = name.split('.') namespace = chunks[:-1] @@ -971,6 +1024,7 @@ class PythonWrapperGenerator(object): w_classes.append(w_classname) g_wname = "_".join(w_classes+[name]) func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs + # Exports static function with internal name (backward compatibility) func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace_str, False)) func.add_variant(decl, isphantom) if g_wname != g_name: # TODO OpenCV 5.0 diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index 5c1bc2354f..b9eca7bc18 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -60,6 +60,10 @@ #endif // PY_MAJOR >=3 +#ifndef PyType_CheckExact +#define PyType_CheckExact(obj) (Py_TYPE(op) == &PyType_Type) +#endif // !PyType_CheckExact + static inline bool getUnicodeString(PyObject * obj, std::string &str) { bool res = false; @@ -91,6 +95,26 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) return res; } +static inline +std::string getPyObjectNameAttr(PyObject* obj) +{ + std::string obj_name; + PyObject* cls_name_obj = PyObject_GetAttrString(obj, "__name__"); + if (cls_name_obj && !getUnicodeString(cls_name_obj, obj_name)) { + obj_name.clear(); + } + #ifndef Py_LIMITED_API + if (PyType_CheckExact(obj) && obj_name.empty()) + { + obj_name = reinterpret_cast(obj)->tp_name; + } + #endif + if (obj_name.empty()) { + obj_name = ""; + } + return obj_name; +} + //================================================================================================== #define CV_PY_FN_WITH_KW_(fn, flags) (PyCFunction)(void*)(PyCFunctionWithKeywords)(fn), (flags) | METH_VARARGS | METH_KEYWORDS @@ -172,107 +196,106 @@ PyObject* pyopencv_from(const TYPE& src) #endif -#define CVPY_TYPE_DECLARE(WNAME, NAME, STORAGE, SNAME) \ - struct pyopencv_##NAME##_t \ +#define CVPY_TYPE_DECLARE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \ + struct pyopencv_##CLASS_ID##_t \ { \ PyObject_HEAD \ STORAGE v; \ }; \ - static PyTypeObject pyopencv_##NAME##_TypeXXX = \ + static PyTypeObject pyopencv_##CLASS_ID##_TypeXXX = \ { \ CVPY_TYPE_HEAD \ - MODULESTR"."#WNAME, \ - sizeof(pyopencv_##NAME##_t), \ + MODULESTR SCOPE"."#EXPORT_NAME, \ + sizeof(pyopencv_##CLASS_ID##_t), \ }; \ - static PyTypeObject * pyopencv_##NAME##_TypePtr = &pyopencv_##NAME##_TypeXXX; \ - static bool pyopencv_##NAME##_getp(PyObject * self, STORAGE * & dst) \ + static PyTypeObject * pyopencv_##CLASS_ID##_TypePtr = &pyopencv_##CLASS_ID##_TypeXXX; \ + static bool pyopencv_##CLASS_ID##_getp(PyObject * self, STORAGE * & dst) \ { \ - if (PyObject_TypeCheck(self, pyopencv_##NAME##_TypePtr)) \ + if (PyObject_TypeCheck(self, pyopencv_##CLASS_ID##_TypePtr)) \ { \ - dst = &(((pyopencv_##NAME##_t*)self)->v); \ + dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \ return true; \ } \ return false; \ } \ - static PyObject * pyopencv_##NAME##_Instance(const STORAGE &r) \ + static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \ { \ - pyopencv_##NAME##_t *m = PyObject_NEW(pyopencv_##NAME##_t, pyopencv_##NAME##_TypePtr); \ + pyopencv_##CLASS_ID##_t *m = PyObject_NEW(pyopencv_##CLASS_ID##_t, pyopencv_##CLASS_ID##_TypePtr); \ new (&(m->v)) STORAGE(r); \ return (PyObject*)m; \ } \ - static void pyopencv_##NAME##_dealloc(PyObject* self) \ + static void pyopencv_##CLASS_ID##_dealloc(PyObject* self) \ { \ - ((pyopencv_##NAME##_t*)self)->v.STORAGE::~SNAME(); \ + ((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \ PyObject_Del(self); \ } \ - static PyObject* pyopencv_##NAME##_repr(PyObject* self) \ + static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \ { \ char str[1000]; \ - sprintf(str, "<"#WNAME" %p>", self); \ + sprintf(str, "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \ return PyString_FromString(str); \ } -#define CVPY_TYPE_INIT_STATIC(WNAME, NAME, ERROR_HANDLER, BASE, CONSTRUCTOR) \ +#define CVPY_TYPE_INIT_STATIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \ { \ - pyopencv_##NAME##_TypePtr->tp_base = pyopencv_##BASE##_TypePtr; \ - pyopencv_##NAME##_TypePtr->tp_dealloc = pyopencv_##NAME##_dealloc; \ - pyopencv_##NAME##_TypePtr->tp_repr = pyopencv_##NAME##_repr; \ - pyopencv_##NAME##_TypePtr->tp_getset = pyopencv_##NAME##_getseters; \ - pyopencv_##NAME##_TypePtr->tp_init = (initproc) CONSTRUCTOR; \ - pyopencv_##NAME##_TypePtr->tp_methods = pyopencv_##NAME##_methods; \ - pyopencv_##NAME##_TypePtr->tp_alloc = PyType_GenericAlloc; \ - pyopencv_##NAME##_TypePtr->tp_new = PyType_GenericNew; \ - pyopencv_##NAME##_TypePtr->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; \ - if (PyType_Ready(pyopencv_##NAME##_TypePtr) != 0) \ + pyopencv_##CLASS_ID##_TypePtr->tp_base = pyopencv_##BASE##_TypePtr; \ + pyopencv_##CLASS_ID##_TypePtr->tp_dealloc = pyopencv_##CLASS_ID##_dealloc; \ + pyopencv_##CLASS_ID##_TypePtr->tp_repr = pyopencv_##CLASS_ID##_repr; \ + pyopencv_##CLASS_ID##_TypePtr->tp_getset = pyopencv_##CLASS_ID##_getseters; \ + pyopencv_##CLASS_ID##_TypePtr->tp_init = (initproc) CONSTRUCTOR; \ + pyopencv_##CLASS_ID##_TypePtr->tp_methods = pyopencv_##CLASS_ID##_methods; \ + pyopencv_##CLASS_ID##_TypePtr->tp_alloc = PyType_GenericAlloc; \ + pyopencv_##CLASS_ID##_TypePtr->tp_new = PyType_GenericNew; \ + pyopencv_##CLASS_ID##_TypePtr->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; \ + if (PyType_Ready(pyopencv_##CLASS_ID##_TypePtr) != 0) \ { \ ERROR_HANDLER; \ } \ - CVPY_TYPE_INCREF(pyopencv_##NAME##_TypePtr); \ - if (PyModule_AddObject(m, #WNAME, (PyObject *)pyopencv_##NAME##_TypePtr) < 0) \ + CVPY_TYPE_INCREF(pyopencv_##CLASS_ID##_TypePtr); \ + if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \ { \ - printf("Failed to register a new type: " #WNAME ", base (" #BASE ")\n"); \ - Py_DECREF(pyopencv_##NAME##_TypePtr); \ + printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \ ERROR_HANDLER; \ } \ } //================================================================================================== -#define CVPY_TYPE_DECLARE_DYNAMIC(WNAME, NAME, STORAGE, SNAME) \ - struct pyopencv_##NAME##_t \ +#define CVPY_TYPE_DECLARE_DYNAMIC(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \ + struct pyopencv_##CLASS_ID##_t \ { \ PyObject_HEAD \ STORAGE v; \ }; \ - static PyObject * pyopencv_##NAME##_TypePtr = 0; \ - static bool pyopencv_##NAME##_getp(PyObject * self, STORAGE * & dst) \ + static PyObject * pyopencv_##CLASS_ID##_TypePtr = 0; \ + static bool pyopencv_##CLASS_ID##_getp(PyObject * self, STORAGE * & dst) \ { \ - if (PyObject_TypeCheck(self, (PyTypeObject*)pyopencv_##NAME##_TypePtr)) \ + if (PyObject_TypeCheck(self, (PyTypeObject*)pyopencv_##CLASS_ID##_TypePtr)) \ { \ - dst = &(((pyopencv_##NAME##_t*)self)->v); \ + dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \ return true; \ } \ return false; \ } \ - static PyObject * pyopencv_##NAME##_Instance(const STORAGE &r) \ + static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \ { \ - pyopencv_##NAME##_t *m = PyObject_New(pyopencv_##NAME##_t, (PyTypeObject*)pyopencv_##NAME##_TypePtr); \ + pyopencv_##CLASS_ID##_t *m = PyObject_New(pyopencv_##CLASS_ID##_t, (PyTypeObject*)pyopencv_##CLASS_ID##_TypePtr); \ new (&(m->v)) STORAGE(r); \ return (PyObject*)m; \ } \ - static void pyopencv_##NAME##_dealloc(PyObject* self) \ + static void pyopencv_##CLASS_ID##_dealloc(PyObject* self) \ { \ - ((pyopencv_##NAME##_t*)self)->v.STORAGE::~SNAME(); \ + ((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \ PyObject_Del(self); \ } \ - static PyObject* pyopencv_##NAME##_repr(PyObject* self) \ + static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \ { \ char str[1000]; \ - sprintf(str, "<"#WNAME" %p>", self); \ + sprintf(str, "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \ return PyString_FromString(str); \ } \ - static PyType_Slot pyopencv_##NAME##_Slots[] = \ + static PyType_Slot pyopencv_##CLASS_ID##_Slots[] = \ { \ {Py_tp_dealloc, 0}, \ {Py_tp_repr, 0}, \ @@ -283,37 +306,36 @@ PyObject* pyopencv_from(const TYPE& src) {Py_tp_new, 0}, \ {0, 0} \ }; \ - static PyType_Spec pyopencv_##NAME##_Spec = \ + static PyType_Spec pyopencv_##CLASS_ID##_Spec = \ { \ - MODULESTR"."#WNAME, \ - sizeof(pyopencv_##NAME##_t), \ + MODULESTR SCOPE"."#EXPORT_NAME, \ + sizeof(pyopencv_##CLASS_ID##_t), \ 0, \ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, \ - pyopencv_##NAME##_Slots \ + pyopencv_##CLASS_ID##_Slots \ }; -#define CVPY_TYPE_INIT_DYNAMIC(WNAME, NAME, ERROR_HANDLER, BASE, CONSTRUCTOR) \ +#define CVPY_TYPE_INIT_DYNAMIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \ { \ - pyopencv_##NAME##_Slots[0].pfunc /*tp_dealloc*/ = (void*)pyopencv_##NAME##_dealloc; \ - pyopencv_##NAME##_Slots[1].pfunc /*tp_repr*/ = (void*)pyopencv_##NAME##_repr; \ - pyopencv_##NAME##_Slots[2].pfunc /*tp_getset*/ = (void*)pyopencv_##NAME##_getseters; \ - pyopencv_##NAME##_Slots[3].pfunc /*tp_init*/ = (void*) CONSTRUCTOR; \ - pyopencv_##NAME##_Slots[4].pfunc /*tp_methods*/ = pyopencv_##NAME##_methods; \ - pyopencv_##NAME##_Slots[5].pfunc /*tp_alloc*/ = (void*)PyType_GenericAlloc; \ - pyopencv_##NAME##_Slots[6].pfunc /*tp_new*/ = (void*)PyType_GenericNew; \ + pyopencv_##CLASS_ID##_Slots[0].pfunc /*tp_dealloc*/ = (void*)pyopencv_##CLASS_ID##_dealloc; \ + pyopencv_##CLASS_ID##_Slots[1].pfunc /*tp_repr*/ = (void*)pyopencv_##CLASS_ID##_repr; \ + pyopencv_##CLASS_ID##_Slots[2].pfunc /*tp_getset*/ = (void*)pyopencv_##CLASS_ID##_getseters; \ + pyopencv_##CLASS_ID##_Slots[3].pfunc /*tp_init*/ = (void*) CONSTRUCTOR; \ + pyopencv_##CLASS_ID##_Slots[4].pfunc /*tp_methods*/ = pyopencv_##CLASS_ID##_methods; \ + pyopencv_##CLASS_ID##_Slots[5].pfunc /*tp_alloc*/ = (void*)PyType_GenericAlloc; \ + pyopencv_##CLASS_ID##_Slots[6].pfunc /*tp_new*/ = (void*)PyType_GenericNew; \ PyObject * bases = 0; \ if (pyopencv_##BASE##_TypePtr) \ bases = PyTuple_Pack(1, pyopencv_##BASE##_TypePtr); \ - pyopencv_##NAME##_TypePtr = PyType_FromSpecWithBases(&pyopencv_##NAME##_Spec, bases); \ - if (!pyopencv_##NAME##_TypePtr) \ + pyopencv_##CLASS_ID##_TypePtr = PyType_FromSpecWithBases(&pyopencv_##CLASS_ID##_Spec, bases); \ + if (!pyopencv_##CLASS_ID##_TypePtr) \ { \ - printf("Failed to create type from spec: " #WNAME ", base (" #BASE ")\n"); \ + printf("Failed to create type from spec: " #CLASS_ID ", base (" #BASE ")\n"); \ ERROR_HANDLER; \ } \ - if (PyModule_AddObject(m, #WNAME, (PyObject *)pyopencv_##NAME##_TypePtr) < 0) \ + if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \ { \ - printf("Failed to register a new type: " #WNAME ", base (" #BASE ")\n"); \ - Py_DECREF(pyopencv_##NAME##_TypePtr); \ + printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \ ERROR_HANDLER; \ } \ } diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index 41e5c6ba4b..ec56585ace 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -618,6 +618,91 @@ class Arguments(NewOpenCVTests): self.assertEqual(flag, cv.utils.nested.testEchoBooleanFunction(flag), msg="Function in nested module returns wrong result") + def test_class_from_submodule_has_global_alias(self): + self.assertTrue(hasattr(cv.ml, "Boost"), + msg="Class is not registered in the submodule") + self.assertTrue(hasattr(cv, "ml_Boost"), + msg="Class from submodule doesn't have alias in the " + "global module") + self.assertEqual(cv.ml.Boost, cv.ml_Boost, + msg="Classes from submodules and global module don't refer " + "to the same type") + + def test_inner_class_has_global_alias(self): + self.assertTrue(hasattr(cv.SimpleBlobDetector, "Params"), + msg="Class is not registered as inner class") + self.assertEqual(cv.SimpleBlobDetector.Params, cv.SimpleBlobDetector_Params, + msg="Inner class and class in global module don't refer " + "to the same type") + self.assertTrue(hasattr(cv, "SimpleBlobDetector_Params"), + msg="Inner class doesn't have alias in the global module") + + def test_class_from_submodule_has_global_alias(self): + self.assertTrue(hasattr(cv.ml, "Boost"), + msg="Class is not registered in the submodule") + self.assertTrue(hasattr(cv, "ml_Boost"), + msg="Class from submodule doesn't have alias in the " + "global module") + self.assertEqual(cv.ml.Boost, cv.ml_Boost, + msg="Classes from submodules and global module don't refer " + "to the same type") + + def test_inner_class_has_global_alias(self): + self.assertTrue(hasattr(cv.SimpleBlobDetector, "Params"), + msg="Class is not registered as inner class") + self.assertTrue(hasattr(cv, "SimpleBlobDetector_Params"), + msg="Inner class doesn't have alias in the global module") + self.assertEqual(cv.SimpleBlobDetector.Params, cv.SimpleBlobDetector_Params, + msg="Inner class and class in global module don't refer " + "to the same type") + self.assertTrue(hasattr(cv, "SimpleBlobDetector_Params"), + msg="Inner class doesn't have alias in the global module") + + def test_export_class_with_different_name(self): + self.assertTrue(hasattr(cv.utils.nested, "ExportClassName"), + msg="Class with export alias is not registered in the submodule") + self.assertTrue(hasattr(cv, "utils_nested_ExportClassName"), + msg="Class with export alias doesn't have alias in the " + "global module") + self.assertEqual(cv.utils.nested.ExportClassName.originalName(), "OriginalClassName") + + instance = cv.utils.nested.ExportClassName.create() + self.assertTrue(isinstance(instance, cv.utils.nested.ExportClassName), + msg="Factory function returns wrong class instance: {}".format(type(instance))) + self.assertTrue(hasattr(cv.utils.nested, "ExportClassName_create"), + msg="Factory function should have alias in the same module as the class") + # self.assertFalse(hasattr(cv.utils.nested, "OriginalClassName_create"), + # msg="Factory function should not be registered with original class name, "\ + # "when class has different export name") + + def test_export_inner_class_of_class_exported_with_different_name(self): + if not hasattr(cv.utils.nested, "ExportClassName"): + raise unittest.SkipTest("Outer class with export alias is not registered in the submodule") + + self.assertTrue(hasattr(cv.utils.nested.ExportClassName, "Params"), + msg="Inner class with export alias is not registered in " + "the outer class") + self.assertTrue(hasattr(cv, "utils_nested_ExportClassName_Params"), + msg="Inner class with export alias is not registered in " + "global module") + params = cv.utils.nested.ExportClassName.Params() + params.int_value = 45 + params.float_value = 4.5 + + instance = cv.utils.nested.ExportClassName.create(params) + self.assertTrue(isinstance(instance, cv.utils.nested.ExportClassName), + msg="Factory function returns wrong class instance: {}".format(type(instance))) + self.assertEqual( + params.int_value, instance.getIntParam(), + msg="Class initialized with wrong integer parameter. Expected: {}. Actual: {}".format( + params.int_value, instance.getIntParam() + )) + self.assertEqual( + params.float_value, instance.getFloatParam(), + msg="Class initialized with wrong integer parameter. Expected: {}. Actual: {}".format( + params.float_value, instance.getFloatParam() + )) + class SamplesFindFile(NewOpenCVTests):