vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# ----------------------------------------------------------------------------
|
||||
# CMake file for python support
|
||||
# ----------------------------------------------------------------------------
|
||||
if(DEFINED OPENCV_INITIAL_PASS) # OpenCV build
|
||||
|
||||
if(ANDROID OR APPLE_FRAMEWORK OR WINRT)
|
||||
ocv_module_disable_(python3)
|
||||
return()
|
||||
elseif(BUILD_opencv_world OR (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug"))
|
||||
if(NOT DEFINED BUILD_opencv_python3)
|
||||
set(__disable_python3 ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(bindings)
|
||||
|
||||
add_subdirectory(test)
|
||||
|
||||
if(__disable_python3)
|
||||
ocv_module_disable_(python3)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_subdirectory(python3)
|
||||
|
||||
else() # standalone build
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(OpenCVPython CXX C)
|
||||
include("./standalone.cmake")
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,182 @@
|
||||
set(MODULE_NAME "python_bindings_generator")
|
||||
set(OPENCV_MODULE_IS_PART_OF_WORLD FALSE)
|
||||
ocv_add_module(${MODULE_NAME} INTERNAL)
|
||||
|
||||
set(OPENCV_PYTHON_SIGNATURES_FILE "${CMAKE_CURRENT_BINARY_DIR}/pyopencv_signatures.json" CACHE INTERNAL "")
|
||||
set(OPENCV_PYTHON_BINDINGS_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE INTERNAL "")
|
||||
|
||||
# This file is included from a subdirectory
|
||||
set(PYTHON_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../")
|
||||
|
||||
if(NOT OPENCV_SKIP_PYTHON_LOADER)
|
||||
include("${PYTHON_SOURCE_DIR}/python_loader.cmake")
|
||||
endif()
|
||||
|
||||
# get list of modules to wrap
|
||||
set(OPENCV_PYTHON_MODULES)
|
||||
foreach(m ${OPENCV_MODULES_BUILD})
|
||||
if (";${OPENCV_MODULE_${m}_WRAPPERS};" MATCHES ";python;" AND HAVE_${m})
|
||||
list(APPEND OPENCV_PYTHON_MODULES ${m})
|
||||
#message(STATUS "\t${m}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(opencv_hdrs "")
|
||||
set(opencv_userdef_hdrs "")
|
||||
foreach(m ${OPENCV_PYTHON_MODULES})
|
||||
foreach (hdr ${OPENCV_MODULE_${m}_HEADERS})
|
||||
ocv_is_subdir(is_sub "${OPENCV_MODULE_${m}_LOCATION}/include" "${hdr}")
|
||||
if(is_sub)
|
||||
list(APPEND opencv_hdrs "${hdr}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# both wrapping and C++ implementation
|
||||
file(GLOB hdr2 ${OPENCV_MODULE_${m}_LOCATION}/misc/python/python_*.hpp)
|
||||
list(SORT hdr2)
|
||||
list(APPEND opencv_hdrs ${hdr2})
|
||||
list(APPEND opencv_userdef_hdrs ${hdr2})
|
||||
|
||||
file(GLOB hdr ${OPENCV_MODULE_${m}_LOCATION}/misc/python/shadow*.hpp)
|
||||
list(SORT hdr)
|
||||
list(APPEND opencv_hdrs ${hdr})
|
||||
file(GLOB userdef_hdrs ${OPENCV_MODULE_${m}_LOCATION}/misc/python/pyopencv*.hpp)
|
||||
list(SORT userdef_hdrs)
|
||||
list(APPEND opencv_userdef_hdrs ${userdef_hdrs})
|
||||
endforeach(m)
|
||||
|
||||
# header blacklist
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*\\\\.h$")
|
||||
ocv_list_filterout(opencv_hdrs "modules/core/include/opencv2/core/fast_math.hpp")
|
||||
ocv_list_filterout(opencv_hdrs "modules/core/.*/cuda/")
|
||||
ocv_list_filterout(opencv_hdrs "modules/core/.*/hal/")
|
||||
ocv_list_filterout(opencv_hdrs "modules/core/.*/opencl/")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.+/utils/trace.hpp")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*\\\\.inl\\\\.h*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*_inl\\\\.h*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*\\\\.details\\\\.h*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*\\\\.private\\\\.h*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*/private\\\\.h*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*/legacy/.*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/.*/detection_based_tracker\\\\.hpp") # Conditional compilation
|
||||
if(NOT HAVE_CUDA)
|
||||
ocv_list_filterout(opencv_hdrs "modules/cuda.*")
|
||||
ocv_list_filterout(opencv_hdrs "modules/cudev")
|
||||
endif()
|
||||
|
||||
set(cv2_generated_files
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_enums.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_funcs.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_include.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_modules.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_modules_content.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_types.h"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pyopencv_generated_types_content.h"
|
||||
"${OPENCV_PYTHON_SIGNATURES_FILE}"
|
||||
)
|
||||
|
||||
|
||||
set(config_json_headers_list "")
|
||||
foreach(header IN LISTS opencv_hdrs)
|
||||
if(NOT config_json_headers_list STREQUAL "")
|
||||
set(config_json_headers_list "${config_json_headers_list},\n\"${header}\"")
|
||||
else()
|
||||
set(config_json_headers_list "\"${header}\"")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVBindingsPreprocessorDefinitions.cmake")
|
||||
|
||||
ocv_bindings_generator_populate_preprocessor_definitions(
|
||||
OPENCV_MODULES_BUILD
|
||||
opencv_preprocessor_defs
|
||||
)
|
||||
|
||||
set(__config_str
|
||||
"{
|
||||
\"headers\": [
|
||||
${config_json_headers_list}
|
||||
],
|
||||
\"preprocessor_definitions\": {
|
||||
${opencv_preprocessor_defs}
|
||||
}
|
||||
}")
|
||||
|
||||
set(JSON_CONFIG_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/gen_python_config.json")
|
||||
if(EXISTS "${JSON_CONFIG_FILE_PATH}")
|
||||
file(READ "${JSON_CONFIG_FILE_PATH}" __content)
|
||||
else()
|
||||
set(__content "")
|
||||
endif()
|
||||
if(NOT "${__content}" STREQUAL "${__config_str}")
|
||||
file(WRITE "${JSON_CONFIG_FILE_PATH}" "${__config_str}")
|
||||
endif()
|
||||
unset(__config_str)
|
||||
|
||||
file(GLOB_RECURSE typing_stubs_generation_files "${PYTHON_SOURCE_DIR}/src2/typing_stubs_generation/*.py")
|
||||
add_custom_command(
|
||||
OUTPUT ${cv2_generated_files}
|
||||
COMMAND "${PYTHON_DEFAULT_EXECUTABLE}" "${PYTHON_SOURCE_DIR}/src2/gen2.py"
|
||||
"--config" "${JSON_CONFIG_FILE_PATH}"
|
||||
"--output_dir" "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
DEPENDS "${PYTHON_SOURCE_DIR}/src2/gen2.py"
|
||||
"${PYTHON_SOURCE_DIR}/src2/hdr_parser.py"
|
||||
"${typing_stubs_generation_files}"
|
||||
"${PYTHON_SOURCE_DIR}/src2/typing_stubs_generator.py"
|
||||
# not a real build dependency (file(WRITE) result): ${CMAKE_CURRENT_BINARY_DIR}/headers.txt
|
||||
${opencv_hdrs}
|
||||
COMMENT "Generate files for Python bindings and documentation"
|
||||
)
|
||||
|
||||
add_custom_target(gen_opencv_python_source DEPENDS ${cv2_generated_files})
|
||||
|
||||
if(TARGET copy_opencv_typing_stubs)
|
||||
add_dependencies(copy_opencv_typing_stubs gen_opencv_python_source)
|
||||
endif()
|
||||
|
||||
set(cv2_custom_hdr "${CMAKE_CURRENT_BINARY_DIR}/pyopencv_custom_headers.h")
|
||||
set(cv2_custom_hdr_str "//user-defined headers\n")
|
||||
foreach(uh ${opencv_userdef_hdrs})
|
||||
set(cv2_custom_hdr_str "${cv2_custom_hdr_str}#include \"${uh}\"\n")
|
||||
endforeach(uh)
|
||||
if(EXISTS "${cv2_custom_hdr}")
|
||||
file(READ "${cv2_custom_hdr}" __content)
|
||||
else()
|
||||
set(__content "")
|
||||
endif()
|
||||
if("${__content}" STREQUAL "${cv2_custom_hdr_str}")
|
||||
# Up-to-date
|
||||
else()
|
||||
file(WRITE "${cv2_custom_hdr}" "${cv2_custom_hdr_str}")
|
||||
endif()
|
||||
unset(__content)
|
||||
|
||||
|
||||
#
|
||||
# Configuration for standalone build of Python bindings
|
||||
#
|
||||
set(PYTHON_CONFIG_SCRIPT "")
|
||||
ocv_cmake_script_append_var(PYTHON_CONFIG_SCRIPT
|
||||
CMAKE_BUILD_TYPE
|
||||
BUILD_SHARED_LIBS
|
||||
|
||||
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
|
||||
CV_GCC CV_CLANG ENABLE_NOISY_WARNINGS
|
||||
|
||||
CMAKE_MODULE_LINKER_FLAGS
|
||||
CMAKE_INSTALL_PREFIX
|
||||
OPENCV_PYTHON_INSTALL_PATH
|
||||
|
||||
OpenCV_SOURCE_DIR
|
||||
|
||||
OPENCV_FORCE_PYTHON_LIBS
|
||||
OPENCV_PYTHON_SKIP_LINKER_EXCLUDE_LIBS
|
||||
|
||||
OPENCV_PYTHON_BINDINGS_DIR
|
||||
cv2_custom_hdr
|
||||
cv2_generated_files
|
||||
)
|
||||
set(CMAKE_HELPER_SCRIPT "${CMAKE_BINARY_DIR}/opencv_python_config.cmake")
|
||||
file(GENERATE OUTPUT "${CMAKE_HELPER_SCRIPT}" CONTENT "${PYTHON_CONFIG_SCRIPT}")
|
||||
@@ -0,0 +1,244 @@
|
||||
# This file is included from a subdirectory
|
||||
set(PYTHON_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
ocv_add_module(${MODULE_NAME} BINDINGS PRIVATE_REQUIRED opencv_python_bindings_generator)
|
||||
|
||||
include_directories(SYSTEM
|
||||
"${${PYTHON}_INCLUDE_PATH}"
|
||||
${${PYTHON}_NUMPY_INCLUDE_DIRS}
|
||||
)
|
||||
ocv_module_include_directories(
|
||||
"${PYTHON_SOURCE_DIR}/src2"
|
||||
"${OPENCV_PYTHON_BINDINGS_DIR}"
|
||||
)
|
||||
|
||||
# try to use dynamic symbols linking with libpython.so
|
||||
set(OPENCV_FORCE_PYTHON_LIBS OFF CACHE BOOL "")
|
||||
string(REGEX REPLACE "(^| )-Wl,--no-undefined( |$)" " " CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
|
||||
if(NOT WIN32 AND NOT APPLE AND NOT OPENCV_PYTHON_SKIP_LINKER_EXCLUDE_LIBS)
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--exclude-libs=ALL")
|
||||
endif()
|
||||
|
||||
ocv_add_library(${the_module} MODULE
|
||||
${PYTHON_SOURCE_DIR}/src2/cv2.cpp
|
||||
${PYTHON_SOURCE_DIR}/src2/cv2_util.cpp
|
||||
${PYTHON_SOURCE_DIR}/src2/cv2_numpy.cpp
|
||||
${PYTHON_SOURCE_DIR}/src2/cv2_convert.cpp
|
||||
${PYTHON_SOURCE_DIR}/src2/cv2_highgui.cpp
|
||||
${cv2_generated_hdrs}
|
||||
${opencv_userdef_hdrs}
|
||||
${cv2_custom_hdr}
|
||||
)
|
||||
|
||||
if(TARGET gen_opencv_python_source)
|
||||
add_dependencies(${the_module} gen_opencv_python_source)
|
||||
endif()
|
||||
|
||||
if(TARGET copy_opencv_typing_stubs)
|
||||
# Python 3.6+
|
||||
add_dependencies(${the_module} copy_opencv_typing_stubs)
|
||||
endif()
|
||||
|
||||
ocv_assert(${PYTHON}_VERSION_MAJOR)
|
||||
ocv_assert(${PYTHON}_VERSION_MINOR)
|
||||
|
||||
if(${PYTHON}_LIMITED_API)
|
||||
# support only python3.3+
|
||||
ocv_assert(${PYTHON}_VERSION_MAJOR EQUAL 3 AND ${PYTHON}_VERSION_MINOR GREATER 2)
|
||||
target_compile_definitions(${the_module} PRIVATE CVPY_DYNAMIC_INIT)
|
||||
target_compile_definitions(${the_module} PRIVATE PYTHON3_LIMITED_API_VERSION=${PYTHON3_LIMITED_API_VERSION})
|
||||
if(WIN32)
|
||||
string(REPLACE
|
||||
"python${${PYTHON}_VERSION_MAJOR}${${PYTHON}_VERSION_MINOR}.lib"
|
||||
"python${${PYTHON}_VERSION_MAJOR}.lib"
|
||||
${PYTHON}_LIBRARIES
|
||||
"${${PYTHON}_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(${the_module} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
|
||||
elseif(WIN32 OR OPENCV_FORCE_PYTHON_LIBS)
|
||||
if(${PYTHON}_DEBUG_LIBRARIES AND NOT ${PYTHON}_LIBRARIES MATCHES "optimized.*debug")
|
||||
ocv_target_link_libraries(${the_module} PRIVATE ${${PYTHON}_LIBRARIES})
|
||||
ocv_target_link_libraries(${the_module} PRIVATE debug ${${PYTHON}_DEBUG_LIBRARIES} optimized ${${PYTHON}_LIBRARIES})
|
||||
else()
|
||||
ocv_target_link_libraries(${the_module} PRIVATE ${${PYTHON}_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TARGET gen_opencv_python_source)
|
||||
set(deps ${OPENCV_MODULE_${the_module}_DEPS})
|
||||
list(REMOVE_ITEM deps opencv_python_bindings_generator) # don't add dummy module to target_link_libraries list
|
||||
endif()
|
||||
ocv_target_link_libraries(${the_module} PRIVATE ${deps})
|
||||
|
||||
if(DEFINED ${PYTHON}_CVPY_SUFFIX)
|
||||
set(CVPY_SUFFIX "${${PYTHON}_CVPY_SUFFIX}")
|
||||
else()
|
||||
set(__python_ext_suffix_var "EXT_SUFFIX")
|
||||
if("${${PYTHON}_VERSION_MAJOR}" STREQUAL "2")
|
||||
set(__python_ext_suffix_var "SO")
|
||||
endif()
|
||||
execute_process(COMMAND ${${PYTHON}_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_config_var('${__python_ext_suffix_var}'))"
|
||||
RESULT_VARIABLE PYTHON_CVPY_PROCESS
|
||||
OUTPUT_VARIABLE CVPY_SUFFIX
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT PYTHON_CVPY_PROCESS EQUAL 0)
|
||||
set(CVPY_SUFFIX ".so")
|
||||
endif()
|
||||
if(${PYTHON}_LIMITED_API)
|
||||
if(WIN32)
|
||||
string(REGEX REPLACE "\\.[^\\.]*\\." "." CVPY_SUFFIX "${CVPY_SUFFIX}")
|
||||
else()
|
||||
string(REGEX REPLACE "\\.[^\\.]*\\." ".abi${${PYTHON}_VERSION_MAJOR}." CVPY_SUFFIX "${CVPY_SUFFIX}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
ocv_update(OPENCV_PYTHON_EXTENSION_BUILD_PATH "${LIBRARY_OUTPUT_PATH}/${MODULE_INSTALL_SUBDIR}")
|
||||
|
||||
set_target_properties(${the_module} PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${OPENCV_PYTHON_EXTENSION_BUILD_PATH}"
|
||||
ARCHIVE_OUTPUT_NAME ${the_module} # prevent name conflict for python2/3 outputs
|
||||
PREFIX ""
|
||||
OUTPUT_NAME cv2
|
||||
SUFFIX "${CVPY_SUFFIX}")
|
||||
|
||||
if(ENABLE_SOLUTION_FOLDERS)
|
||||
set_target_properties(${the_module} PROPERTIES FOLDER "bindings")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-DCVAPI_EXPORTS)
|
||||
endif()
|
||||
|
||||
if((CV_GCC OR CV_CLANG) AND NOT ENABLE_NOISY_WARNINGS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-function")
|
||||
endif()
|
||||
|
||||
if(MSVC AND NOT ENABLE_NOISY_WARNINGS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") #unreferenced formal parameter
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4127") #conditional expression is constant
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4505") #unreferenced local function has been removed
|
||||
string(REPLACE "/W4" "/W3" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
|
||||
if(MSVC)
|
||||
ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4996)
|
||||
else()
|
||||
ocv_warnings_disable(CMAKE_CXX_FLAGS
|
||||
-Wdeprecated-declarations
|
||||
-Woverloaded-virtual -Wunused-private-field
|
||||
-Wundef # accurate guard via #pragma doesn't work (C++ preprocessor doesn't handle #pragma)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC AND NOT BUILD_SHARED_LIBS)
|
||||
set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /DEBUG")
|
||||
endif()
|
||||
|
||||
if(MSVC AND NOT ${PYTHON}_DEBUG_LIBRARIES)
|
||||
set(PYTHON_INSTALL_CONFIGURATIONS CONFIGURATIONS Release)
|
||||
else()
|
||||
set(PYTHON_INSTALL_CONFIGURATIONS "")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(PYTHON_INSTALL_ARCHIVE "")
|
||||
else()
|
||||
set(PYTHON_INSTALL_ARCHIVE ARCHIVE DESTINATION ${${PYTHON}_PACKAGES_PATH} COMPONENT python)
|
||||
endif()
|
||||
|
||||
set(__python_loader_subdir "")
|
||||
if(NOT OPENCV_SKIP_PYTHON_LOADER)
|
||||
set(__python_loader_subdir "cv2/")
|
||||
endif()
|
||||
|
||||
if(NOT " ${PYTHON}" STREQUAL " PYTHON"
|
||||
AND NOT DEFINED OPENCV_PYTHON_INSTALL_PATH
|
||||
)
|
||||
if(DEFINED OPENCV_${PYTHON}_INSTALL_PATH)
|
||||
set(OPENCV_PYTHON_INSTALL_PATH "${OPENCV_${PYTHON}_INSTALL_PATH}")
|
||||
elseif(NOT OPENCV_SKIP_PYTHON_LOADER)
|
||||
set(OPENCV_PYTHON_INSTALL_PATH "${${PYTHON}_PACKAGES_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT OPENCV_SKIP_PYTHON_LOADER AND DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/python_loader.cmake")
|
||||
set(OPENCV_PYTHON_INSTALL_PATH_SETUPVARS "${OPENCV_PYTHON_INSTALL_PATH}" CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
if(OPENCV_SKIP_PYTHON_LOADER)
|
||||
if(DEFINED OPENCV_${PYTHON}_INSTALL_PATH)
|
||||
set(__python_binary_install_path "${OPENCV_${PYTHON}_INSTALL_PATH}")
|
||||
elseif(DEFINED ${PYTHON}_PACKAGES_PATH)
|
||||
set(__python_binary_install_path "${${PYTHON}_PACKAGES_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR "Specify 'OPENCV_${PYTHON}_INSTALL_PATH' variable")
|
||||
endif()
|
||||
else()
|
||||
ocv_assert(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
if(${PYTHON}_LIMITED_API)
|
||||
set(__python_binary_subdir "python-${${PYTHON}_VERSION_MAJOR}")
|
||||
else()
|
||||
set(__python_binary_subdir "python-${${PYTHON}_VERSION_MAJOR}.${${PYTHON}_VERSION_MINOR}")
|
||||
endif()
|
||||
set(__python_binary_install_path "${OPENCV_PYTHON_INSTALL_PATH}/${__python_loader_subdir}${__python_binary_subdir}")
|
||||
endif()
|
||||
|
||||
install(TARGETS ${the_module}
|
||||
${PYTHON_INSTALL_CONFIGURATIONS}
|
||||
RUNTIME DESTINATION "${__python_binary_install_path}" COMPONENT python
|
||||
LIBRARY DESTINATION "${__python_binary_install_path}" COMPONENT python
|
||||
${PYTHON_INSTALL_ARCHIVE}
|
||||
)
|
||||
|
||||
set(__INSTALL_PATH_${PYTHON} "${__python_binary_install_path}" CACHE INTERNAL "") # CMake status
|
||||
|
||||
if(NOT OPENCV_SKIP_PYTHON_LOADER)
|
||||
ocv_assert(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
if(OpenCV_FOUND)
|
||||
set(__loader_path "${OpenCV_BINARY_DIR}/python_loader")
|
||||
else()
|
||||
set(__loader_path "${CMAKE_BINARY_DIR}/python_loader")
|
||||
endif()
|
||||
|
||||
set(__python_loader_install_tmp_path "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/install/python_loader/")
|
||||
set(OpenCV_PYTHON_LOADER_FULL_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/${OPENCV_PYTHON_INSTALL_PATH}/cv2")
|
||||
if(IS_ABSOLUTE "${OPENCV_PYTHON_INSTALL_PATH}")
|
||||
set(CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE "'${OPENCV_PYTHON_INSTALL_PATH}/cv2'")
|
||||
else()
|
||||
set(CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE "LOADER_DIR")
|
||||
endif()
|
||||
|
||||
if(DEFINED ${PYTHON}_VERSION_MINOR AND NOT ${PYTHON}_LIMITED_API)
|
||||
set(__target_config "config-${${PYTHON}_VERSION_MAJOR}.${${PYTHON}_VERSION_MINOR}.py")
|
||||
else()
|
||||
set(__target_config "config-${${PYTHON}_VERSION_MAJOR}.py")
|
||||
endif()
|
||||
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
set(CMAKE_PYTHON_EXTENSION_PATH "'${OPENCV_PYTHON_EXTENSION_BUILD_PATH}/Release'") # TODO: CMAKE_BUILD_TYPE is not defined
|
||||
else()
|
||||
set(CMAKE_PYTHON_EXTENSION_PATH "'${OPENCV_PYTHON_EXTENSION_BUILD_PATH}'")
|
||||
endif()
|
||||
configure_file("${PYTHON_SOURCE_DIR}/package/template/config-x.y.py.in" "${__loader_path}/cv2/${__target_config}" @ONLY)
|
||||
|
||||
if(IS_ABSOLUTE __python_binary_install_path)
|
||||
set(CMAKE_PYTHON_EXTENSION_PATH "'${__python_binary_install_path}'")
|
||||
else()
|
||||
file(RELATIVE_PATH OpenCV_PYTHON_BINARY_RELATIVE_INSTALL_PATH "${OpenCV_PYTHON_LOADER_FULL_INSTALL_PATH}" "${CMAKE_INSTALL_PREFIX}/${__python_binary_install_path}")
|
||||
set(CMAKE_PYTHON_EXTENSION_PATH "os.path.join(${CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE}, '${OpenCV_PYTHON_BINARY_RELATIVE_INSTALL_PATH}')")
|
||||
endif()
|
||||
configure_file("${PYTHON_SOURCE_DIR}/package/template/config-x.y.py.in" "${__python_loader_install_tmp_path}/cv2/${__target_config}" @ONLY)
|
||||
install(FILES "${__python_loader_install_tmp_path}/cv2/${__target_config}" DESTINATION "${OPENCV_PYTHON_INSTALL_PATH}/cv2/" COMPONENT python)
|
||||
endif() # NOT OPENCV_SKIP_PYTHON_LOADER
|
||||
|
||||
unset(PYTHON_SRC_DIR)
|
||||
unset(PYTHON_CVPY_PROCESS)
|
||||
unset(CVPY_SUFFIX)
|
||||
unset(PYTHON_INSTALL_CONFIGURATIONS)
|
||||
unset(PYTHON_INSTALL_ARCHIVE)
|
||||
@@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.egg-info
|
||||
*dist
|
||||
@@ -0,0 +1,181 @@
|
||||
'''
|
||||
OpenCV Python binary extension loader
|
||||
'''
|
||||
import os
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
__all__ = []
|
||||
|
||||
try:
|
||||
import numpy
|
||||
import numpy.core.multiarray
|
||||
except ImportError:
|
||||
print('OpenCV bindings requires "numpy" package.')
|
||||
print('Install it via command:')
|
||||
print(' pip install numpy')
|
||||
raise
|
||||
|
||||
# TODO
|
||||
# is_x64 = sys.maxsize > 2**32
|
||||
|
||||
|
||||
def __load_extra_py_code_for_module(base, name, enable_debug_print=False):
|
||||
module_name = "{}.{}".format(__name__, name)
|
||||
export_module_name = "{}.{}".format(base, name)
|
||||
native_module = sys.modules.pop(module_name, None)
|
||||
try:
|
||||
py_module = importlib.import_module(module_name)
|
||||
except (ImportError, AttributeError) as err:
|
||||
if enable_debug_print:
|
||||
print("Can't load Python code for module:", module_name,
|
||||
". Reason:", err)
|
||||
# Extension doesn't contain extra py code
|
||||
return False
|
||||
|
||||
if base in sys.modules and not hasattr(sys.modules[base], name):
|
||||
setattr(sys.modules[base], name, py_module)
|
||||
sys.modules[export_module_name] = py_module
|
||||
# If it is C extension module it is already loaded by cv2 package
|
||||
if native_module:
|
||||
setattr(py_module, "_native", native_module)
|
||||
for k, v in filter(lambda kv: not hasattr(py_module, kv[0]),
|
||||
native_module.__dict__.items()):
|
||||
if enable_debug_print: print(' symbol({}): {} = {}'.format(name, k, v))
|
||||
setattr(py_module, k, v)
|
||||
return True
|
||||
|
||||
|
||||
def __collect_extra_submodules(enable_debug_print=False):
|
||||
def modules_filter(module):
|
||||
return all((
|
||||
# module is not internal
|
||||
not module.startswith("_"),
|
||||
not module.startswith("python-"),
|
||||
# it is not a file
|
||||
os.path.isdir(os.path.join(_extra_submodules_init_path, module))
|
||||
))
|
||||
if sys.version_info[0] < 3:
|
||||
if enable_debug_print:
|
||||
print("Extra submodules is loaded only for Python 3")
|
||||
return []
|
||||
|
||||
__INIT_FILE_PATH = os.path.abspath(__file__)
|
||||
_extra_submodules_init_path = os.path.dirname(__INIT_FILE_PATH)
|
||||
return filter(modules_filter, os.listdir(_extra_submodules_init_path))
|
||||
|
||||
|
||||
def bootstrap():
|
||||
import sys
|
||||
|
||||
import copy
|
||||
save_sys_path = copy.copy(sys.path)
|
||||
|
||||
if hasattr(sys, 'OpenCV_LOADER'):
|
||||
print(sys.path)
|
||||
raise ImportError('ERROR: recursion is detected during loading of "cv2" binary extensions. Check OpenCV installation.')
|
||||
sys.OpenCV_LOADER = True
|
||||
|
||||
DEBUG = False
|
||||
if hasattr(sys, 'OpenCV_LOADER_DEBUG'):
|
||||
DEBUG = True
|
||||
|
||||
import platform
|
||||
if DEBUG: print('OpenCV loader: os.name="{}" platform.system()="{}"'.format(os.name, str(platform.system())))
|
||||
|
||||
LOADER_DIR = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
|
||||
|
||||
PYTHON_EXTENSIONS_PATHS = []
|
||||
BINARIES_PATHS = []
|
||||
|
||||
g_vars = globals()
|
||||
l_vars = locals().copy()
|
||||
|
||||
if sys.version_info[:2] < (3, 0):
|
||||
from . load_config_py2 import exec_file_wrapper
|
||||
else:
|
||||
from . load_config_py3 import exec_file_wrapper
|
||||
|
||||
def load_first_config(fnames, required=True):
|
||||
for fname in fnames:
|
||||
fpath = os.path.join(LOADER_DIR, fname)
|
||||
if not os.path.exists(fpath):
|
||||
if DEBUG: print('OpenCV loader: config not found, skip: {}'.format(fpath))
|
||||
continue
|
||||
if DEBUG: print('OpenCV loader: loading config: {}'.format(fpath))
|
||||
exec_file_wrapper(fpath, g_vars, l_vars)
|
||||
return True
|
||||
if required:
|
||||
raise ImportError('OpenCV loader: missing configuration file: {}. Check OpenCV installation.'.format(fnames))
|
||||
|
||||
load_first_config(['config.py'], True)
|
||||
load_first_config([
|
||||
'config-{}.{}.py'.format(sys.version_info[0], sys.version_info[1]),
|
||||
'config-{}.py'.format(sys.version_info[0])
|
||||
], True)
|
||||
|
||||
if DEBUG: print('OpenCV loader: PYTHON_EXTENSIONS_PATHS={}'.format(str(l_vars['PYTHON_EXTENSIONS_PATHS'])))
|
||||
if DEBUG: print('OpenCV loader: BINARIES_PATHS={}'.format(str(l_vars['BINARIES_PATHS'])))
|
||||
|
||||
applySysPathWorkaround = False
|
||||
if hasattr(sys, 'OpenCV_REPLACE_SYS_PATH_0'):
|
||||
applySysPathWorkaround = True
|
||||
else:
|
||||
try:
|
||||
BASE_DIR = os.path.dirname(LOADER_DIR)
|
||||
if sys.path[0] == BASE_DIR or os.path.realpath(sys.path[0]) == BASE_DIR:
|
||||
applySysPathWorkaround = True
|
||||
except:
|
||||
if DEBUG: print('OpenCV loader: exception during checking workaround for sys.path[0]')
|
||||
pass # applySysPathWorkaround is False
|
||||
|
||||
for p in reversed(l_vars['PYTHON_EXTENSIONS_PATHS']):
|
||||
sys.path.insert(1 if not applySysPathWorkaround else 0, p)
|
||||
|
||||
if os.name == 'nt':
|
||||
if sys.version_info[:2] >= (3, 8): # https://github.com/python/cpython/pull/12302
|
||||
for p in l_vars['BINARIES_PATHS']:
|
||||
try:
|
||||
os.add_dll_directory(p)
|
||||
except Exception as e:
|
||||
if DEBUG: print('Failed os.add_dll_directory(): '+ str(e))
|
||||
pass
|
||||
os.environ['PATH'] = ';'.join(l_vars['BINARIES_PATHS']) + ';' + os.environ.get('PATH', '')
|
||||
if DEBUG: print('OpenCV loader: PATH={}'.format(str(os.environ['PATH'])))
|
||||
else:
|
||||
# amending of LD_LIBRARY_PATH works for sub-processes only
|
||||
os.environ['LD_LIBRARY_PATH'] = ':'.join(l_vars['BINARIES_PATHS']) + ':' + os.environ.get('LD_LIBRARY_PATH', '')
|
||||
|
||||
if DEBUG: print("Relink everything from native cv2 module to cv2 package")
|
||||
|
||||
py_module = sys.modules.pop("cv2")
|
||||
|
||||
native_module = importlib.import_module("cv2")
|
||||
|
||||
sys.modules["cv2"] = py_module
|
||||
setattr(py_module, "_native", native_module)
|
||||
|
||||
for item_name, item in filter(lambda kv: kv[0] not in ("__file__", "__loader__", "__spec__",
|
||||
"__name__", "__package__"),
|
||||
native_module.__dict__.items()):
|
||||
if item_name not in g_vars:
|
||||
g_vars[item_name] = item
|
||||
|
||||
sys.path = save_sys_path # multiprocessing should start from bootstrap code (https://github.com/opencv/opencv/issues/18502)
|
||||
|
||||
try:
|
||||
del sys.OpenCV_LOADER
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print("Exception during delete OpenCV_LOADER:", e)
|
||||
|
||||
if DEBUG: print('OpenCV loader: binary extension... OK')
|
||||
|
||||
for submodule in __collect_extra_submodules(DEBUG):
|
||||
if __load_extra_py_code_for_module("cv2", submodule, DEBUG):
|
||||
if DEBUG: print("Extra Python code for", submodule, "is loaded")
|
||||
|
||||
if DEBUG: print('OpenCV loader: DONE')
|
||||
|
||||
|
||||
bootstrap()
|
||||
@@ -0,0 +1,6 @@
|
||||
# flake8: noqa
|
||||
import sys
|
||||
|
||||
if sys.version_info[:2] < (3, 0):
|
||||
def exec_file_wrapper(fpath, g_vars, l_vars):
|
||||
execfile(fpath, g_vars, l_vars)
|
||||
@@ -0,0 +1,9 @@
|
||||
# flake8: noqa
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info[:2] >= (3, 0):
|
||||
def exec_file_wrapper(fpath, g_vars, l_vars):
|
||||
with open(fpath) as f:
|
||||
code = compile(f.read(), fpath, 'exec')
|
||||
exec(code, g_vars, l_vars)
|
||||
@@ -0,0 +1 @@
|
||||
from .version import get_ocv_version
|
||||
@@ -0,0 +1,5 @@
|
||||
import cv2
|
||||
|
||||
|
||||
def get_ocv_version():
|
||||
return getattr(cv2, "__version__", "unavailable")
|
||||
@@ -0,0 +1,82 @@
|
||||
import os
|
||||
import setuptools
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def collect_module_typing_stub_files(root_module_path):
|
||||
stub_files = []
|
||||
for module_path, _, files in os.walk(root_module_path):
|
||||
stub_files.extend(
|
||||
map(lambda p: os.path.join(module_path, p),
|
||||
filter(lambda f: f.endswith(".pyi"), files))
|
||||
)
|
||||
return stub_files
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(SCRIPT_DIR)
|
||||
|
||||
package_name = 'opencv'
|
||||
package_version = os.environ.get('OPENCV_VERSION', '5.0.0') # TODO
|
||||
|
||||
long_description = 'Open Source Computer Vision Library Python bindings' # TODO
|
||||
|
||||
root_module_path = os.path.join(SCRIPT_DIR, "cv2")
|
||||
py_typed_path = os.path.join(root_module_path, "py.typed")
|
||||
typing_stub_files = []
|
||||
if os.path.isfile(py_typed_path):
|
||||
typing_stub_files = collect_module_typing_stub_files(root_module_path)
|
||||
if len(typing_stub_files) > 0:
|
||||
typing_stub_files.append(py_typed_path)
|
||||
|
||||
setuptools.setup(
|
||||
name=package_name,
|
||||
version=package_version,
|
||||
url='https://github.com/opencv/opencv',
|
||||
license='Apache 2.0',
|
||||
description='OpenCV python bindings',
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
packages=setuptools.find_packages(),
|
||||
package_data={
|
||||
"cv2": typing_stub_files
|
||||
},
|
||||
maintainer="OpenCV Team",
|
||||
install_requires="numpy",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Education",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: Science/Research",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: C++",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Topic :: Scientific/Engineering",
|
||||
"Topic :: Scientific/Engineering :: Image Recognition",
|
||||
"Topic :: Software Development",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
PYTHON_EXTENSIONS_PATHS = [
|
||||
@CMAKE_PYTHON_EXTENSION_PATH@
|
||||
] + PYTHON_EXTENSIONS_PATHS
|
||||
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
BINARIES_PATHS = [
|
||||
@CMAKE_PYTHON_BINARIES_PATH@
|
||||
] + BINARIES_PATHS
|
||||
@@ -0,0 +1,14 @@
|
||||
if(NOT PYTHON3_INCLUDE_PATH OR NOT PYTHON3_NUMPY_INCLUDE_DIRS)
|
||||
ocv_module_disable(python3)
|
||||
endif()
|
||||
|
||||
set(the_description "The python3 bindings")
|
||||
set(MODULE_NAME python3)
|
||||
set(MODULE_INSTALL_SUBDIR python3)
|
||||
|
||||
set(PYTHON PYTHON3)
|
||||
|
||||
include(../common.cmake)
|
||||
|
||||
unset(MODULE_NAME)
|
||||
unset(MODULE_INSTALL_SUBDIR)
|
||||
@@ -0,0 +1,157 @@
|
||||
ocv_assert(NOT OPENCV_SKIP_PYTHON_LOADER)
|
||||
|
||||
set(PYTHON_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
if(OpenCV_FOUND)
|
||||
set(__loader_path "${OpenCV_BINARY_DIR}/python_loader")
|
||||
message(STATUS "OpenCV Python: during development append to PYTHONPATH: ${__loader_path}")
|
||||
else()
|
||||
set(__loader_path "${CMAKE_BINARY_DIR}/python_loader")
|
||||
endif()
|
||||
|
||||
set(__python_loader_install_tmp_path "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/install/python_loader/")
|
||||
if(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
if(IS_ABSOLUTE "${OPENCV_PYTHON_INSTALL_PATH}")
|
||||
set(OpenCV_PYTHON_INSTALL_PATH_RELATIVE_CONFIGCMAKE "${CMAKE_INSTALL_PREFIX}/")
|
||||
set(CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE "'${CMAKE_INSTALL_PREFIX}'")
|
||||
else()
|
||||
file(RELATIVE_PATH OpenCV_PYTHON_INSTALL_PATH_RELATIVE_CONFIGCMAKE "${CMAKE_INSTALL_PREFIX}/${OPENCV_PYTHON_INSTALL_PATH}/cv2" ${CMAKE_INSTALL_PREFIX})
|
||||
set(CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE "os.path.join(LOADER_DIR, '${OpenCV_PYTHON_INSTALL_PATH_RELATIVE_CONFIGCMAKE}')")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE "os.path.join(LOADER_DIR, 'not_installed')")
|
||||
endif()
|
||||
|
||||
if(OpenCV_FOUND)
|
||||
return() # Ignore "standalone" builds of Python bindings
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
set(PYTHON_LOADER_FILES
|
||||
"setup.py" "cv2/__init__.py"
|
||||
"cv2/load_config_py2.py" "cv2/load_config_py3.py"
|
||||
)
|
||||
foreach(fname ${PYTHON_LOADER_FILES})
|
||||
get_filename_component(__dir "${fname}" DIRECTORY)
|
||||
# avoid using of file(COPY) to rerun CMake on changes
|
||||
configure_file("${PYTHON_SOURCE_DIR}/package/${fname}" "${__loader_path}/${fname}" COPYONLY)
|
||||
if(fname STREQUAL "setup.py")
|
||||
if(OPENCV_PYTHON_SETUP_PY_INSTALL_PATH)
|
||||
install(FILES "${PYTHON_SOURCE_DIR}/package/${fname}" DESTINATION "${OPENCV_PYTHON_SETUP_PY_INSTALL_PATH}" COMPONENT python)
|
||||
endif()
|
||||
elseif(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
install(FILES "${PYTHON_SOURCE_DIR}/package/${fname}" DESTINATION "${OPENCV_PYTHON_INSTALL_PATH}/${__dir}" COMPONENT python)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(WIN32)
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_PATH "'${EXECUTABLE_OUTPUT_PATH}/Release'") # TODO: CMAKE_BUILD_TYPE is not defined
|
||||
else()
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_PATH "'${EXECUTABLE_OUTPUT_PATH}'")
|
||||
endif()
|
||||
else()
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_PATH "'${LIBRARY_OUTPUT_PATH}'")
|
||||
endif()
|
||||
string(REPLACE ";" ",\n " CMAKE_PYTHON_BINARIES_PATH "${CMAKE_PYTHON_BINARIES_PATH}")
|
||||
configure_file("${PYTHON_SOURCE_DIR}/package/template/config.py.in" "${__loader_path}/cv2/config.py" @ONLY)
|
||||
|
||||
|
||||
|
||||
# install
|
||||
if(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
if(WIN32)
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_INSTALL_PATH "os.path.join(${CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE}, '${OPENCV_BIN_INSTALL_PATH}')")
|
||||
else()
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_INSTALL_PATH "os.path.join(${CMAKE_PYTHON_EXTENSION_INSTALL_PATH_BASE}, '${OPENCV_LIB_INSTALL_PATH}')")
|
||||
endif()
|
||||
set(CMAKE_PYTHON_BINARIES_PATH "${CMAKE_PYTHON_BINARIES_INSTALL_PATH}")
|
||||
if (WIN32 AND HAVE_CUDA)
|
||||
set(_cuda_bin_dir "bin")
|
||||
if (ENABLE_CUDA_FIRST_CLASS_LANGUAGE)
|
||||
if (DEFINED CUDAToolkit_LIBRARY_ROOT)
|
||||
if(DEFINED CUDAToolkit_VERSION_MAJOR AND CUDAToolkit_VERSION_MAJOR GREATER_EQUAL 13)
|
||||
set(_cuda_bin_dir "bin/x64")
|
||||
endif()
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_PATH "os.path.join(os.getenv('CUDA_PATH', '${CUDAToolkit_LIBRARY_ROOT}'), '${_cuda_bin_dir}')")
|
||||
endif()
|
||||
else()
|
||||
if (DEFINED CUDA_TOOLKIT_ROOT_DIR)
|
||||
if(DEFINED CUDA_VERSION_MAJOR AND CUDA_VERSION_MAJOR GREATER_EQUAL 13)
|
||||
set(_cuda_bin_dir "bin/x64")
|
||||
endif()
|
||||
list(APPEND CMAKE_PYTHON_BINARIES_PATH "os.path.join(os.getenv('CUDA_PATH', '${CUDA_TOOLKIT_ROOT_DIR}'), '${_cuda_bin_dir}')")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
string(REPLACE ";" ",\n " CMAKE_PYTHON_BINARIES_PATH "${CMAKE_PYTHON_BINARIES_PATH}")
|
||||
configure_file("${PYTHON_SOURCE_DIR}/package/template/config.py.in" "${__python_loader_install_tmp_path}/cv2/config.py" @ONLY)
|
||||
install(FILES "${__python_loader_install_tmp_path}/cv2/config.py" DESTINATION "${OPENCV_PYTHON_INSTALL_PATH}/cv2/" COMPONENT python)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Handle Python extra code (submodules)
|
||||
#
|
||||
function(ocv_add_python_files_from_path search_path)
|
||||
file(GLOB_RECURSE extra_py_files
|
||||
RELATIVE "${search_path}"
|
||||
# Plain Python code
|
||||
"${search_path}/*.py"
|
||||
# Type annotations
|
||||
"${search_path}/*.pyi"
|
||||
)
|
||||
ocv_debug_message("Extra Py files for ${search_path}: ${extra_py_files}")
|
||||
if(extra_py_files)
|
||||
list(SORT extra_py_files)
|
||||
foreach(filename ${extra_py_files})
|
||||
get_filename_component(module "${filename}" DIRECTORY)
|
||||
if(NOT ${module} IN_LIST extra_modules)
|
||||
list(APPEND extra_modules ${module})
|
||||
endif()
|
||||
configure_file("${search_path}/${filename}" "${__loader_path}/cv2/${filename}" COPYONLY)
|
||||
if(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
install(FILES "${search_path}/${filename}" DESTINATION "${OPENCV_PYTHON_INSTALL_PATH}/cv2/${module}/" COMPONENT python)
|
||||
endif()
|
||||
endforeach()
|
||||
message(STATUS "Found '${extra_modules}' Python modules from ${search_path}")
|
||||
else()
|
||||
message(WARNING "Can't add Python files and modules from '${module_path}'. There is no .py or .pyi files")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
ocv_add_python_files_from_path("${PYTHON_SOURCE_DIR}/package/extra_modules")
|
||||
|
||||
foreach(m ${OPENCV_MODULES_BUILD})
|
||||
if (";${OPENCV_MODULE_${m}_WRAPPERS};" MATCHES ";python;" AND HAVE_${m}
|
||||
AND EXISTS "${OPENCV_MODULE_${m}_LOCATION}/misc/python/package"
|
||||
)
|
||||
ocv_add_python_files_from_path("${OPENCV_MODULE_${m}_LOCATION}/misc/python/package")
|
||||
endif()
|
||||
endforeach(m)
|
||||
|
||||
if(NOT "${OPENCV_PYTHON_EXTRA_MODULES_PATH}" STREQUAL "")
|
||||
foreach(extra_ocv_py_modules_path ${OPENCV_PYTHON_EXTRA_MODULES_PATH})
|
||||
ocv_add_python_files_from_path(${extra_ocv_py_modules_path})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(${PYTHON}_VERSION_STRING VERSION_GREATER "3.6" AND PYTHON_DEFAULT_VERSION VERSION_GREATER "3.6")
|
||||
add_custom_target(copy_opencv_typing_stubs)
|
||||
# Copy all generated stub files to python_loader directory only if
|
||||
# generation succeeds, this behvoir can't be achieved with default
|
||||
# CMake constructions, because failed generation produces a warning instead of
|
||||
# halts on hard error.
|
||||
add_custom_command(
|
||||
TARGET copy_opencv_typing_stubs
|
||||
POST_BUILD
|
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} ${PYTHON_SOURCE_DIR}/src2/copy_typings_stubs_on_success.py
|
||||
--stubs_dir ${OPENCV_PYTHON_BINDINGS_DIR}/cv2
|
||||
--output_dir ${__loader_path}/cv2
|
||||
)
|
||||
if(DEFINED OPENCV_PYTHON_INSTALL_PATH)
|
||||
install(DIRECTORY "${OPENCV_PYTHON_BINDINGS_DIR}/cv2" DESTINATION "${OPENCV_PYTHON_INSTALL_PATH}" COMPONENT python)
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,62 @@
|
||||
import argparse
|
||||
import warnings
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8, ):
|
||||
# shutil.copytree received the `dirs_exist_ok` parameter
|
||||
from functools import partial
|
||||
import shutil
|
||||
|
||||
copy_tree = partial(shutil.copytree, dirs_exist_ok=True)
|
||||
else:
|
||||
from distutils.dir_util import copy_tree
|
||||
|
||||
|
||||
def _remove_stale_pyi_files(directory):
|
||||
"""Remove .pyi files and py.typed markers from the directory tree.
|
||||
|
||||
During incremental builds, disabling a previously enabled module leaves
|
||||
stale typing stubs in the loader directory from a previous copy. Since
|
||||
copy_tree merges rather than replaces, those stale files persist.
|
||||
Removing all stub files before copying ensures only stubs for currently
|
||||
enabled modules are present. Runtime .py files are not affected.
|
||||
"""
|
||||
for dirpath, dirnames, filenames in os.walk(directory):
|
||||
for fname in filenames:
|
||||
if fname.endswith('.pyi') or fname == 'py.typed':
|
||||
os.remove(os.path.join(dirpath, fname))
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
py_typed_path = os.path.join(args.stubs_dir, 'py.typed')
|
||||
if not os.path.isfile(py_typed_path):
|
||||
warnings.warn(
|
||||
'{} is missing, it means that typings stubs generation is either '
|
||||
'failed or has been skipped. Ensure that Python 3.6+ is used for '
|
||||
'build and there is no warnings during Python source code '
|
||||
'generation phase.'.format(py_typed_path)
|
||||
)
|
||||
return
|
||||
if os.path.isdir(args.output_dir):
|
||||
_remove_stale_pyi_files(args.output_dir)
|
||||
copy_tree(args.stubs_dir, args.output_dir)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Copies generated typing stubs only when generation '
|
||||
'succeeded. This is identified by presence of the `py.typed` file '
|
||||
'inside typing stubs directory.'
|
||||
)
|
||||
parser.add_argument('--stubs_dir', type=str,
|
||||
help='Path to directory containing generated typing '
|
||||
'stubs file')
|
||||
parser.add_argument('--output_dir', type=str,
|
||||
help='Path to output directory')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,632 @@
|
||||
// must be defined before importing numpy headers
|
||||
// https://numpy.org/doc/1.17/reference/c-api.array.html#importing-the-api
|
||||
#define PY_ARRAY_UNIQUE_SYMBOL opencv_ARRAY_API
|
||||
|
||||
#include "cv2.hpp"
|
||||
|
||||
#include "opencv2/opencv_modules.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
#include "pyopencv_generated_include.h"
|
||||
|
||||
#include "cv2_util.hpp"
|
||||
#include "cv2_numpy.hpp"
|
||||
#include "cv2_convert.hpp"
|
||||
#include "cv2_highgui.hpp"
|
||||
|
||||
using namespace cv;
|
||||
|
||||
typedef std::vector<uchar> vector_uchar;
|
||||
typedef std::vector<char> vector_char;
|
||||
typedef std::vector<int> vector_int;
|
||||
typedef std::vector<float> vector_float;
|
||||
typedef std::vector<double> vector_double;
|
||||
typedef std::vector<size_t> vector_size_t;
|
||||
typedef std::vector<Point> vector_Point;
|
||||
typedef std::vector<Point2f> vector_Point2f;
|
||||
typedef std::vector<Point3f> vector_Point3f;
|
||||
typedef std::vector<Size> vector_Size;
|
||||
typedef std::vector<Vec2f> vector_Vec2f;
|
||||
typedef std::vector<Vec3f> vector_Vec3f;
|
||||
typedef std::vector<Vec4f> vector_Vec4f;
|
||||
typedef std::vector<Vec6f> vector_Vec6f;
|
||||
typedef std::vector<Vec4i> vector_Vec4i;
|
||||
typedef std::vector<Rect> vector_Rect;
|
||||
typedef std::vector<Rect2d> vector_Rect2d;
|
||||
typedef std::vector<RotatedRect> vector_RotatedRect;
|
||||
typedef std::vector<KeyPoint> vector_KeyPoint;
|
||||
typedef std::vector<Mat> vector_Mat;
|
||||
typedef std::vector<std::vector<Mat> > vector_vector_Mat;
|
||||
typedef std::vector<UMat> vector_UMat;
|
||||
typedef std::vector<DMatch> vector_DMatch;
|
||||
typedef std::vector<String> vector_String;
|
||||
typedef std::vector<std::string> vector_string;
|
||||
typedef std::vector<Scalar> vector_Scalar;
|
||||
#ifdef HAVE_OPENCV_OBJDETECT
|
||||
typedef std::vector<aruco::Dictionary> vector_Dictionary;
|
||||
#endif // HAVE_OPENCV_OBJDETECT
|
||||
#ifdef HAVE_OPENCV_GEOMETRY
|
||||
typedef std::vector<MSTEdge> vector_MSTEdge;
|
||||
#endif // HAVE_OPENCV_GEOMETRY
|
||||
|
||||
typedef std::vector<std::vector<char> > vector_vector_char;
|
||||
typedef std::vector<std::vector<Point> > vector_vector_Point;
|
||||
typedef std::vector<std::vector<Point2f> > vector_vector_Point2f;
|
||||
typedef std::vector<std::vector<Point3f> > vector_vector_Point3f;
|
||||
typedef std::vector<std::vector<DMatch> > vector_vector_DMatch;
|
||||
typedef std::vector<std::vector<KeyPoint> > vector_vector_KeyPoint;
|
||||
|
||||
// enum { ARG_NONE = 0, ARG_MAT = 1, ARG_SCALAR = 2 };
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int convert_to_char(PyObject *o, char *dst, const ArgInfo& info)
|
||||
{
|
||||
std::string str;
|
||||
if (getUnicodeString(o, str))
|
||||
{
|
||||
*dst = str[0];
|
||||
return 1;
|
||||
}
|
||||
(*dst) = 0;
|
||||
return failmsg("Expected single character string for argument '%s'", info.name);
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif
|
||||
|
||||
|
||||
#include "pyopencv_generated_enums.h"
|
||||
|
||||
#ifdef CVPY_DYNAMIC_INIT
|
||||
#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(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
|
||||
#include "pyopencv_custom_headers.h"
|
||||
|
||||
#include "pyopencv_generated_types_content.h"
|
||||
#include "pyopencv_generated_funcs.h"
|
||||
|
||||
static PyObject* pycvRegisterMatType(PyObject *self, PyObject *value)
|
||||
{
|
||||
CV_LOG_DEBUG(NULL, cv::format("pycvRegisterMatType %p %p\n", self, value));
|
||||
|
||||
if (0 == PyType_Check(value))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Type argument is expected");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(value);
|
||||
pyopencv_Mat_TypePtr = (PyTypeObject*)value;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef special_methods[] = {
|
||||
{"_registerMatType", (PyCFunction)(pycvRegisterMatType), METH_O, "_registerMatType(cv.Mat) -> None (Internal)"},
|
||||
{"redirectError", CV_PY_FN_WITH_KW(pycvRedirectError), "redirectError(onError) -> None"},
|
||||
#ifdef HAVE_OPENCV_HIGHGUI
|
||||
{"createTrackbar", (PyCFunction)pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"},
|
||||
{"createButton", CV_PY_FN_WITH_KW(pycvCreateButton), "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"},
|
||||
{"setMouseCallback", CV_PY_FN_WITH_KW(pycvSetMouseCallback), "setMouseCallback(windowName, onMouse [, param]) -> None"},
|
||||
#endif
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
{"dnn_registerLayer", CV_PY_FN_WITH_KW(pyopencv_cv_dnn_registerLayer), "registerLayer(type, class) -> None"},
|
||||
{"dnn_unregisterLayer", CV_PY_FN_WITH_KW(pyopencv_cv_dnn_unregisterLayer), "unregisterLayer(type) -> None"},
|
||||
#endif
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
/* Module init */
|
||||
|
||||
struct ConstDef
|
||||
{
|
||||
const char * name;
|
||||
long long val;
|
||||
};
|
||||
|
||||
static inline bool strStartsWith(const std::string& str, const std::string& prefix) {
|
||||
return prefix.empty() || \
|
||||
(str.size() >= prefix.size() && std::memcmp(str.data(), prefix.data(), prefix.size()) == 0);
|
||||
}
|
||||
|
||||
static inline bool strEndsWith(const std::string& str, char symbol) {
|
||||
return !str.empty() && str[str.size() - 1] == symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Creates a submodule of the `root`. Missing parents submodules
|
||||
* are created as needed. If name equals to parent module name than
|
||||
* borrowed reference to parent module is returned (no reference counting
|
||||
* are done).
|
||||
* Submodule lifetime is managed by the parent module.
|
||||
* If nested submodules are created than the lifetime is managed by the
|
||||
* predecessor submodule in a list.
|
||||
*
|
||||
* \param parent_module Parent module object.
|
||||
* \param name Submodule name.
|
||||
* \return borrowed reference to the created submodule.
|
||||
* If any of submodules can't be created than NULL is returned.
|
||||
*/
|
||||
static PyObject* createSubmodule(PyObject* parent_module, const std::string& name)
|
||||
{
|
||||
if (!parent_module)
|
||||
{
|
||||
return PyErr_Format(PyExc_ImportError,
|
||||
"Bindings generation error. "
|
||||
"Parent module is NULL during the submodule '%s' creation",
|
||||
name.c_str()
|
||||
);
|
||||
}
|
||||
if (strEndsWith(name, '.'))
|
||||
{
|
||||
return PyErr_Format(PyExc_ImportError,
|
||||
"Bindings generation error. "
|
||||
"Submodule can't end with a dot. Got: %s", name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
const std::string parent_name = PyModule_GetName(parent_module);
|
||||
|
||||
/// Special case handling when caller tries to register a submodule of the parent module with
|
||||
/// the same name
|
||||
if (name == parent_name) {
|
||||
return parent_module;
|
||||
}
|
||||
|
||||
if (!strStartsWith(name, parent_name))
|
||||
{
|
||||
return PyErr_Format(PyExc_ImportError,
|
||||
"Bindings generation error. "
|
||||
"Submodule name should always start with a parent module name. "
|
||||
"Parent name: %s. Submodule name: %s", parent_name.c_str(),
|
||||
name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
size_t submodule_name_end = name.find('.', parent_name.size() + 1);
|
||||
/// There is no intermediate submodules in the provided name
|
||||
if (submodule_name_end == std::string::npos)
|
||||
{
|
||||
submodule_name_end = name.size();
|
||||
}
|
||||
|
||||
PyObject* submodule = parent_module;
|
||||
|
||||
for (size_t submodule_name_start = parent_name.size() + 1;
|
||||
submodule_name_start < name.size(); )
|
||||
{
|
||||
const std::string submodule_name = name.substr(submodule_name_start,
|
||||
submodule_name_end - submodule_name_start);
|
||||
|
||||
const std::string full_submodule_name = name.substr(0, submodule_name_end);
|
||||
|
||||
|
||||
PyObject* parent_module_dict = PyModule_GetDict(submodule);
|
||||
/// If submodule already exists it can be found in the parent module dictionary,
|
||||
/// otherwise it should be added to it.
|
||||
submodule = PyDict_GetItemString(parent_module_dict,
|
||||
submodule_name.c_str());
|
||||
if (!submodule)
|
||||
{
|
||||
/// Populates global modules dictionary and returns borrowed reference to it
|
||||
submodule = PyImport_AddModule(full_submodule_name.c_str());
|
||||
if (!submodule)
|
||||
{
|
||||
/// Return `PyImport_AddModule` NULL with an exception set on failure.
|
||||
return NULL;
|
||||
}
|
||||
/// Populates parent module dictionary. Submodule lifetime should be managed
|
||||
/// by the global modules dictionary and parent module dictionary, so Py_DECREF after
|
||||
/// successful call to the `PyDict_SetItemString` is redundant.
|
||||
if (PyDict_SetItemString(parent_module_dict, submodule_name.c_str(), submodule) < 0) {
|
||||
return PyErr_Format(PyExc_ImportError,
|
||||
"Can't register a submodule '%s' (full name: '%s')",
|
||||
submodule_name.c_str(), full_submodule_name.c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
submodule_name_start = submodule_name_end + 1;
|
||||
|
||||
submodule_name_end = name.find('.', submodule_name_start);
|
||||
if (submodule_name_end == std::string::npos) {
|
||||
submodule_name_end = name.size();
|
||||
}
|
||||
}
|
||||
return submodule;
|
||||
}
|
||||
|
||||
static bool init_submodule(PyObject * root, const char * name, PyMethodDef * methods, ConstDef * consts)
|
||||
{
|
||||
// traverse and create nested submodules
|
||||
PyObject* submodule = createSubmodule(root, name);
|
||||
if (!submodule)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// populate module's dict
|
||||
PyObject * d = PyModule_GetDict(submodule);
|
||||
for (PyMethodDef * m = methods; m->ml_name != NULL; ++m)
|
||||
{
|
||||
PyObject * method_obj = PyCFunction_NewEx(m, NULL, NULL);
|
||||
if (PyDict_SetItemString(d, m->ml_name, method_obj) < 0)
|
||||
{
|
||||
PyErr_Format(PyExc_ImportError,
|
||||
"Can't register function %s in module: %s", m->ml_name, name
|
||||
);
|
||||
Py_CLEAR(method_obj);
|
||||
return false;
|
||||
}
|
||||
Py_DECREF(method_obj);
|
||||
}
|
||||
for (ConstDef * c = consts; c->name != NULL; ++c)
|
||||
{
|
||||
PyObject* const_obj = PyLong_FromLongLong(c->val);
|
||||
if (PyDict_SetItemString(d, c->name, const_obj) < 0)
|
||||
{
|
||||
PyErr_Format(PyExc_ImportError,
|
||||
"Can't register constant %s in module %s", c->name, name
|
||||
);
|
||||
Py_CLEAR(const_obj);
|
||||
return false;
|
||||
}
|
||||
Py_DECREF(const_obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool registerTypeInModuleScope(PyObject* module, const char* type_name, PyObject* type_obj)
|
||||
{
|
||||
Py_INCREF(type_obj); /// Give PyModule_AddObject a reference to steal.
|
||||
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<PyTypeObject*>(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<PyTypeObject*>(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)
|
||||
{
|
||||
#define CVPY_MODULE(NAMESTR, NAME) \
|
||||
if (!init_submodule(m, MODULESTR NAMESTR, methods_##NAME, consts_##NAME)) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
#include "pyopencv_generated_modules.h"
|
||||
#undef CVPY_MODULE
|
||||
|
||||
#ifdef CVPY_DYNAMIC_INIT
|
||||
#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(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"
|
||||
#undef CVPY_TYPE
|
||||
|
||||
PyObject* d = PyModule_GetDict(m);
|
||||
|
||||
|
||||
PyObject* version_obj = PyString_FromString(CV_VERSION);
|
||||
if (PyDict_SetItemString(d, "__version__", version_obj) < 0) {
|
||||
PyErr_SetString(PyExc_ImportError, "Can't update module version");
|
||||
Py_CLEAR(version_obj);
|
||||
return false;
|
||||
}
|
||||
Py_DECREF(version_obj);
|
||||
|
||||
PyObject *opencv_error_dict = PyDict_New();
|
||||
PyDict_SetItemString(opencv_error_dict, "file", Py_None);
|
||||
PyDict_SetItemString(opencv_error_dict, "func", Py_None);
|
||||
PyDict_SetItemString(opencv_error_dict, "line", Py_None);
|
||||
PyDict_SetItemString(opencv_error_dict, "code", Py_None);
|
||||
PyDict_SetItemString(opencv_error_dict, "msg", Py_None);
|
||||
PyDict_SetItemString(opencv_error_dict, "err", Py_None);
|
||||
opencv_error = PyErr_NewException((char*)MODULESTR".error", NULL, opencv_error_dict);
|
||||
Py_DECREF(opencv_error_dict);
|
||||
PyDict_SetItemString(d, "error", opencv_error);
|
||||
|
||||
|
||||
#define PUBLISH_(I, var_name, type_obj) \
|
||||
PyObject* type_obj = PyInt_FromLong(I); \
|
||||
if (PyDict_SetItemString(d, var_name, type_obj) < 0) \
|
||||
{ \
|
||||
PyErr_SetString(PyExc_ImportError, "Can't register " var_name " constant"); \
|
||||
Py_CLEAR(type_obj); \
|
||||
return false; \
|
||||
} \
|
||||
Py_DECREF(type_obj);
|
||||
|
||||
#define PUBLISH(I) PUBLISH_(I, #I, I ## _obj)
|
||||
|
||||
PUBLISH(CV_8U);
|
||||
PUBLISH(CV_8UC1);
|
||||
PUBLISH(CV_8UC2);
|
||||
PUBLISH(CV_8UC3);
|
||||
PUBLISH(CV_8UC4);
|
||||
PUBLISH(CV_8S);
|
||||
PUBLISH(CV_8SC1);
|
||||
PUBLISH(CV_8SC2);
|
||||
PUBLISH(CV_8SC3);
|
||||
PUBLISH(CV_8SC4);
|
||||
PUBLISH(CV_16U);
|
||||
PUBLISH(CV_16UC1);
|
||||
PUBLISH(CV_16UC2);
|
||||
PUBLISH(CV_16UC3);
|
||||
PUBLISH(CV_16UC4);
|
||||
PUBLISH(CV_16S);
|
||||
PUBLISH(CV_16SC1);
|
||||
PUBLISH(CV_16SC2);
|
||||
PUBLISH(CV_16SC3);
|
||||
PUBLISH(CV_16SC4);
|
||||
PUBLISH(CV_32U);
|
||||
PUBLISH(CV_32UC1);
|
||||
PUBLISH(CV_32UC2);
|
||||
PUBLISH(CV_32UC3);
|
||||
PUBLISH(CV_32UC4);
|
||||
PUBLISH(CV_32S);
|
||||
PUBLISH(CV_32SC1);
|
||||
PUBLISH(CV_32SC2);
|
||||
PUBLISH(CV_32SC3);
|
||||
PUBLISH(CV_32SC4);
|
||||
PUBLISH(CV_64U);
|
||||
PUBLISH(CV_64UC1);
|
||||
PUBLISH(CV_64UC2);
|
||||
PUBLISH(CV_64UC3);
|
||||
PUBLISH(CV_64UC4);
|
||||
PUBLISH(CV_64S);
|
||||
PUBLISH(CV_64SC1);
|
||||
PUBLISH(CV_64SC2);
|
||||
PUBLISH(CV_64SC3);
|
||||
PUBLISH(CV_64SC4);
|
||||
PUBLISH(CV_32F);
|
||||
PUBLISH(CV_32FC1);
|
||||
PUBLISH(CV_32FC2);
|
||||
PUBLISH(CV_32FC3);
|
||||
PUBLISH(CV_32FC4);
|
||||
PUBLISH(CV_64F);
|
||||
PUBLISH(CV_64FC1);
|
||||
PUBLISH(CV_64FC2);
|
||||
PUBLISH(CV_64FC3);
|
||||
PUBLISH(CV_64FC4);
|
||||
PUBLISH(CV_16F);
|
||||
PUBLISH(CV_16FC1);
|
||||
PUBLISH(CV_16FC2);
|
||||
PUBLISH(CV_16FC3);
|
||||
PUBLISH(CV_16FC4);
|
||||
PUBLISH(CV_Bool);
|
||||
PUBLISH(CV_BoolC1);
|
||||
PUBLISH(CV_BoolC2);
|
||||
PUBLISH(CV_BoolC3);
|
||||
PUBLISH(CV_BoolC4);
|
||||
#undef PUBLISH_
|
||||
#undef PUBLISH
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC visibility push(default)
|
||||
#endif
|
||||
|
||||
#if defined(CV_PYTHON_3)
|
||||
// === Python 3
|
||||
|
||||
static struct PyModuleDef cv2_moduledef =
|
||||
{
|
||||
PyModuleDef_HEAD_INIT,
|
||||
MODULESTR,
|
||||
"Python wrapper for OpenCV.",
|
||||
-1, /* size of per-interpreter state of the module,
|
||||
or -1 if the module keeps state in global variables. */
|
||||
special_methods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_cv2();
|
||||
PyObject* PyInit_cv2()
|
||||
{
|
||||
import_array(); // from numpy
|
||||
PyObject* m = PyModule_Create(&cv2_moduledef);
|
||||
if (!init_body(m))
|
||||
return NULL;
|
||||
return m;
|
||||
}
|
||||
|
||||
#else
|
||||
// === Python 2
|
||||
PyMODINIT_FUNC initcv2();
|
||||
void initcv2()
|
||||
{
|
||||
import_array(); // from numpy
|
||||
PyObject* m = Py_InitModule(MODULESTR, special_methods);
|
||||
init_body(m);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef CV2_HPP
|
||||
#define CV2_HPP
|
||||
|
||||
//warning number '5033' not a valid compiler warning in vc12
|
||||
#if defined(_MSC_VER) && (_MSC_VER > 1800)
|
||||
// eliminating duplicated round() declaration
|
||||
#define HAVE_ROUND 1
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:5033) // 'register' is no longer a supported storage class
|
||||
#endif
|
||||
|
||||
// #define CVPY_DYNAMIC_INIT
|
||||
// #define Py_DEBUG
|
||||
|
||||
#if defined(CVPY_DYNAMIC_INIT) && !defined(Py_DEBUG)
|
||||
# ifndef PYTHON3_LIMITED_API_VERSION
|
||||
# define PYTHON3_LIMITED_API_VERSION 0x03060000
|
||||
# endif
|
||||
# define Py_LIMITED_API PYTHON3_LIMITED_API_VERSION
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <Python.h>
|
||||
#include <limits>
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
#undef CVPY_DYNAMIC_INIT
|
||||
#else
|
||||
#define CV_PYTHON_3 1
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER > 1800)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#define MODULESTR "cv2"
|
||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include "pycompat.hpp"
|
||||
|
||||
class ArgInfo
|
||||
{
|
||||
private:
|
||||
static const uint32_t arg_outputarg_flag = 0x1;
|
||||
static const uint32_t arg_arithm_op_src_flag = 0x2;
|
||||
static const uint32_t arg_pathlike_flag = 0x4;
|
||||
static const uint32_t arg_nd_mat_flag = 0x8;
|
||||
|
||||
public:
|
||||
const char* name;
|
||||
bool outputarg;
|
||||
bool arithm_op_src;
|
||||
bool pathlike;
|
||||
bool nd_mat;
|
||||
// more fields may be added if necessary
|
||||
|
||||
ArgInfo(const char* name_, uint32_t arg_) :
|
||||
name(name_),
|
||||
outputarg((arg_ & arg_outputarg_flag) != 0),
|
||||
arithm_op_src((arg_ & arg_arithm_op_src_flag) != 0),
|
||||
pathlike((arg_ & arg_pathlike_flag) != 0),
|
||||
nd_mat((arg_ & arg_nd_mat_flag) != 0) {}
|
||||
|
||||
private:
|
||||
ArgInfo(const ArgInfo&) = delete;
|
||||
ArgInfo& operator=(const ArgInfo&) = delete;
|
||||
};
|
||||
|
||||
|
||||
#endif // CV2_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,646 @@
|
||||
#ifndef CV2_CONVERT_HPP
|
||||
#define CV2_CONVERT_HPP
|
||||
|
||||
#include "cv2.hpp"
|
||||
#include "cv2_util.hpp"
|
||||
#include "cv2_numpy.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <type_traits> // std::enable_if
|
||||
|
||||
extern PyTypeObject* pyopencv_Mat_TypePtr;
|
||||
|
||||
#define CV_HAS_CONVERSION_ERROR(x) (((x) == -1) && PyErr_Occurred())
|
||||
|
||||
inline bool isBool(PyObject* obj) CV_NOEXCEPT
|
||||
{
|
||||
return PyArray_IsScalar(obj, Bool) || PyBool_Check(obj);
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
|
||||
// exception-safe pyopencv_to
|
||||
template<typename _Tp> static
|
||||
bool pyopencv_to_safe(PyObject* obj, _Tp& value, const ArgInfo& info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return pyopencv_to(obj, value, info);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
PyErr_SetString(opencv_error, cv::format("Conversion error: %s, what: %s", info.name, e.what()).c_str());
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
PyErr_SetString(opencv_error, cv::format("Conversion error: %s", info.name).c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
template<typename T, class TEnable = void> // TEnable is used for SFINAE checks
|
||||
struct PyOpenCV_Converter
|
||||
{
|
||||
//static inline bool to(PyObject* obj, T& p, const ArgInfo& info);
|
||||
//static inline PyObject* from(const T& src);
|
||||
};
|
||||
|
||||
// --- Generic
|
||||
|
||||
template<typename T>
|
||||
bool pyopencv_to(PyObject* obj, T& p, const ArgInfo& info) { return PyOpenCV_Converter<T>::to(obj, p, info); }
|
||||
|
||||
template<typename T>
|
||||
PyObject* pyopencv_from(const T& src) { return PyOpenCV_Converter<T>::from(src); }
|
||||
|
||||
// --- Matx
|
||||
|
||||
template<typename _Tp, int m, int n>
|
||||
bool pyopencv_to(PyObject* o, cv::Matx<_Tp, m, n>& mx, const ArgInfo& info)
|
||||
{
|
||||
if (!o || o == Py_None) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cv::Mat tmp;
|
||||
if (!pyopencv_to(o, tmp, info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp.copyTo(mx);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename _Tp, int m, int n>
|
||||
PyObject* pyopencv_from(const cv::Matx<_Tp, m, n>& matx)
|
||||
{
|
||||
return pyopencv_from(cv::Mat(matx));
|
||||
}
|
||||
|
||||
// --- bool
|
||||
template<> bool pyopencv_to(PyObject* obj, bool& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const bool& value);
|
||||
|
||||
// --- Mat
|
||||
template<> bool pyopencv_to(PyObject* o, cv::Mat& m, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Mat& m);
|
||||
|
||||
// --- Ptr
|
||||
template<typename T>
|
||||
struct PyOpenCV_Converter< cv::Ptr<T> >
|
||||
{
|
||||
static PyObject* from(const cv::Ptr<T>& p)
|
||||
{
|
||||
if (!p)
|
||||
Py_RETURN_NONE;
|
||||
return pyopencv_from(*p);
|
||||
}
|
||||
static bool to(PyObject *o, cv::Ptr<T>& p, const ArgInfo& info)
|
||||
{
|
||||
if (!o || o == Py_None)
|
||||
return true;
|
||||
p = cv::makePtr<T>();
|
||||
return pyopencv_to(o, *p, info);
|
||||
}
|
||||
};
|
||||
|
||||
// --- ptr
|
||||
template<> bool pyopencv_to(PyObject* obj, void*& ptr, const ArgInfo& info);
|
||||
PyObject* pyopencv_from(void*& ptr);
|
||||
|
||||
// --- Scalar
|
||||
template<> bool pyopencv_to(PyObject *o, cv::Scalar& s, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Scalar& src);
|
||||
|
||||
// --- size_t
|
||||
template<> bool pyopencv_to(PyObject* obj, size_t& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const size_t& value);
|
||||
|
||||
// --- int
|
||||
template<> bool pyopencv_to(PyObject* obj, int& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const int& value);
|
||||
|
||||
// --- int64
|
||||
template<> bool pyopencv_to(PyObject* obj, int64& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const int64& value);
|
||||
|
||||
// There is conflict between "size_t" and "unsigned int".
|
||||
// They are the same type on some 32-bit platforms.
|
||||
template<typename T>
|
||||
struct PyOpenCV_Converter
|
||||
< T, typename std::enable_if< std::is_same<unsigned int, T>::value && !std::is_same<unsigned int, size_t>::value >::type >
|
||||
{
|
||||
static inline PyObject* from(const unsigned int& value)
|
||||
{
|
||||
return PyLong_FromUnsignedLong(value);
|
||||
}
|
||||
|
||||
static inline bool to(PyObject* obj, unsigned int& value, const ArgInfo& info)
|
||||
{
|
||||
CV_UNUSED(info);
|
||||
if(!obj || obj == Py_None)
|
||||
return true;
|
||||
if(PyInt_Check(obj))
|
||||
value = (unsigned int)PyInt_AsLong(obj);
|
||||
else if(PyLong_Check(obj))
|
||||
value = (unsigned int)PyLong_AsLong(obj);
|
||||
else
|
||||
return false;
|
||||
return value != (unsigned int)-1 || !PyErr_Occurred();
|
||||
}
|
||||
};
|
||||
|
||||
// There is conflict between "uint64_t" and "size_t".
|
||||
// They are the same type on some 32-bit platforms.
|
||||
template<typename T>
|
||||
struct PyOpenCV_Converter
|
||||
< T, typename std::enable_if< std::is_same<uint64_t, T>::value && !std::is_same<uint64_t, size_t>::value >::type >
|
||||
{
|
||||
static inline PyObject* from(const uint64_t& value)
|
||||
{
|
||||
return PyLong_FromUnsignedLongLong(value);
|
||||
}
|
||||
|
||||
static inline bool to(PyObject* obj, uint64_t& value, const ArgInfo& info)
|
||||
{
|
||||
CV_UNUSED(info);
|
||||
if(!obj || obj == Py_None)
|
||||
return true;
|
||||
if(PyInt_Check(obj))
|
||||
value = (uint64_t)PyInt_AsUnsignedLongLongMask(obj);
|
||||
else if(PyLong_Check(obj))
|
||||
value = (uint64_t)PyLong_AsUnsignedLongLong(obj);
|
||||
else
|
||||
return false;
|
||||
return value != (uint64_t)-1 || !PyErr_Occurred();
|
||||
}
|
||||
};
|
||||
|
||||
// There is conflict between "long long" and "int64".
|
||||
// They are the same type on some 32-bit platforms.
|
||||
template<typename T>
|
||||
struct PyOpenCV_Converter
|
||||
< T, typename std::enable_if< std::is_same<long long, T>::value && !std::is_same<long long, int64>::value >::type >
|
||||
{
|
||||
static inline PyObject* from(const long long& value)
|
||||
{
|
||||
return PyLong_FromLongLong(value);
|
||||
}
|
||||
|
||||
static inline bool to(PyObject* obj, long long& value, const ArgInfo& info)
|
||||
{
|
||||
CV_UNUSED(info);
|
||||
if(!obj || obj == Py_None)
|
||||
return true;
|
||||
else if(PyLong_Check(obj))
|
||||
value = PyLong_AsLongLong(obj);
|
||||
else
|
||||
return false;
|
||||
return value != (long long)-1 || !PyErr_Occurred();
|
||||
}
|
||||
};
|
||||
|
||||
// --- uchar
|
||||
template<> bool pyopencv_to(PyObject* obj, uchar& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const uchar& value);
|
||||
|
||||
// --- char
|
||||
template<> bool pyopencv_to(PyObject* obj, char& value, const ArgInfo& info);
|
||||
|
||||
// --- double
|
||||
template<> bool pyopencv_to(PyObject* obj, double& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const double& value);
|
||||
|
||||
// --- float
|
||||
template<> bool pyopencv_to(PyObject* obj, float& value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const float& value);
|
||||
|
||||
// --- string
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::String &value, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::String& value);
|
||||
#if CV_VERSION_MAJOR == 3
|
||||
template<> PyObject* pyopencv_from(const std::string& value);
|
||||
#endif
|
||||
|
||||
// --- Size
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Size& sz, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Size& sz);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Size_<float>& sz, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Size_<float>& sz);
|
||||
|
||||
// --- Rect
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Rect& r, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Rect& r);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Rect2f& r, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Rect2f& r);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Rect2d& r, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Rect2d& r);
|
||||
|
||||
// --- RotatedRect
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::RotatedRect& dst, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::RotatedRect& src);
|
||||
|
||||
// --- Range
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Range& r, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Range& r);
|
||||
|
||||
// --- Point
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point& p);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point2f& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point2f& p);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point2d& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point2d& p);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point3i& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point3i& p);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point3f& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point3f& p);
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::Point3d& p, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::Point3d& p);
|
||||
|
||||
// --- Vec
|
||||
template<typename _Tp, int cn>
|
||||
bool pyopencv_to(PyObject* o, cv::Vec<_Tp, cn>& vec, const ArgInfo& info)
|
||||
{
|
||||
return pyopencv_to(o, (cv::Matx<_Tp, cn, 1>&)vec, info);
|
||||
}
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec4d& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec4d& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec4f& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec4f& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec4i& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec4i& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec3d& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec3d& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec3f& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec3f& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec3i& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec3i& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec2d& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec2d& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec2f& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec2f& v);
|
||||
bool pyopencv_to(PyObject* obj, cv::Vec2i& v, ArgInfo& info);
|
||||
PyObject* pyopencv_from(const cv::Vec2i& v);
|
||||
|
||||
// --- TermCriteria
|
||||
template<> bool pyopencv_to(PyObject* obj, cv::TermCriteria& dst, const ArgInfo& info);
|
||||
template<> PyObject* pyopencv_from(const cv::TermCriteria& src);
|
||||
|
||||
// --- Moments
|
||||
template<> PyObject* pyopencv_from(const cv::Moments& m);
|
||||
|
||||
// --- pair
|
||||
template<> PyObject* pyopencv_from(const std::pair<int, double>& src);
|
||||
|
||||
// --- vector
|
||||
template <typename Tp>
|
||||
struct pyopencvVecConverter;
|
||||
|
||||
template <typename Tp>
|
||||
bool pyopencv_to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
|
||||
{
|
||||
if (!obj || obj == Py_None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return pyopencvVecConverter<Tp>::to(obj, value, info);
|
||||
}
|
||||
|
||||
template <typename Tp>
|
||||
PyObject* pyopencv_from(const std::vector<Tp>& value)
|
||||
{
|
||||
return pyopencvVecConverter<Tp>::from(value);
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
bool pyopencv_to(PyObject *obj, std::map<K,V> &map, const ArgInfo& info)
|
||||
{
|
||||
if (!obj || obj == Py_None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
PyObject* py_key = nullptr;
|
||||
PyObject* py_value = nullptr;
|
||||
Py_ssize_t pos = 0;
|
||||
|
||||
if (!PyDict_Check(obj)) {
|
||||
failmsg("Can't parse '%s'. Input argument isn't dict or"
|
||||
" an instance of subtype of the dict type", info.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
while(PyDict_Next(obj, &pos, &py_key, &py_value))
|
||||
{
|
||||
K cpp_key;
|
||||
if (!pyopencv_to(py_key, cpp_key, ArgInfo("key", 0))) {
|
||||
failmsg("Can't parse dict key. Key on position %lu has a wrong type", pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
V cpp_value;
|
||||
if (!pyopencv_to(py_value, cpp_value, ArgInfo("value", 0))) {
|
||||
failmsg("Can't parse dict value. Value on position %lu has a wrong type", pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
map.emplace(cpp_key, cpp_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Tp>
|
||||
static bool pyopencv_to_generic_vec(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
|
||||
{
|
||||
if (!obj || obj == Py_None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (info.nd_mat && PyArray_Check(obj))
|
||||
{
|
||||
/*
|
||||
If obj is marked as nd mat and of array type, it is parsed to a single
|
||||
mat in the target vector to avoid being split into multiple mats
|
||||
*/
|
||||
value.resize(1);
|
||||
if (!pyopencv_to(obj, value.front(), info))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Array item has a wrong type", info.name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else // parse as sequence
|
||||
{
|
||||
if (!PySequence_Check(obj))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name);
|
||||
return false;
|
||||
}
|
||||
const size_t n = static_cast<size_t>(PySequence_Size(obj));
|
||||
value.resize(n);
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
SafeSeqItem item_wrap(obj, i);
|
||||
if (!pyopencv_to(item_wrap.item, value[i], info))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> inline bool pyopencv_to_generic_vec(PyObject* obj, std::vector<bool>& value, const ArgInfo& info)
|
||||
{
|
||||
if (!obj || obj == Py_None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!PySequence_Check(obj))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name);
|
||||
return false;
|
||||
}
|
||||
const size_t n = static_cast<size_t>(PySequence_Size(obj));
|
||||
value.resize(n);
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
SafeSeqItem item_wrap(obj, i);
|
||||
bool elem{};
|
||||
if (!pyopencv_to(item_wrap.item, elem, info))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i);
|
||||
return false;
|
||||
}
|
||||
value[i] = elem;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Tp>
|
||||
static PyObject* pyopencv_from_generic_vec(const std::vector<Tp>& value)
|
||||
{
|
||||
Py_ssize_t n = static_cast<Py_ssize_t>(value.size());
|
||||
PySafeObject seq(PyTuple_New(n));
|
||||
for (Py_ssize_t i = 0; i < n; i++)
|
||||
{
|
||||
PyObject* item = pyopencv_from(value[i]);
|
||||
// If item can't be assigned - PyTuple_SetItem raises exception and returns -1.
|
||||
if (!item || PyTuple_SetItem(seq, i, item) == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return seq.release();
|
||||
}
|
||||
|
||||
template<> inline PyObject* pyopencv_from_generic_vec(const std::vector<bool>& value)
|
||||
{
|
||||
Py_ssize_t n = static_cast<Py_ssize_t>(value.size());
|
||||
PySafeObject seq(PyTuple_New(n));
|
||||
for (Py_ssize_t i = 0; i < n; i++)
|
||||
{
|
||||
bool elem = value[i];
|
||||
PyObject* item = pyopencv_from(elem);
|
||||
// If item can't be assigned - PyTuple_SetItem raises exception and returns -1.
|
||||
if (!item || PyTuple_SetItem(seq, i, item) == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return seq.release();
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template <bool Value>
|
||||
struct BooleanConstant
|
||||
{
|
||||
static const bool value = Value;
|
||||
typedef BooleanConstant<Value> type;
|
||||
};
|
||||
|
||||
typedef BooleanConstant<true> TrueType;
|
||||
typedef BooleanConstant<false> FalseType;
|
||||
|
||||
template <class T>
|
||||
struct VoidType {
|
||||
typedef void type;
|
||||
};
|
||||
|
||||
template <class T, class DType = void>
|
||||
struct IsRepresentableAsMatDataType : FalseType
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct IsRepresentableAsMatDataType<T, typename VoidType<typename cv::DataType<T>::channel_type>::type> : TrueType
|
||||
{
|
||||
};
|
||||
|
||||
// https://github.com/opencv/opencv/issues/20930
|
||||
template <> struct IsRepresentableAsMatDataType<cv::RotatedRect, void> : FalseType {};
|
||||
|
||||
} // namespace traits
|
||||
|
||||
template <typename Tp>
|
||||
struct pyopencvVecConverter
|
||||
{
|
||||
typedef typename std::vector<Tp>::iterator VecIt;
|
||||
|
||||
static bool to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
|
||||
{
|
||||
if (!PyArray_Check(obj))
|
||||
{
|
||||
return pyopencv_to_generic_vec(obj, value, info);
|
||||
}
|
||||
// If user passed an array it is possible to make faster conversions in several cases
|
||||
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(obj);
|
||||
const NPY_TYPES target_type = asNumpyType<Tp>();
|
||||
const NPY_TYPES source_type = static_cast<NPY_TYPES>(PyArray_TYPE(array_obj));
|
||||
if (target_type == NPY_OBJECT)
|
||||
{
|
||||
// Non-planar arrays representing objects (e.g. array of N Rect is an array of shape Nx4) have NPY_OBJECT
|
||||
// as their target type.
|
||||
return pyopencv_to_generic_vec(obj, value, info);
|
||||
}
|
||||
if (PyArray_NDIM(array_obj) > 1)
|
||||
{
|
||||
failmsg("Can't parse %dD array as '%s' vector argument", PyArray_NDIM(array_obj), info.name);
|
||||
return false;
|
||||
}
|
||||
if (target_type != source_type)
|
||||
{
|
||||
// Source type requires conversion
|
||||
// Allowed conversions for target type is handled in the corresponding pyopencv_to function
|
||||
return pyopencv_to_generic_vec(obj, value, info);
|
||||
}
|
||||
// For all other cases, all array data can be directly copied to std::vector data
|
||||
// Simple `memcpy` is not possible because NumPy array can reference a slice of the bigger array:
|
||||
// ```
|
||||
// arr = np.ones((8, 4, 5), dtype=np.int32)
|
||||
// convertible_to_vector_of_int = arr[:, 0, 1]
|
||||
// ```
|
||||
value.resize(static_cast<size_t>(PyArray_SIZE(array_obj)));
|
||||
const npy_intp item_step = PyArray_STRIDE(array_obj, 0) / PyArray_ITEMSIZE(array_obj);
|
||||
const Tp* data_ptr = static_cast<Tp*>(PyArray_DATA(array_obj));
|
||||
for (VecIt it = value.begin(); it != value.end(); ++it, data_ptr += item_step) {
|
||||
*it = *data_ptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject* from(const std::vector<Tp>& value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
return from(value, ::traits::IsRepresentableAsMatDataType<Tp>());
|
||||
}
|
||||
|
||||
private:
|
||||
static PyObject* from(const std::vector<Tp>& value, ::traits::FalseType)
|
||||
{
|
||||
// Underlying type is not representable as Mat Data Type
|
||||
return pyopencv_from_generic_vec(value);
|
||||
}
|
||||
|
||||
static PyObject* from(const std::vector<Tp>& value, ::traits::TrueType)
|
||||
{
|
||||
// Underlying type is representable as Mat Data Type, so faster return type is available
|
||||
typedef cv::DataType<Tp> DType;
|
||||
typedef typename DType::channel_type UnderlyingArrayType;
|
||||
|
||||
// If Mat is always exposed as NumPy array this code path can be reduced to the following snipped:
|
||||
// Mat src(value);
|
||||
// PyObject* array = pyopencv_from(src);
|
||||
// return PyArray_Squeeze(reinterpret_cast<PyArrayObject*>(array));
|
||||
// This puts unnecessary restrictions on Mat object those might be avoided without losing the performance.
|
||||
// Moreover, this version is a bit faster, because it doesn't create temporary objects with reference counting.
|
||||
|
||||
const NPY_TYPES target_type = asNumpyType<UnderlyingArrayType>();
|
||||
const int cols = DType::channels;
|
||||
PyObject* array = NULL;
|
||||
if (cols == 1)
|
||||
{
|
||||
npy_intp dims = static_cast<npy_intp>(value.size());
|
||||
array = PyArray_SimpleNew(1, &dims, target_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
npy_intp dims[2] = {static_cast<npy_intp>(value.size()), cols};
|
||||
array = PyArray_SimpleNew(2, dims, target_type);
|
||||
}
|
||||
if(!array)
|
||||
{
|
||||
// NumPy arrays with shape (N, 1) and (N) are not equal, so correct error message should distinguish
|
||||
// them too.
|
||||
cv::String shape;
|
||||
if (cols > 1)
|
||||
{
|
||||
shape = cv::format("(%d x %d)", static_cast<int>(value.size()), cols);
|
||||
}
|
||||
else
|
||||
{
|
||||
shape = cv::format("(%d)", static_cast<int>(value.size()));
|
||||
}
|
||||
const cv::String error_message = cv::format("Can't allocate NumPy array for vector with dtype=%d and shape=%s",
|
||||
static_cast<int>(target_type), shape.c_str());
|
||||
emit_failmsg(PyExc_MemoryError, error_message.c_str());
|
||||
return array;
|
||||
}
|
||||
// Fill the array
|
||||
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(array);
|
||||
UnderlyingArrayType* array_data = static_cast<UnderlyingArrayType*>(PyArray_DATA(array_obj));
|
||||
// if Tp is representable as Mat DataType, so the following cast is pretty safe...
|
||||
const UnderlyingArrayType* value_data = reinterpret_cast<const UnderlyingArrayType*>(value.data());
|
||||
memcpy(array_data, value_data, sizeof(UnderlyingArrayType) * value.size() * static_cast<size_t>(cols));
|
||||
return array;
|
||||
}
|
||||
};
|
||||
|
||||
// --- tuple
|
||||
template<std::size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
convert_to_python_tuple(const std::tuple<Tp...>&, PyObject*) { }
|
||||
|
||||
template<std::size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I < sizeof...(Tp), void>::type
|
||||
convert_to_python_tuple(const std::tuple<Tp...>& cpp_tuple, PyObject* py_tuple)
|
||||
{
|
||||
PyObject* item = pyopencv_from(std::get<I>(cpp_tuple));
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
PyTuple_SetItem(py_tuple, I, item);
|
||||
convert_to_python_tuple<I + 1, Tp...>(cpp_tuple, py_tuple);
|
||||
}
|
||||
|
||||
template<typename... Ts>
|
||||
PyObject* pyopencv_from(const std::tuple<Ts...>& cpp_tuple)
|
||||
{
|
||||
size_t size = sizeof...(Ts);
|
||||
PyObject* py_tuple = PyTuple_New(size);
|
||||
convert_to_python_tuple(cpp_tuple, py_tuple);
|
||||
size_t actual_size = PyTuple_Size(py_tuple);
|
||||
|
||||
if (actual_size < size)
|
||||
{
|
||||
Py_DECREF(py_tuple);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return py_tuple;
|
||||
}
|
||||
|
||||
#endif // CV2_CONVERT_HPP
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "cv2_highgui.hpp"
|
||||
|
||||
#ifdef HAVE_OPENCV_HIGHGUI
|
||||
|
||||
#include "cv2_util.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <map>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
static void OnMouse(int event, int x, int y, int flags, void* param)
|
||||
{
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
|
||||
PyObject *o = (PyObject*)param;
|
||||
PyObject *args = Py_BuildValue("iiiiO", event, x, y, flags, PyTuple_GetItem(o, 1));
|
||||
|
||||
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
|
||||
if (r == NULL)
|
||||
PyErr_Print();
|
||||
else
|
||||
Py_DECREF(r);
|
||||
Py_DECREF(args);
|
||||
PyGILState_Release(gstate);
|
||||
}
|
||||
|
||||
PyObject *pycvSetMouseCallback(PyObject*, PyObject *args, PyObject *kw)
|
||||
{
|
||||
const char *keywords[] = { "window_name", "on_mouse", "param", NULL };
|
||||
char* name;
|
||||
PyObject *on_mouse;
|
||||
PyObject *param = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "sO|O", (char**)keywords, &name, &on_mouse, ¶m))
|
||||
return NULL;
|
||||
if (!PyCallable_Check(on_mouse)) {
|
||||
PyErr_SetString(PyExc_TypeError, "on_mouse must be callable");
|
||||
return NULL;
|
||||
}
|
||||
if (param == NULL) {
|
||||
param = Py_None;
|
||||
}
|
||||
PyObject* py_callback_info = Py_BuildValue("OO", on_mouse, param);
|
||||
static std::map<std::string, PyObject*> registered_callbacks;
|
||||
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
|
||||
if (i != registered_callbacks.end())
|
||||
{
|
||||
Py_DECREF(i->second);
|
||||
i->second = py_callback_info;
|
||||
}
|
||||
else
|
||||
{
|
||||
registered_callbacks.insert(std::pair<std::string, PyObject*>(std::string(name), py_callback_info));
|
||||
}
|
||||
ERRWRAP2(setMouseCallback(name, OnMouse, py_callback_info));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
static void OnChange(int pos, void *param)
|
||||
{
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
|
||||
PyObject *o = (PyObject*)param;
|
||||
PyObject *args = Py_BuildValue("(i)", pos);
|
||||
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
|
||||
if (r == NULL)
|
||||
PyErr_Print();
|
||||
else
|
||||
Py_DECREF(r);
|
||||
Py_DECREF(args);
|
||||
PyGILState_Release(gstate);
|
||||
}
|
||||
|
||||
// workaround for #20408, use nullptr, set value later
|
||||
static int _createTrackbar(const String &trackbar_name, const String &window_name, int value, int count,
|
||||
TrackbarCallback onChange, PyObject* py_callback_info)
|
||||
{
|
||||
int n = createTrackbar(trackbar_name, window_name, NULL, count, onChange, py_callback_info);
|
||||
setTrackbarPos(trackbar_name, window_name, value);
|
||||
return n;
|
||||
}
|
||||
|
||||
PyObject *pycvCreateTrackbar(PyObject*, PyObject *args)
|
||||
{
|
||||
PyObject *on_change;
|
||||
char* trackbar_name;
|
||||
char* window_name;
|
||||
int value;
|
||||
int count;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ssiiO", &trackbar_name, &window_name, &value, &count, &on_change))
|
||||
return NULL;
|
||||
if (!PyCallable_Check(on_change)) {
|
||||
PyErr_SetString(PyExc_TypeError, "on_change must be callable");
|
||||
return NULL;
|
||||
}
|
||||
PyObject* py_callback_info = Py_BuildValue("OO", on_change, Py_None);
|
||||
std::string name = std::string(window_name) + ":" + std::string(trackbar_name);
|
||||
static std::map<std::string, PyObject*> registered_callbacks;
|
||||
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
|
||||
if (i != registered_callbacks.end())
|
||||
{
|
||||
Py_DECREF(i->second);
|
||||
i->second = py_callback_info;
|
||||
}
|
||||
else
|
||||
{
|
||||
registered_callbacks.insert(std::pair<std::string, PyObject*>(name, py_callback_info));
|
||||
}
|
||||
ERRWRAP2(_createTrackbar(trackbar_name, window_name, value, count, OnChange, py_callback_info));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
static void OnButtonChange(int state, void *param)
|
||||
{
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
|
||||
PyObject *o = (PyObject*)param;
|
||||
PyObject *args;
|
||||
if(PyTuple_GetItem(o, 1) != NULL)
|
||||
{
|
||||
args = Py_BuildValue("(iO)", state, PyTuple_GetItem(o,1));
|
||||
}
|
||||
else
|
||||
{
|
||||
args = Py_BuildValue("(i)", state);
|
||||
}
|
||||
|
||||
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
|
||||
if (r == NULL)
|
||||
PyErr_Print();
|
||||
else
|
||||
Py_DECREF(r);
|
||||
Py_DECREF(args);
|
||||
PyGILState_Release(gstate);
|
||||
}
|
||||
|
||||
PyObject *pycvCreateButton(PyObject*, PyObject *args, PyObject *kw)
|
||||
{
|
||||
const char* keywords[] = {"buttonName", "onChange", "userData", "buttonType", "initialButtonState", NULL};
|
||||
PyObject *on_change;
|
||||
PyObject *userdata = NULL;
|
||||
char* button_name;
|
||||
int button_type = 0;
|
||||
int initial_button_state = 0;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "sO|Oii", (char**)keywords, &button_name, &on_change, &userdata, &button_type, &initial_button_state))
|
||||
return NULL;
|
||||
if (!PyCallable_Check(on_change)) {
|
||||
PyErr_SetString(PyExc_TypeError, "onChange must be callable");
|
||||
return NULL;
|
||||
}
|
||||
if (userdata == NULL) {
|
||||
userdata = Py_None;
|
||||
}
|
||||
|
||||
PyObject* py_callback_info = Py_BuildValue("OO", on_change, userdata);
|
||||
std::string name(button_name);
|
||||
|
||||
static std::map<std::string, PyObject*> registered_callbacks;
|
||||
std::map<std::string, PyObject*>::iterator i = registered_callbacks.find(name);
|
||||
if (i != registered_callbacks.end())
|
||||
{
|
||||
Py_DECREF(i->second);
|
||||
i->second = py_callback_info;
|
||||
}
|
||||
else
|
||||
{
|
||||
registered_callbacks.insert(std::pair<std::string, PyObject*>(name, py_callback_info));
|
||||
}
|
||||
ERRWRAP2(createButton(button_name, OnButtonChange, py_callback_info, button_type, initial_button_state != 0));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENCV_HIGHGUI
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef CV2_HIGHGUI_HPP
|
||||
#define CV2_HIGHGUI_HPP
|
||||
|
||||
#include "cv2.hpp"
|
||||
#include "opencv2/opencv_modules.hpp"
|
||||
|
||||
#ifdef HAVE_OPENCV_HIGHGUI
|
||||
PyObject *pycvSetMouseCallback(PyObject*, PyObject *args, PyObject *kw);
|
||||
// workaround for #20408, use nullptr, set value later
|
||||
PyObject *pycvCreateTrackbar(PyObject*, PyObject *args);
|
||||
PyObject *pycvCreateButton(PyObject*, PyObject *args, PyObject *kw);
|
||||
#endif
|
||||
|
||||
#endif // CV2_HIGHGUI_HPP
|
||||
@@ -0,0 +1,68 @@
|
||||
// must be defined before importing numpy headers
|
||||
// https://numpy.org/doc/1.17/reference/c-api.array.html#importing-the-api
|
||||
#define NO_IMPORT_ARRAY
|
||||
#define PY_ARRAY_UNIQUE_SYMBOL opencv_ARRAY_API
|
||||
|
||||
#include "cv2_numpy.hpp"
|
||||
#include "cv2_util.hpp"
|
||||
|
||||
using namespace cv;
|
||||
|
||||
UMatData* NumpyAllocator::allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const
|
||||
{
|
||||
UMatData* u = new UMatData(this);
|
||||
u->data = u->origdata = (uchar*)PyArray_DATA((PyArrayObject*) o);
|
||||
npy_intp* _strides = PyArray_STRIDES((PyArrayObject*) o);
|
||||
for( int i = 0; i < dims - 1; i++ )
|
||||
step[i] = (size_t)_strides[i];
|
||||
if( dims > 0 )
|
||||
step[dims-1] = CV_ELEM_SIZE(type);
|
||||
u->size = dims > 0 ? sizes[0]*step[0] : CV_ELEM_SIZE(type);
|
||||
u->userdata = o;
|
||||
return u;
|
||||
}
|
||||
|
||||
UMatData* NumpyAllocator::allocate(int dims0, const int* sizes, int type, void* data, size_t* step, AccessFlag flags, UMatUsageFlags usageFlags) const
|
||||
{
|
||||
if( data != 0 )
|
||||
{
|
||||
// issue #6969: CV_Error(Error::StsAssert, "The data should normally be NULL!");
|
||||
// probably this is safe to do in such extreme case
|
||||
return stdAllocator->allocate(dims0, sizes, type, data, step, flags, usageFlags);
|
||||
}
|
||||
PyEnsureGIL gil;
|
||||
|
||||
int depth = CV_MAT_DEPTH(type);
|
||||
int cn = CV_MAT_CN(type);
|
||||
int typenum = cvDepthToNumpyType(depth);
|
||||
int i, dims = dims0;
|
||||
cv::AutoBuffer<npy_intp> _sizes(dims + 1);
|
||||
for( i = 0; i < dims; i++ )
|
||||
_sizes[i] = sizes[i];
|
||||
if( cn > 1 )
|
||||
_sizes[dims++] = cn;
|
||||
PyObject* o = PyArray_SimpleNew(dims, _sizes.data(), typenum);
|
||||
if(!o)
|
||||
CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));
|
||||
return allocate(o, dims0, sizes, type, step);
|
||||
}
|
||||
|
||||
bool NumpyAllocator::allocate(UMatData* u, AccessFlag accessFlags, UMatUsageFlags usageFlags) const
|
||||
{
|
||||
return stdAllocator->allocate(u, accessFlags, usageFlags);
|
||||
}
|
||||
|
||||
void NumpyAllocator::deallocate(UMatData* u) const
|
||||
{
|
||||
if(!u)
|
||||
return;
|
||||
PyEnsureGIL gil;
|
||||
CV_Assert(u->urefcount >= 0);
|
||||
CV_Assert(u->refcount >= 0);
|
||||
if(u->refcount == 0)
|
||||
{
|
||||
PyObject* o = (PyObject*)u->userdata;
|
||||
Py_XDECREF(o);
|
||||
delete u;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#ifndef CV2_NUMPY_HPP
|
||||
#define CV2_NUMPY_HPP
|
||||
|
||||
#include "cv2.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
|
||||
class NumpyAllocator : public cv::MatAllocator
|
||||
{
|
||||
public:
|
||||
NumpyAllocator() { stdAllocator = cv::Mat::getStdAllocator(); }
|
||||
~NumpyAllocator() {}
|
||||
|
||||
cv::UMatData* allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const;
|
||||
cv::UMatData* allocate(int dims0, const int* sizes, int type, void* data, size_t* step, cv::AccessFlag flags, cv::UMatUsageFlags usageFlags) const CV_OVERRIDE;
|
||||
bool allocate(cv::UMatData* u, cv::AccessFlag accessFlags, cv::UMatUsageFlags usageFlags) const CV_OVERRIDE;
|
||||
void deallocate(cv::UMatData* u) const CV_OVERRIDE;
|
||||
|
||||
const cv::MatAllocator* stdAllocator;
|
||||
};
|
||||
|
||||
inline NumpyAllocator& GetNumpyAllocator() {static NumpyAllocator gNumpyAllocator;return gNumpyAllocator;}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
// HACK(?): function from cv2_util.hpp
|
||||
extern int failmsg(const char *fmt, ...);
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T>
|
||||
NPY_TYPES asNumpyType()
|
||||
{
|
||||
return NPY_OBJECT;
|
||||
}
|
||||
|
||||
template<>
|
||||
NPY_TYPES asNumpyType<bool>()
|
||||
{
|
||||
return NPY_BOOL;
|
||||
}
|
||||
|
||||
#define CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(src, dst) \
|
||||
template<> \
|
||||
NPY_TYPES asNumpyType<src>() \
|
||||
{ \
|
||||
return NPY_##dst; \
|
||||
} \
|
||||
template<> \
|
||||
NPY_TYPES asNumpyType<u##src>() \
|
||||
{ \
|
||||
return NPY_U##dst; \
|
||||
}
|
||||
|
||||
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int8_t, INT8)
|
||||
|
||||
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int16_t, INT16)
|
||||
|
||||
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int32_t, INT32)
|
||||
|
||||
CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int64_t, INT64)
|
||||
|
||||
#undef CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION
|
||||
|
||||
template<>
|
||||
NPY_TYPES asNumpyType<float>()
|
||||
{
|
||||
return NPY_FLOAT;
|
||||
}
|
||||
|
||||
template<>
|
||||
NPY_TYPES asNumpyType<double>()
|
||||
{
|
||||
return NPY_DOUBLE;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PyArray_Descr* getNumpyTypeDescriptor()
|
||||
{
|
||||
return PyArray_DescrFromType(asNumpyType<T>());
|
||||
}
|
||||
|
||||
template <>
|
||||
PyArray_Descr* getNumpyTypeDescriptor<size_t>()
|
||||
{
|
||||
#if SIZE_MAX == ULONG_MAX
|
||||
return PyArray_DescrFromType(NPY_ULONG);
|
||||
#elif SIZE_MAX == ULLONG_MAX
|
||||
return PyArray_DescrFromType(NPY_ULONGLONG);
|
||||
#else
|
||||
return PyArray_DescrFromType(NPY_UINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
bool isRepresentable(U value) {
|
||||
return (std::numeric_limits<T>::min() <= value) && (value <= std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool canBeSafelyCasted(PyObject* obj, PyArray_Descr* to)
|
||||
{
|
||||
return PyArray_CanCastTo(PyArray_DescrFromScalar(obj), to) != 0;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
bool canBeSafelyCasted<size_t>(PyObject* obj, PyArray_Descr* to)
|
||||
{
|
||||
PyArray_Descr* from = PyArray_DescrFromScalar(obj);
|
||||
if (PyArray_CanCastTo(from, to))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// False negative scenarios:
|
||||
// - Signed input is positive so it can be safely cast to unsigned output
|
||||
// - Input has wider limits but value is representable within output limits
|
||||
// - All the above
|
||||
if (PyDataType_ISSIGNED(from))
|
||||
{
|
||||
int64_t input = 0;
|
||||
PyArray_CastScalarToCtype(obj, &input, getNumpyTypeDescriptor<int64_t>());
|
||||
return (input >= 0) && isRepresentable<size_t>(static_cast<uint64_t>(input));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t input = 0;
|
||||
PyArray_CastScalarToCtype(obj, &input, getNumpyTypeDescriptor<uint64_t>());
|
||||
return isRepresentable<size_t>(input);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
bool parseNumpyScalar(PyObject* obj, T& value)
|
||||
{
|
||||
if (PyArray_CheckScalar(obj))
|
||||
{
|
||||
// According to the numpy documentation:
|
||||
// There are 21 statically-defined PyArray_Descr objects for the built-in data-types
|
||||
// So descriptor pointer is not owning.
|
||||
PyArray_Descr* to = getNumpyTypeDescriptor<T>();
|
||||
if (canBeSafelyCasted<T>(obj, to))
|
||||
{
|
||||
PyArray_CastScalarToCtype(obj, &value, to);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
struct SafeSeqItem
|
||||
{
|
||||
PyObject * item;
|
||||
SafeSeqItem(PyObject *obj, size_t idx) { item = PySequence_GetItem(obj, idx); }
|
||||
~SafeSeqItem() { Py_XDECREF(item); }
|
||||
|
||||
private:
|
||||
SafeSeqItem(const SafeSeqItem&); // = delete
|
||||
SafeSeqItem& operator=(const SafeSeqItem&); // = delete
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class RefWrapper
|
||||
{
|
||||
public:
|
||||
RefWrapper(T& item) : item_(item) {}
|
||||
|
||||
T& get() CV_NOEXCEPT { return item_; }
|
||||
|
||||
private:
|
||||
T& item_;
|
||||
};
|
||||
|
||||
// In order to support this conversion on 3.x branch - use custom reference_wrapper
|
||||
// and C-style array instead of std::array<T, N>
|
||||
template <class T, std::size_t N>
|
||||
bool parseSequence(PyObject* obj, RefWrapper<T> (&value)[N], const ArgInfo& info)
|
||||
{
|
||||
if (!obj || obj == Py_None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!PySequence_Check(obj))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Input argument doesn't provide sequence "
|
||||
"protocol", info.name);
|
||||
return false;
|
||||
}
|
||||
const std::size_t sequenceSize = PySequence_Size(obj);
|
||||
if (sequenceSize != N)
|
||||
{
|
||||
failmsg("Can't parse '%s'. Expected sequence length %lu, got %lu",
|
||||
info.name, N, sequenceSize);
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
SafeSeqItem seqItem(obj, i);
|
||||
if (!pyopencv_to(seqItem.item, value[i].get(), info))
|
||||
{
|
||||
failmsg("Can't parse '%s'. Sequence item with index %lu has a "
|
||||
"wrong type", info.name, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
#endif // CV2_NUMPY_HPP
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "cv2_util.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/utils/configuration.private.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
PyObject* opencv_error = NULL;
|
||||
cv::TLSData<std::vector<std::string> > conversionErrorsTLS;
|
||||
|
||||
int cvDepthToNumpyType(int depth)
|
||||
{
|
||||
const int f = (int)(sizeof(size_t)/8);
|
||||
return depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE :
|
||||
depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT :
|
||||
depth == CV_32U ? NPY_UINT32 : depth == CV_32S ? NPY_INT32 : depth == CV_64S ? NPY_INT64 :
|
||||
depth == CV_32F ? NPY_FLOAT : depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF :
|
||||
depth == CV_Bool ? NPY_BOOL : f*NPY_ULONGLONG + (f^1)*NPY_UINT;
|
||||
}
|
||||
|
||||
int numpyTypeToCvDepth(int typenum)
|
||||
{
|
||||
return typenum == NPY_UBYTE ? CV_8U : typenum == NPY_BYTE ? CV_8S :
|
||||
typenum == NPY_USHORT ? CV_16U : typenum == NPY_SHORT ? CV_16S :
|
||||
typenum == NPY_INT ? CV_32S : typenum == NPY_UINT32 ? CV_32U : typenum == NPY_INT32 ? CV_32S :
|
||||
typenum == NPY_HALF ? CV_16F : typenum == NPY_FLOAT ? CV_32F : typenum == NPY_DOUBLE ? CV_64F :
|
||||
typenum == NPY_BOOL ? CV_Bool : -1;
|
||||
}
|
||||
|
||||
using namespace cv;
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
bool isPythonBindingsDebugEnabled()
|
||||
{
|
||||
static bool param_debug = cv::utils::getConfigurationParameterBool("OPENCV_PYTHON_DEBUG", false);
|
||||
return param_debug;
|
||||
}
|
||||
|
||||
void emit_failmsg(PyObject * exc, const char *msg)
|
||||
{
|
||||
static bool param_debug = isPythonBindingsDebugEnabled();
|
||||
if (param_debug)
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "Bindings conversion failed: " << msg);
|
||||
}
|
||||
PyErr_SetString(exc, msg);
|
||||
}
|
||||
|
||||
int failmsg(const char *fmt, ...)
|
||||
{
|
||||
char str[1000];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(str, sizeof(str), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
emit_failmsg(PyExc_TypeError, str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* failmsgp(const char *fmt, ...)
|
||||
{
|
||||
char str[1000];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(str, sizeof(str), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
emit_failmsg(PyExc_TypeError, str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pyRaiseCVException(const cv::Exception &e)
|
||||
{
|
||||
PyObject* temp_obj = PyString_FromString(e.file.c_str());
|
||||
PyObject_SetAttrString(opencv_error, "file", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
temp_obj = PyString_FromString(e.func.c_str());
|
||||
PyObject_SetAttrString(opencv_error, "func", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
temp_obj = PyInt_FromLong(e.line);
|
||||
PyObject_SetAttrString(opencv_error, "line", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
temp_obj = PyInt_FromLong(e.code);
|
||||
PyObject_SetAttrString(opencv_error, "code", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
temp_obj = PyString_FromString(e.msg.c_str());
|
||||
PyObject_SetAttrString(opencv_error, "msg", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
temp_obj = PyString_FromString(e.err.c_str());
|
||||
PyObject_SetAttrString(opencv_error, "err", temp_obj);
|
||||
Py_DECREF(temp_obj);
|
||||
PyErr_SetString(opencv_error, e.what());
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
void pyRaiseCVOverloadException(const std::string& functionName)
|
||||
{
|
||||
const std::vector<std::string>& conversionErrors = conversionErrorsTLS.getRef();
|
||||
const std::size_t conversionErrorsCount = conversionErrors.size();
|
||||
if (conversionErrorsCount > 0)
|
||||
{
|
||||
// In modern std libraries small string optimization is used = no dynamic memory allocations,
|
||||
// but it can be applied only for string with length < 18 symbols (in GCC)
|
||||
const std::string bullet = "\n - ";
|
||||
|
||||
// Estimate required buffer size - save dynamic memory allocations = faster
|
||||
std::size_t requiredBufferSize = bullet.size() * conversionErrorsCount;
|
||||
for (std::size_t i = 0; i < conversionErrorsCount; ++i)
|
||||
{
|
||||
requiredBufferSize += conversionErrors[i].size();
|
||||
}
|
||||
|
||||
// Only string concatenation is required so std::string is way faster than
|
||||
// std::ostringstream
|
||||
std::string errorMessage("Overload resolution failed:");
|
||||
errorMessage.reserve(errorMessage.size() + requiredBufferSize);
|
||||
for (std::size_t i = 0; i < conversionErrorsCount; ++i)
|
||||
{
|
||||
errorMessage += bullet;
|
||||
errorMessage += conversionErrors[i];
|
||||
}
|
||||
cv::Exception exception(Error::StsBadArg, errorMessage, functionName, "", -1);
|
||||
pyRaiseCVException(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
cv::Exception exception(Error::StsInternal, "Overload resolution failed, but no errors reported",
|
||||
functionName, "", -1);
|
||||
pyRaiseCVException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
void pyPopulateArgumentConversionErrors()
|
||||
{
|
||||
if (PyErr_Occurred())
|
||||
{
|
||||
PySafeObject exception_type;
|
||||
PySafeObject exception_value;
|
||||
PySafeObject exception_traceback;
|
||||
PyErr_Fetch(exception_type, exception_value, exception_traceback);
|
||||
PyErr_NormalizeException(exception_type, exception_value,
|
||||
exception_traceback);
|
||||
|
||||
PySafeObject exception_message(PyObject_Str(exception_value));
|
||||
std::string message;
|
||||
getUnicodeString(exception_message, message);
|
||||
conversionErrorsTLS.getRef().push_back(std::move(message));
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
static int OnError(int status, const char *func_name, const char *err_msg, const char *file_name, int line, void *userdata)
|
||||
{
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
|
||||
PyObject *on_error = (PyObject*)userdata;
|
||||
PyObject *args = Py_BuildValue("isssi", status, func_name, err_msg, file_name, line);
|
||||
|
||||
PyObject *r = PyObject_Call(on_error, args, NULL);
|
||||
if (r == NULL) {
|
||||
PyErr_Print();
|
||||
} else {
|
||||
Py_DECREF(r);
|
||||
}
|
||||
|
||||
Py_DECREF(args);
|
||||
PyGILState_Release(gstate);
|
||||
|
||||
return 0; // The return value isn't used
|
||||
}
|
||||
|
||||
PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw)
|
||||
{
|
||||
const char *keywords[] = { "on_error", NULL };
|
||||
PyObject *on_error;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "O", (char**)keywords, &on_error))
|
||||
return NULL;
|
||||
|
||||
if ((on_error != Py_None) && !PyCallable_Check(on_error)) {
|
||||
PyErr_SetString(PyExc_TypeError, "on_error must be callable");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Keep track of the previous handler parameter, so we can decref it when no longer used
|
||||
static PyObject* last_on_error = NULL;
|
||||
if (last_on_error) {
|
||||
Py_DECREF(last_on_error);
|
||||
last_on_error = NULL;
|
||||
}
|
||||
|
||||
if (on_error == Py_None) {
|
||||
ERRWRAP2(redirectError(NULL));
|
||||
} else {
|
||||
last_on_error = on_error;
|
||||
Py_INCREF(last_on_error);
|
||||
ERRWRAP2(redirectError(OnError, last_on_error));
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#ifndef CV2_UTIL_HPP
|
||||
#define CV2_UTIL_HPP
|
||||
|
||||
#include "cv2.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/utils/tls.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
bool isPythonBindingsDebugEnabled();
|
||||
void emit_failmsg(PyObject * exc, const char *msg);
|
||||
int failmsg(const char *fmt, ...);
|
||||
PyObject* failmsgp(const char *fmt, ...);
|
||||
|
||||
int cvDepthToNumpyType(int depth);
|
||||
int numpyTypeToCvDepth(int typenum);
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
class PyAllowThreads
|
||||
{
|
||||
public:
|
||||
PyAllowThreads() : _state(PyEval_SaveThread()) {}
|
||||
~PyAllowThreads()
|
||||
{
|
||||
PyEval_RestoreThread(_state);
|
||||
}
|
||||
private:
|
||||
PyThreadState* _state;
|
||||
};
|
||||
|
||||
class PyEnsureGIL
|
||||
{
|
||||
public:
|
||||
PyEnsureGIL() : _state(PyGILState_Ensure()) {}
|
||||
~PyEnsureGIL()
|
||||
{
|
||||
PyGILState_Release(_state);
|
||||
}
|
||||
private:
|
||||
PyGILState_STATE _state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Light weight RAII wrapper for `PyObject*` owning references.
|
||||
* In comparison to C++11 `std::unique_ptr` with custom deleter, it provides
|
||||
* implicit conversion functions that might be useful to initialize it with
|
||||
* Python functions those returns owning references through the `PyObject**`
|
||||
* e.g. `PyErr_Fetch` or directly pass it to functions those want to borrow
|
||||
* reference to object (doesn't extend object lifetime) e.g. `PyObject_Str`.
|
||||
*/
|
||||
class PySafeObject
|
||||
{
|
||||
public:
|
||||
PySafeObject() : obj_(NULL) {}
|
||||
|
||||
explicit PySafeObject(PyObject* obj) : obj_(obj) {}
|
||||
|
||||
~PySafeObject()
|
||||
{
|
||||
Py_CLEAR(obj_);
|
||||
}
|
||||
|
||||
operator PyObject*()
|
||||
{
|
||||
return obj_;
|
||||
}
|
||||
|
||||
operator PyObject**()
|
||||
{
|
||||
return &obj_;
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return obj_ != nullptr;
|
||||
}
|
||||
|
||||
PyObject* release()
|
||||
{
|
||||
PyObject* obj = obj_;
|
||||
obj_ = NULL;
|
||||
return obj;
|
||||
}
|
||||
|
||||
private:
|
||||
PyObject* obj_;
|
||||
|
||||
// Explicitly disable copy operations
|
||||
PySafeObject(const PySafeObject*); // = delete
|
||||
PySafeObject& operator=(const PySafeObject&); // = delete
|
||||
};
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
extern PyObject* opencv_error;
|
||||
|
||||
void pyRaiseCVException(const cv::Exception &e);
|
||||
|
||||
#define ERRWRAP2(expr) \
|
||||
try \
|
||||
{ \
|
||||
PyAllowThreads allowThreads; \
|
||||
expr; \
|
||||
} \
|
||||
catch (const cv::Exception &e) \
|
||||
{ \
|
||||
pyRaiseCVException(e); \
|
||||
return 0; \
|
||||
} \
|
||||
catch (const std::exception &e) \
|
||||
{ \
|
||||
PyErr_SetString(opencv_error, e.what()); \
|
||||
return 0; \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
PyErr_SetString(opencv_error, "Unknown C++ exception from OpenCV code"); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
extern cv::TLSData<std::vector<std::string> > conversionErrorsTLS;
|
||||
|
||||
inline void pyPrepareArgumentConversionErrorsStorage(std::size_t size)
|
||||
{
|
||||
std::vector<std::string>& conversionErrors = conversionErrorsTLS.getRef();
|
||||
conversionErrors.clear();
|
||||
conversionErrors.reserve(size);
|
||||
}
|
||||
|
||||
void pyRaiseCVOverloadException(const std::string& functionName);
|
||||
void pyPopulateArgumentConversionErrors();
|
||||
|
||||
//======================================================================================================================
|
||||
|
||||
PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw);
|
||||
|
||||
#endif // CV2_UTIL_HPP
|
||||
Executable
+1558
File diff suppressed because it is too large
Load Diff
Executable
+1255
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,371 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009-2011, Willow Garage Inc., 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.
|
||||
//
|
||||
//M*/
|
||||
|
||||
// Defines for Python 2/3 compatibility.
|
||||
#ifndef __PYCOMPAT_HPP__
|
||||
#define __PYCOMPAT_HPP__
|
||||
|
||||
#include <string>
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
// Python3 treats all ints as longs, PyInt_X functions have been removed.
|
||||
#define PyInt_Check PyLong_Check
|
||||
#define PyInt_CheckExact PyLong_CheckExact
|
||||
#define PyInt_AsLong PyLong_AsLong
|
||||
#define PyInt_AS_LONG PyLong_AS_LONG
|
||||
#define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask
|
||||
#define PyInt_FromLong PyLong_FromLong
|
||||
#define PyNumber_Int PyNumber_Long
|
||||
|
||||
|
||||
#define PyString_FromString PyUnicode_FromString
|
||||
#define PyString_FromStringAndSize PyUnicode_FromStringAndSize
|
||||
|
||||
#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;
|
||||
if (PyUnicode_Check(obj))
|
||||
{
|
||||
PyObject * bytes = PyUnicode_AsUTF8String(obj);
|
||||
if (PyBytes_Check(bytes))
|
||||
{
|
||||
const char * raw = PyBytes_AsString(bytes);
|
||||
if (raw)
|
||||
{
|
||||
str = std::string(raw);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
Py_XDECREF(bytes);
|
||||
}
|
||||
else if (PyBytes_Check(obj))
|
||||
{
|
||||
const char * raw = PyBytes_AsString(obj);
|
||||
if (raw)
|
||||
{
|
||||
str = std::string(raw);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyString_Check(obj))
|
||||
{
|
||||
const char * raw = PyString_AsString(obj);
|
||||
if (raw)
|
||||
{
|
||||
str = std::string(raw);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline
|
||||
std::string getPyObjectAttr(PyObject* obj, const char* attrName)
|
||||
{
|
||||
std::string obj_name;
|
||||
PyObject* cls_name_obj = PyObject_GetAttrString(obj, attrName);
|
||||
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<PyTypeObject*>(obj)->tp_name;
|
||||
}
|
||||
#endif
|
||||
if (obj_name.empty()) {
|
||||
obj_name = "<UNAVAILABLE>";
|
||||
}
|
||||
return obj_name;
|
||||
}
|
||||
|
||||
static inline
|
||||
std::string getPyObjectNameAttr(PyObject* obj)
|
||||
{
|
||||
return getPyObjectAttr(obj, "__name__");
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
#define CV_PY_FN_WITH_KW_(fn, flags) (PyCFunction)(void*)(PyCFunctionWithKeywords)(fn), (flags) | METH_VARARGS | METH_KEYWORDS
|
||||
#define CV_PY_FN_NOARGS_(fn, flags) (PyCFunction)(fn), (flags) | METH_NOARGS
|
||||
|
||||
#define CV_PY_FN_WITH_KW(fn) CV_PY_FN_WITH_KW_(fn, 0)
|
||||
#define CV_PY_FN_NOARGS(fn) CV_PY_FN_NOARGS_(fn, 0)
|
||||
|
||||
#define CV_PY_TO_CLASS(TYPE) \
|
||||
template<> \
|
||||
bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) \
|
||||
{ \
|
||||
if (!dst || dst == Py_None) \
|
||||
return true; \
|
||||
Ptr<TYPE> ptr; \
|
||||
\
|
||||
if (!pyopencv_to(dst, ptr, info)) return false; \
|
||||
src = *ptr; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define CV_PY_FROM_CLASS(TYPE) \
|
||||
template<> \
|
||||
PyObject* pyopencv_from(const TYPE& src) \
|
||||
{ \
|
||||
Ptr<TYPE> ptr(new TYPE()); \
|
||||
\
|
||||
*ptr = src; \
|
||||
return pyopencv_from(ptr); \
|
||||
}
|
||||
|
||||
#define CV_PY_TO_CLASS_PTR(TYPE) \
|
||||
template<> \
|
||||
bool pyopencv_to(PyObject* dst, TYPE*& src, const ArgInfo& info) \
|
||||
{ \
|
||||
if (!dst || dst == Py_None) \
|
||||
return true; \
|
||||
Ptr<TYPE> ptr; \
|
||||
\
|
||||
if (!pyopencv_to(dst, ptr, info)) return false; \
|
||||
src = ptr; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define CV_PY_FROM_CLASS_PTR(TYPE) \
|
||||
static PyObject* pyopencv_from(TYPE*& src) \
|
||||
{ \
|
||||
return pyopencv_from(Ptr<TYPE>(src)); \
|
||||
}
|
||||
|
||||
#define CV_PY_TO_ENUM(TYPE) \
|
||||
template<> \
|
||||
bool pyopencv_to(PyObject* dst, TYPE& src, const ArgInfo& info) \
|
||||
{ \
|
||||
if (!dst || dst == Py_None) \
|
||||
return true; \
|
||||
int underlying = 0; \
|
||||
\
|
||||
if (!pyopencv_to(dst, underlying, info)) return false; \
|
||||
src = static_cast<TYPE>(underlying); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define CV_PY_FROM_ENUM(TYPE) \
|
||||
template<> \
|
||||
PyObject* pyopencv_from(const TYPE& src) \
|
||||
{ \
|
||||
return pyopencv_from(static_cast<int>(src)); \
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define CVPY_TYPE_HEAD PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||
#define CVPY_TYPE_INCREF(T) Py_INCREF(T)
|
||||
#else
|
||||
#define CVPY_TYPE_HEAD PyObject_HEAD_INIT(&PyType_Type) 0,
|
||||
#define CVPY_TYPE_INCREF(T) _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA (T)->ob_refcnt++
|
||||
#endif
|
||||
|
||||
|
||||
#define CVPY_TYPE_DECLARE(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \
|
||||
struct pyopencv_##CLASS_ID##_t \
|
||||
{ \
|
||||
PyObject_HEAD \
|
||||
STORAGE v; \
|
||||
}; \
|
||||
static PyTypeObject pyopencv_##CLASS_ID##_TypeXXX = \
|
||||
{ \
|
||||
CVPY_TYPE_HEAD \
|
||||
MODULESTR SCOPE"."#EXPORT_NAME, \
|
||||
sizeof(pyopencv_##CLASS_ID##_t), \
|
||||
}; \
|
||||
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_##CLASS_ID##_TypePtr)) \
|
||||
{ \
|
||||
dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \
|
||||
return true; \
|
||||
} \
|
||||
return false; \
|
||||
} \
|
||||
static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \
|
||||
{ \
|
||||
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_##CLASS_ID##_dealloc(PyObject* self) \
|
||||
{ \
|
||||
((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \
|
||||
PyObject_Del(self); \
|
||||
} \
|
||||
static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \
|
||||
{ \
|
||||
char str[1000]; \
|
||||
snprintf(str, sizeof(str), "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \
|
||||
return PyString_FromString(str); \
|
||||
}
|
||||
|
||||
|
||||
#define CVPY_TYPE_INIT_STATIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \
|
||||
{ \
|
||||
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_##CLASS_ID##_TypePtr); \
|
||||
if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \
|
||||
{ \
|
||||
printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \
|
||||
ERROR_HANDLER; \
|
||||
} \
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
#define CVPY_TYPE_DECLARE_DYNAMIC(EXPORT_NAME, CLASS_ID, STORAGE, SNAME, SCOPE) \
|
||||
struct pyopencv_##CLASS_ID##_t \
|
||||
{ \
|
||||
PyObject_HEAD \
|
||||
STORAGE v; \
|
||||
}; \
|
||||
static PyObject * pyopencv_##CLASS_ID##_TypePtr = 0; \
|
||||
static bool pyopencv_##CLASS_ID##_getp(PyObject * self, STORAGE * & dst) \
|
||||
{ \
|
||||
if (PyObject_TypeCheck(self, (PyTypeObject*)pyopencv_##CLASS_ID##_TypePtr)) \
|
||||
{ \
|
||||
dst = &(((pyopencv_##CLASS_ID##_t*)self)->v); \
|
||||
return true; \
|
||||
} \
|
||||
return false; \
|
||||
} \
|
||||
static PyObject * pyopencv_##CLASS_ID##_Instance(const STORAGE &r) \
|
||||
{ \
|
||||
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_##CLASS_ID##_dealloc(PyObject* self) \
|
||||
{ \
|
||||
((pyopencv_##CLASS_ID##_t*)self)->v.STORAGE::~SNAME(); \
|
||||
PyObject_Del(self); \
|
||||
} \
|
||||
static PyObject* pyopencv_##CLASS_ID##_repr(PyObject* self) \
|
||||
{ \
|
||||
char str[1000]; \
|
||||
snprintf(str, sizeof(str), "< " MODULESTR SCOPE"."#EXPORT_NAME" %p>", self); \
|
||||
return PyString_FromString(str); \
|
||||
} \
|
||||
static PyType_Slot pyopencv_##CLASS_ID##_Slots[] = \
|
||||
{ \
|
||||
{Py_tp_dealloc, 0}, \
|
||||
{Py_tp_repr, 0}, \
|
||||
{Py_tp_getset, 0}, \
|
||||
{Py_tp_init, 0}, \
|
||||
{Py_tp_methods, 0}, \
|
||||
{Py_tp_alloc, 0}, \
|
||||
{Py_tp_new, 0}, \
|
||||
{0, 0} \
|
||||
}; \
|
||||
static PyType_Spec pyopencv_##CLASS_ID##_Spec = \
|
||||
{ \
|
||||
MODULESTR SCOPE"."#EXPORT_NAME, \
|
||||
sizeof(pyopencv_##CLASS_ID##_t), \
|
||||
0, \
|
||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, \
|
||||
pyopencv_##CLASS_ID##_Slots \
|
||||
};
|
||||
|
||||
#define CVPY_TYPE_INIT_DYNAMIC(EXPORT_NAME, CLASS_ID, ERROR_HANDLER, BASE, CONSTRUCTOR, SCOPE) \
|
||||
{ \
|
||||
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_##CLASS_ID##_TypePtr = PyType_FromSpecWithBases(&pyopencv_##CLASS_ID##_Spec, bases); \
|
||||
if (!pyopencv_##CLASS_ID##_TypePtr) \
|
||||
{ \
|
||||
printf("Failed to create type from spec: " #CLASS_ID ", base (" #BASE ")\n"); \
|
||||
ERROR_HANDLER; \
|
||||
} \
|
||||
if (!registerNewType(m, #EXPORT_NAME, (PyObject*)pyopencv_##CLASS_ID##_TypePtr, SCOPE)) \
|
||||
{ \
|
||||
printf("Failed to register a new type: " #EXPORT_NAME ", base (" #BASE ") in " SCOPE " \n"); \
|
||||
Py_DECREF(pyopencv_##CLASS_ID##_TypePtr); \
|
||||
ERROR_HANDLER; \
|
||||
} \
|
||||
Py_DECREF(pyopencv_##CLASS_ID##_TypePtr); \
|
||||
}
|
||||
|
||||
// Debug module load:
|
||||
//
|
||||
// else \
|
||||
// { \
|
||||
// printf("Init: " #NAME ", base (" #BASE ") -> %p" "\n", pyopencv_##NAME##_TypePtr); \
|
||||
// } \
|
||||
|
||||
|
||||
#endif // END HEADER GUARD
|
||||
@@ -0,0 +1,35 @@
|
||||
from .nodes import (
|
||||
NamespaceNode,
|
||||
ClassNode,
|
||||
ClassProperty,
|
||||
EnumerationNode,
|
||||
FunctionNode,
|
||||
ConstantNode,
|
||||
TypeNode,
|
||||
OptionalTypeNode,
|
||||
TupleTypeNode,
|
||||
AliasTypeNode,
|
||||
SequenceTypeNode,
|
||||
AnyTypeNode,
|
||||
AggregatedTypeNode,
|
||||
PathLikeTypeNode,
|
||||
)
|
||||
|
||||
from .types_conversion import (
|
||||
replace_template_parameters_with_placeholders,
|
||||
get_template_instantiation_type,
|
||||
create_type_node
|
||||
)
|
||||
|
||||
from .ast_utils import (
|
||||
SymbolName,
|
||||
ScopeNotFoundError,
|
||||
SymbolNotFoundError,
|
||||
find_scope,
|
||||
find_class_node,
|
||||
create_class_node,
|
||||
create_function_node,
|
||||
resolve_enum_scopes
|
||||
)
|
||||
|
||||
from .generation import generate_typing_stubs
|
||||
@@ -0,0 +1,474 @@
|
||||
__all__ = [
|
||||
"apply_manual_api_refinement"
|
||||
]
|
||||
|
||||
from typing import cast, Sequence, Callable, Iterable, Optional
|
||||
|
||||
from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode,
|
||||
ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode,
|
||||
AggregatedTypeNode, CallableTypeNode, AnyTypeNode,
|
||||
TupleTypeNode, UnionTypeNode, ProtocolClassNode,
|
||||
DictTypeNode, ClassTypeNode, AliasRefTypeNode)
|
||||
from .ast_utils import (find_function_node, SymbolName,
|
||||
for_each_function_overload)
|
||||
from .types_conversion import create_type_node
|
||||
|
||||
|
||||
def apply_manual_api_refinement(root: NamespaceNode) -> None:
|
||||
refine_highgui_module(root)
|
||||
refine_cuda_module(root)
|
||||
export_matrix_type_constants(root)
|
||||
refine_dnn_module(root)
|
||||
# Export OpenCV exception class
|
||||
builtin_exception = root.add_class("Exception")
|
||||
builtin_exception.is_exported = False
|
||||
root.add_class("error", (builtin_exception, ), ERROR_CLASS_PROPERTIES)
|
||||
for symbol_name, refine_symbol in NODES_TO_REFINE.items():
|
||||
refine_symbol(root, symbol_name)
|
||||
version_constant = root.add_constant("__version__", "<unused>")
|
||||
version_constant._value_type = "str"
|
||||
|
||||
convert_returned_scalar_to_tuple(root)
|
||||
|
||||
"""
|
||||
def redirectError(
|
||||
onError: Callable[[int, str, str, str, int], None] | None
|
||||
) -> None: ...
|
||||
"""
|
||||
root.add_function("redirectError", [
|
||||
FunctionNode.Arg(
|
||||
"onError",
|
||||
OptionalTypeNode(
|
||||
CallableTypeNode(
|
||||
"ErrorCallback",
|
||||
[
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.str_(),
|
||||
PrimitiveTypeNode.str_(),
|
||||
PrimitiveTypeNode.str_(),
|
||||
PrimitiveTypeNode.int_()
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
|
||||
|
||||
def make_optional_none_return(root_node: NamespaceNode,
|
||||
function_symbol_name: SymbolName) -> None:
|
||||
"""
|
||||
Make return type Optional[MatLike],
|
||||
for the functions that may return None.
|
||||
"""
|
||||
function = find_function_node(root_node, function_symbol_name)
|
||||
for overload in function.overloads:
|
||||
if overload.return_type is not None:
|
||||
if not isinstance(overload.return_type.type_node, OptionalTypeNode):
|
||||
overload.return_type.type_node = OptionalTypeNode(
|
||||
overload.return_type.type_node
|
||||
)
|
||||
|
||||
def export_matrix_type_constants(root: NamespaceNode) -> None:
|
||||
MAX_PREDEFINED_CHANNELS = 4
|
||||
|
||||
depth_names = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32U", "CV_32S",
|
||||
"CV_64U", "CV_64S", "CV_32F", "CV_64F", "CV_16F", "CV_16BF" "CV_Bool")
|
||||
for depth_value, depth_name in enumerate(depth_names):
|
||||
# Export depth constants
|
||||
root.add_constant(depth_name, str(depth_value))
|
||||
# Export predefined types
|
||||
for c in range(MAX_PREDEFINED_CHANNELS):
|
||||
root.add_constant(f"{depth_name}C{c + 1}",
|
||||
f"{depth_value + 8 * c}")
|
||||
# Export type creation function
|
||||
root.add_function(
|
||||
f"{depth_name}C",
|
||||
(FunctionNode.Arg("channels", PrimitiveTypeNode.int_()), ),
|
||||
FunctionNode.RetType(PrimitiveTypeNode.int_())
|
||||
)
|
||||
# Export CV_MAKETYPE
|
||||
root.add_function(
|
||||
"CV_MAKETYPE",
|
||||
(FunctionNode.Arg("depth", PrimitiveTypeNode.int_()),
|
||||
FunctionNode.Arg("channels", PrimitiveTypeNode.int_())),
|
||||
FunctionNode.RetType(PrimitiveTypeNode.int_())
|
||||
)
|
||||
|
||||
|
||||
def make_optional_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]:
|
||||
def _make_optional_arg(root_node: NamespaceNode,
|
||||
function_symbol_name: SymbolName) -> None:
|
||||
function = find_function_node(root_node, function_symbol_name)
|
||||
for arg_name in arg_names:
|
||||
found_overload_with_arg = False
|
||||
|
||||
for overload in function.overloads:
|
||||
arg_idx = _find_argument_index(overload.arguments, arg_name)
|
||||
|
||||
# skip overloads without this argument
|
||||
if arg_idx is None:
|
||||
continue
|
||||
|
||||
# Avoid multiplying optional qualification
|
||||
if isinstance(overload.arguments[arg_idx].type_node, OptionalTypeNode):
|
||||
continue
|
||||
|
||||
overload.arguments[arg_idx].type_node = OptionalTypeNode(
|
||||
cast(TypeNode, overload.arguments[arg_idx].type_node)
|
||||
)
|
||||
|
||||
found_overload_with_arg = True
|
||||
|
||||
if not found_overload_with_arg:
|
||||
raise RuntimeError(
|
||||
f"Failed to find argument with name: '{arg_name}'"
|
||||
f" in '{function_symbol_name.name}' overloads"
|
||||
)
|
||||
|
||||
return _make_optional_arg
|
||||
|
||||
|
||||
def convert_returned_scalar_to_tuple(root: NamespaceNode) -> None:
|
||||
"""Force `tuple[float, float, float, float]` usage instead of Scalar alias
|
||||
for return types due to `pyopencv_from` specialization for Scalar type.
|
||||
"""
|
||||
|
||||
float_4_tuple_node = TupleTypeNode(
|
||||
"ScalarOutput",
|
||||
items=(PrimitiveTypeNode.float_(),) * 4
|
||||
)
|
||||
|
||||
def fix_scalar_return_type(fn: FunctionNode.Overload):
|
||||
if fn.return_type is None:
|
||||
return
|
||||
if fn.return_type.type_node.typename == "Scalar":
|
||||
fn.return_type.type_node = float_4_tuple_node
|
||||
|
||||
for overload in for_each_function_overload(root):
|
||||
fix_scalar_return_type(overload)
|
||||
|
||||
for ns in root.namespaces.values():
|
||||
for overload in for_each_function_overload(ns):
|
||||
fix_scalar_return_type(overload)
|
||||
|
||||
|
||||
def refine_cuda_module(root: NamespaceNode) -> None:
|
||||
def fix_cudaoptflow_enums_names() -> None:
|
||||
for class_name in ("NvidiaOpticalFlow_1_0", "NvidiaOpticalFlow_2_0"):
|
||||
if class_name not in cuda_root.classes:
|
||||
continue
|
||||
opt_flow_class = cuda_root.classes[class_name]
|
||||
_trim_class_name_from_argument_types(
|
||||
for_each_function_overload(opt_flow_class), class_name
|
||||
)
|
||||
|
||||
def fix_namespace_usage_scope(cuda_ns: NamespaceNode) -> None:
|
||||
USED_TYPES = ("GpuMat", "Stream")
|
||||
|
||||
def fix_type_usage(type_node: TypeNode) -> None:
|
||||
if isinstance(type_node, AggregatedTypeNode):
|
||||
for item in type_node.items:
|
||||
fix_type_usage(item)
|
||||
if isinstance(type_node, ASTNodeTypeNode):
|
||||
if type_node._typename in USED_TYPES:
|
||||
type_node._typename = f"cuda_{type_node._typename}"
|
||||
|
||||
for overload in for_each_function_overload(cuda_ns):
|
||||
if overload.return_type is not None:
|
||||
fix_type_usage(overload.return_type.type_node)
|
||||
for type_node in [arg.type_node for arg in overload.arguments
|
||||
if arg.type_node is not None]:
|
||||
fix_type_usage(type_node)
|
||||
|
||||
if "cuda" not in root.namespaces:
|
||||
return
|
||||
cuda_root = root.namespaces["cuda"]
|
||||
fix_cudaoptflow_enums_names()
|
||||
for ns in [ns for ns_name, ns in root.namespaces.items()
|
||||
if ns_name.startswith("cuda")]:
|
||||
fix_namespace_usage_scope(ns)
|
||||
|
||||
|
||||
def refine_highgui_module(root: NamespaceNode) -> None:
|
||||
# Check if library is built with enabled highgui module
|
||||
if "destroyAllWindows" not in root.functions:
|
||||
return
|
||||
"""
|
||||
def createTrackbar(trackbarName: str,
|
||||
windowName: str,
|
||||
value: int,
|
||||
count: int,
|
||||
onChange: Callable[[int], None]) -> None: ...
|
||||
"""
|
||||
root.add_function(
|
||||
"createTrackbar",
|
||||
[
|
||||
FunctionNode.Arg("trackbarName", PrimitiveTypeNode.str_()),
|
||||
FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()),
|
||||
FunctionNode.Arg("value", PrimitiveTypeNode.int_()),
|
||||
FunctionNode.Arg("count", PrimitiveTypeNode.int_()),
|
||||
FunctionNode.Arg("onChange",
|
||||
CallableTypeNode("TrackbarCallback",
|
||||
PrimitiveTypeNode.int_("int"))),
|
||||
]
|
||||
)
|
||||
"""
|
||||
def createButton(buttonName: str,
|
||||
onChange: Callable[[tuple[int] | tuple[int, Any]], None],
|
||||
userData: Any | None = ...,
|
||||
buttonType: int = ...,
|
||||
initialButtonState: int = ...) -> None: ...
|
||||
"""
|
||||
root.add_function(
|
||||
"createButton",
|
||||
[
|
||||
FunctionNode.Arg("buttonName", PrimitiveTypeNode.str_()),
|
||||
FunctionNode.Arg(
|
||||
"onChange",
|
||||
CallableTypeNode(
|
||||
"ButtonCallback",
|
||||
UnionTypeNode(
|
||||
"onButtonChangeCallbackData",
|
||||
[
|
||||
TupleTypeNode("onButtonChangeCallbackData",
|
||||
[PrimitiveTypeNode.int_(), ]),
|
||||
TupleTypeNode("onButtonChangeCallbackData",
|
||||
[PrimitiveTypeNode.int_(),
|
||||
AnyTypeNode("void*")])
|
||||
]
|
||||
)
|
||||
)),
|
||||
FunctionNode.Arg("userData",
|
||||
OptionalTypeNode(AnyTypeNode("void*")),
|
||||
default_value="None"),
|
||||
FunctionNode.Arg("buttonType", PrimitiveTypeNode.int_(),
|
||||
default_value="0"),
|
||||
FunctionNode.Arg("initialButtonState", PrimitiveTypeNode.int_(),
|
||||
default_value="0")
|
||||
]
|
||||
)
|
||||
"""
|
||||
def setMouseCallback(
|
||||
windowName: str,
|
||||
onMouse: Callback[[int, int, int, int, Any | None], None],
|
||||
param: Any | None = ...
|
||||
) -> None: ...
|
||||
"""
|
||||
root.add_function(
|
||||
"setMouseCallback",
|
||||
[
|
||||
FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()),
|
||||
FunctionNode.Arg(
|
||||
"onMouse",
|
||||
CallableTypeNode("MouseCallback", [
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.int_(),
|
||||
OptionalTypeNode(AnyTypeNode("void*"))
|
||||
])
|
||||
),
|
||||
FunctionNode.Arg("param", OptionalTypeNode(AnyTypeNode("void*")),
|
||||
default_value="None")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def refine_dnn_module(root: NamespaceNode) -> None:
|
||||
if "dnn" not in root.namespaces:
|
||||
return
|
||||
dnn_module = root.namespaces["dnn"]
|
||||
|
||||
"""
|
||||
class LayerProtocol(Protocol):
|
||||
def __init__(
|
||||
self, params: dict[str, DictValue],
|
||||
blobs: typing.Sequence[cv2.typing.MatLike]
|
||||
) -> None: ...
|
||||
|
||||
def getMemoryShapes(
|
||||
self, inputs: typing.Sequence[typing.Sequence[int]]
|
||||
) -> typing.Sequence[typing.Sequence[int]]: ...
|
||||
|
||||
def forward(
|
||||
self, inputs: typing.Sequence[cv2.typing.MatLike]
|
||||
) -> typing.Sequence[cv2.typing.MatLike]: ...
|
||||
"""
|
||||
layer_proto = ProtocolClassNode("LayerProtocol", dnn_module)
|
||||
layer_proto.add_function(
|
||||
"__init__",
|
||||
arguments=[
|
||||
FunctionNode.Arg(
|
||||
"params",
|
||||
DictTypeNode(
|
||||
"LayerParams", PrimitiveTypeNode.str_(),
|
||||
create_type_node("cv::dnn::DictValue")
|
||||
)
|
||||
),
|
||||
FunctionNode.Arg("blobs", create_type_node("vector<cv::Mat>"))
|
||||
]
|
||||
)
|
||||
layer_proto.add_function(
|
||||
"getMemoryShapes",
|
||||
arguments=[
|
||||
FunctionNode.Arg("inputs",
|
||||
create_type_node("vector<vector<int>>"))
|
||||
],
|
||||
return_type=FunctionNode.RetType(
|
||||
create_type_node("vector<vector<int>>")
|
||||
)
|
||||
)
|
||||
layer_proto.add_function(
|
||||
"forward",
|
||||
arguments=[
|
||||
FunctionNode.Arg("inputs", create_type_node("vector<cv::Mat>"))
|
||||
],
|
||||
return_type=FunctionNode.RetType(create_type_node("vector<cv::Mat>"))
|
||||
)
|
||||
|
||||
"""
|
||||
def dnn_registerLayer(layerTypeName: str,
|
||||
layerClass: typing.Type[LayerProtocol]) -> None: ...
|
||||
"""
|
||||
root.add_function(
|
||||
"dnn_registerLayer",
|
||||
arguments=[
|
||||
FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_()),
|
||||
FunctionNode.Arg(
|
||||
"layerClass",
|
||||
ClassTypeNode(ASTNodeTypeNode(
|
||||
layer_proto.export_name, f"dnn.{layer_proto.export_name}"
|
||||
))
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
"""
|
||||
def dnn_unregisterLayer(layerTypeName: str) -> None: ...
|
||||
"""
|
||||
root.add_function(
|
||||
"dnn_unregisterLayer",
|
||||
arguments=[
|
||||
FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_())
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _trim_class_name_from_argument_types(
|
||||
overloads: Iterable[FunctionNode.Overload],
|
||||
class_name: str
|
||||
) -> None:
|
||||
separator = f"{class_name}_"
|
||||
for overload in overloads:
|
||||
for arg in [arg for arg in overload.arguments
|
||||
if arg.type_node is not None]:
|
||||
ast_node = cast(ASTNodeTypeNode, arg.type_node)
|
||||
if class_name in ast_node.ctype_name:
|
||||
fixed_name = ast_node._typename.split(separator)[-1]
|
||||
ast_node._typename = fixed_name
|
||||
|
||||
|
||||
def _find_argument_index(arguments: Sequence[FunctionNode.Arg],
|
||||
name: str) -> Optional[int]:
|
||||
for i, arg in enumerate(arguments):
|
||||
if arg.name == name:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def make_matlike_or_scalar_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]:
|
||||
"""Make arguments accept both MatLike and Scalar types.
|
||||
|
||||
This is used for functions like inRange where the C++ InputArray parameter
|
||||
can accept both Mat objects and Scalar values (tuples, floats, etc.).
|
||||
|
||||
Example: cv2.inRange(img, (0, 0, 0), (255, 255, 255)) should be valid.
|
||||
"""
|
||||
def _make_matlike_or_scalar_arg(root_node: NamespaceNode,
|
||||
function_symbol_name: SymbolName) -> None:
|
||||
from .predefined_types import PREDEFINED_TYPES
|
||||
|
||||
function = find_function_node(root_node, function_symbol_name)
|
||||
for arg_name in arg_names:
|
||||
found_overload_with_arg = False
|
||||
|
||||
for overload in function.overloads:
|
||||
arg_idx = _find_argument_index(overload.arguments, arg_name)
|
||||
|
||||
# skip overloads without this argument
|
||||
if arg_idx is None:
|
||||
continue
|
||||
|
||||
current_type = overload.arguments[arg_idx].type_node
|
||||
|
||||
# Check if it's already a union or if it already includes Scalar
|
||||
if isinstance(current_type, UnionTypeNode):
|
||||
# Check if Scalar is already in the union
|
||||
has_scalar = any(
|
||||
isinstance(item, AliasRefTypeNode) and item.typename == "Scalar"
|
||||
for item in current_type.items
|
||||
)
|
||||
if has_scalar:
|
||||
continue
|
||||
# Add Scalar to existing union
|
||||
scalar_ref = AliasRefTypeNode("Scalar")
|
||||
current_type.items = current_type.items + (scalar_ref,)
|
||||
else:
|
||||
# Create a union of current type and Scalar
|
||||
scalar_ref = AliasRefTypeNode("Scalar")
|
||||
overload.arguments[arg_idx].type_node = UnionTypeNode(
|
||||
f"{arg_name}_type",
|
||||
(cast(TypeNode, current_type), scalar_ref)
|
||||
)
|
||||
|
||||
found_overload_with_arg = True
|
||||
|
||||
if not found_overload_with_arg:
|
||||
raise RuntimeError(
|
||||
f"Failed to find argument with name: '{arg_name}'"
|
||||
f" in '{function_symbol_name.name}' overloads"
|
||||
)
|
||||
|
||||
return _make_matlike_or_scalar_arg
|
||||
|
||||
|
||||
NODES_TO_REFINE = {
|
||||
SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"),
|
||||
SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"),
|
||||
SymbolName(("cv", ), (), "floodFill"): make_optional_arg("mask"),
|
||||
SymbolName(("cv", ), ("Feature2D", ), "detectAndCompute"): make_optional_arg("mask"),
|
||||
SymbolName(("cv", ), (), "findEssentialMat"): make_optional_arg(
|
||||
"distCoeffs1", "distCoeffs2", "dist_coeff1", "dist_coeff2"
|
||||
),
|
||||
SymbolName(("cv", ), (), "drawFrameAxes"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "getOptimalNewCameraMatrix"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "initInverseRectificationMap"): make_optional_arg("distCoeffs", "R"),
|
||||
SymbolName(("cv", ), (), "initUndistortRectifyMap"): make_optional_arg("distCoeffs", "R"),
|
||||
SymbolName(("cv", ), (), "projectPoints"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solveP3P"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solvePnP"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solvePnPGeneric"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solvePnPRansac"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solvePnPRefineLM"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "solvePnPRefineVVS"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "undistort"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "undistortPoints"): make_optional_arg("distCoeffs"),
|
||||
SymbolName(("cv", ), (), "calibrateCamera"): make_optional_arg("cameraMatrix", "distCoeffs"),
|
||||
SymbolName(("cv", "fisheye"), (), "initUndistortRectifyMap"): make_optional_arg("D"),
|
||||
SymbolName(("cv", ), (), "imread"): make_optional_none_return,
|
||||
SymbolName(("cv", ), (), "imdecode"): make_optional_none_return,
|
||||
SymbolName(("cv", ), (), "HoughCircles"): make_optional_none_return,
|
||||
SymbolName(("cv", ), (), "HoughLines"): make_optional_none_return,
|
||||
SymbolName(("cv", ), (), "HoughLinesP"): make_optional_none_return,
|
||||
# Fix for issue #28534: inRange should accept Scalar for lowerb and upperb
|
||||
SymbolName(("cv", ), (), "inRange"): make_matlike_or_scalar_arg("lowerb", "upperb"),
|
||||
}
|
||||
|
||||
ERROR_CLASS_PROPERTIES = (
|
||||
ClassProperty("code", PrimitiveTypeNode.int_(), False),
|
||||
ClassProperty("err", PrimitiveTypeNode.str_(), False),
|
||||
ClassProperty("file", PrimitiveTypeNode.str_(), False),
|
||||
ClassProperty("func", PrimitiveTypeNode.str_(), False),
|
||||
ClassProperty("line", PrimitiveTypeNode.int_(), False),
|
||||
ClassProperty("msg", PrimitiveTypeNode.str_(), False),
|
||||
)
|
||||
@@ -0,0 +1,440 @@
|
||||
from typing import (NamedTuple, Sequence, Tuple, Union, List,
|
||||
Dict, Callable, Optional, Generator, cast)
|
||||
import keyword
|
||||
|
||||
from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode,
|
||||
EnumerationNode, ClassProperty, OptionalTypeNode,
|
||||
TupleTypeNode, PathLikeTypeNode)
|
||||
|
||||
from .types_conversion import create_type_node
|
||||
|
||||
|
||||
class ScopeNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SymbolNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SymbolName(NamedTuple):
|
||||
namespaces: Tuple[str, ...]
|
||||
classes: Tuple[str, ...]
|
||||
name: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '(namespace="{}", classes="{}", name="{}")'.format(
|
||||
'::'.join(self.namespaces),
|
||||
'::'.join(self.classes),
|
||||
self.name
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, full_symbol_name: str,
|
||||
known_namespaces: Sequence[str],
|
||||
symbol_parts_delimiter: str = '.') -> "SymbolName":
|
||||
"""Performs contextual symbol name parsing into namespaces, classes
|
||||
and "bare" symbol name.
|
||||
|
||||
Args:
|
||||
full_symbol_name (str): Input string to parse symbol name from.
|
||||
known_namespaces (Sequence[str]): Collection of namespace that was
|
||||
met during C++ headers parsing.
|
||||
symbol_parts_delimiter (str, optional): Delimiter string used to
|
||||
split `full_symbol_name` string into chunks. Defaults to '.'.
|
||||
|
||||
Returns:
|
||||
SymbolName: Parsed symbol name structure.
|
||||
|
||||
>>> SymbolName.parse('cv.ns.Feature', ('cv', 'cv.ns'))
|
||||
(namespace="cv::ns", classes="", name="Feature")
|
||||
|
||||
>>> SymbolName.parse('cv.ns.Feature', ())
|
||||
(namespace="", classes="cv::ns", name="Feature")
|
||||
|
||||
>>> SymbolName.parse('cv.ns.Feature.Params', ('cv', 'cv.ns'))
|
||||
(namespace="cv::ns", classes="Feature", name="Params")
|
||||
|
||||
>>> SymbolName.parse('cv::ns::Feature::Params::serialize',
|
||||
... known_namespaces=('cv', 'cv.ns'),
|
||||
... symbol_parts_delimiter='::')
|
||||
(namespace="cv::ns", classes="Feature::Params", name="serialize")
|
||||
"""
|
||||
|
||||
chunks = full_symbol_name.split(symbol_parts_delimiter)
|
||||
namespaces, name = chunks[:-1], chunks[-1]
|
||||
classes: List[str] = []
|
||||
while len(namespaces) > 0 and '.'.join(namespaces) not in known_namespaces:
|
||||
classes.insert(0, namespaces.pop())
|
||||
return SymbolName(tuple(namespaces), tuple(classes), name)
|
||||
|
||||
|
||||
def find_scope(root: NamespaceNode, symbol_name: SymbolName,
|
||||
create_missing_namespaces: bool = True) -> Union[NamespaceNode, ClassNode]:
|
||||
"""Traverses down nodes hierarchy to the direct parent of the node referred
|
||||
by `symbol_name`.
|
||||
|
||||
Args:
|
||||
root (NamespaceNode): Root node of the hierarchy.
|
||||
symbol_name (SymbolName): Full symbol name to find scope for.
|
||||
create_missing_namespaces (bool, optional): Set to True to create missing
|
||||
namespaces while traversing the hierarchy. Defaults to True.
|
||||
|
||||
Raises:
|
||||
ScopeNotFoundError: If direct parent for the node referred by `symbol_name`
|
||||
can't be found e.g. one of classes doesn't exist.
|
||||
|
||||
Returns:
|
||||
Union[NamespaceNode, ClassNode]: Direct parent for the node referred by
|
||||
`symbol_name`.
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> algorithm_node = root.add_class('Algorithm')
|
||||
>>> find_scope(root, SymbolName(('cv', ), ('Algorithm',), 'Params')) == algorithm_node
|
||||
True
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> scope = find_scope(root, SymbolName(('cv', 'gapi', 'detail'), (), 'function'))
|
||||
>>> scope.full_export_name
|
||||
'cv.gapi.detail'
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> scope = find_scope(root, SymbolName(('cv', 'gapi'), ('GOpaque',), 'function'))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ast_utils.ScopeNotFoundError: Can't find a scope for 'function', with \
|
||||
'(namespace="cv::gapi", classes="GOpaque", name="function")', \
|
||||
because 'GOpaque' class is not registered yet
|
||||
"""
|
||||
assert isinstance(root, NamespaceNode), \
|
||||
'Wrong hierarchy root type: {}'.format(type(root))
|
||||
|
||||
assert symbol_name.namespaces[0] == root.name, \
|
||||
"Trying to find scope for '{}' with root namespace different from: '{}'".format(
|
||||
symbol_name, root.name
|
||||
)
|
||||
|
||||
scope: Union[NamespaceNode, ClassNode] = root
|
||||
for namespace in symbol_name.namespaces[1:]:
|
||||
if namespace not in scope.namespaces: # type: ignore
|
||||
if not create_missing_namespaces:
|
||||
raise ScopeNotFoundError(
|
||||
"Can't find a scope for '{}', with '{}', because namespace"
|
||||
" '{}' is not created yet and `create_missing_namespaces`"
|
||||
" flag is set to False".format(
|
||||
symbol_name.name, symbol_name, namespace
|
||||
)
|
||||
)
|
||||
scope = scope.add_namespace(namespace) # type: ignore
|
||||
else:
|
||||
scope = scope.namespaces[namespace] # type: ignore
|
||||
for class_name in symbol_name.classes:
|
||||
if class_name not in scope.classes:
|
||||
raise ScopeNotFoundError(
|
||||
"Can't find a scope for '{}', with '{}', because '{}' "
|
||||
"class is not registered yet".format(
|
||||
symbol_name.name, symbol_name, class_name
|
||||
)
|
||||
)
|
||||
scope = scope.classes[class_name]
|
||||
return scope
|
||||
|
||||
|
||||
def find_class_node(root: NamespaceNode, class_symbol: SymbolName,
|
||||
create_missing_namespaces: bool = False) -> ClassNode:
|
||||
scope = find_scope(root, class_symbol, create_missing_namespaces)
|
||||
if class_symbol.name not in scope.classes:
|
||||
raise SymbolNotFoundError(
|
||||
"Can't find {} in its scope".format(class_symbol)
|
||||
)
|
||||
return scope.classes[class_symbol.name]
|
||||
|
||||
|
||||
def find_function_node(root: NamespaceNode, function_symbol: SymbolName,
|
||||
create_missing_namespaces: bool = False) -> FunctionNode:
|
||||
scope = find_scope(root, function_symbol, create_missing_namespaces)
|
||||
if function_symbol.name not in scope.functions:
|
||||
raise SymbolNotFoundError(
|
||||
"Can't find {} in its scope".format(function_symbol)
|
||||
)
|
||||
return scope.functions[function_symbol.name]
|
||||
|
||||
|
||||
def create_function_node_in_scope(scope: Union[NamespaceNode, ClassNode],
|
||||
func_info) -> FunctionNode:
|
||||
def prepare_overload_arguments_and_return_type(variant):
|
||||
arguments = [] # type: list[FunctionNode.Arg]
|
||||
# Enumerate is required, because `argno` in `variant.py_arglist`
|
||||
# refers to position of argument in C++ function interface,
|
||||
# but `variant.py_noptargs` refers to position in `py_arglist`
|
||||
for i, (_, argno) in enumerate(variant.py_arglist):
|
||||
arg_info = variant.args[argno]
|
||||
type_node = create_type_node(arg_info.tp)
|
||||
# Special handling for string representation of the file system path
|
||||
if arg_info.pathlike and type_node.typename == "str":
|
||||
type_node = PathLikeTypeNode.string_or_pathlike_()
|
||||
|
||||
default_value = None
|
||||
if len(arg_info.defval):
|
||||
default_value = arg_info.defval
|
||||
# If argument is optional and can be None - make its type optional
|
||||
if variant.is_arg_optional(i):
|
||||
# NOTE: should UMat be always mandatory for better type hints?
|
||||
# otherwise overload won't be selected e.g. VideoCapture.read()
|
||||
if arg_info.py_outputarg:
|
||||
type_node = OptionalTypeNode(type_node)
|
||||
default_value = "None"
|
||||
elif arg_info.isbig() and "None" not in type_node.typename:
|
||||
# but avoid duplication of the optioness
|
||||
type_node = OptionalTypeNode(type_node)
|
||||
arguments.append(
|
||||
FunctionNode.Arg(arg_info.export_name, type_node=type_node,
|
||||
default_value=default_value)
|
||||
)
|
||||
if func_info.isconstructor:
|
||||
return arguments, None
|
||||
|
||||
# Function has more than 1 output argument, so its return type is a tuple
|
||||
if len(variant.py_outlist) > 1:
|
||||
ret_types = []
|
||||
# Actual returned value of the function goes first
|
||||
if variant.py_outlist[0][1] == -1:
|
||||
ret_types.append(create_type_node(variant.rettype))
|
||||
outlist = variant.py_outlist[1:]
|
||||
else:
|
||||
outlist = variant.py_outlist
|
||||
for _, argno in outlist:
|
||||
assert argno >= 0, \
|
||||
f"Logic Error! Outlist contains function return type: {outlist}"
|
||||
|
||||
ret_types.append(create_type_node(variant.args[argno].tp))
|
||||
|
||||
return arguments, FunctionNode.RetType(
|
||||
TupleTypeNode("return_type", ret_types)
|
||||
)
|
||||
# Function with 1 output argument in Python
|
||||
if len(variant.py_outlist) == 1:
|
||||
# Can be represented as a function with a non-void return type in C++
|
||||
if variant.rettype:
|
||||
return arguments, FunctionNode.RetType(
|
||||
create_type_node(variant.rettype)
|
||||
)
|
||||
# or a function with void return type and output argument type
|
||||
# such non-const reference
|
||||
ret_type = variant.args[variant.py_outlist[0][1]].tp
|
||||
return arguments, FunctionNode.RetType(
|
||||
create_type_node(ret_type)
|
||||
)
|
||||
# Function without output types returns None in Python
|
||||
return arguments, None
|
||||
|
||||
function_node = FunctionNode(func_info.name)
|
||||
function_node.parent = scope
|
||||
if func_info.isconstructor:
|
||||
function_node.export_name = "__init__"
|
||||
for variant in func_info.variants:
|
||||
arguments, ret_type = prepare_overload_arguments_and_return_type(variant)
|
||||
if isinstance(scope, ClassNode):
|
||||
if func_info.is_static:
|
||||
if ret_type is not None and ret_type.typename.endswith(scope.name):
|
||||
function_node.is_classmethod = True
|
||||
arguments.insert(0, FunctionNode.Arg("cls"))
|
||||
else:
|
||||
function_node.is_static = True
|
||||
else:
|
||||
arguments.insert(0, FunctionNode.Arg("self"))
|
||||
function_node.add_overload(arguments, ret_type)
|
||||
return function_node
|
||||
|
||||
|
||||
def create_function_node(root: NamespaceNode, func_info) -> FunctionNode:
|
||||
func_symbol_name = SymbolName(
|
||||
func_info.namespace.split(".") if len(func_info.namespace) else (),
|
||||
func_info.classname.split(".") if len(func_info.classname) else (),
|
||||
func_info.name
|
||||
)
|
||||
return create_function_node_in_scope(find_scope(root, func_symbol_name),
|
||||
func_info)
|
||||
|
||||
|
||||
def create_class_node_in_scope(scope: Union[NamespaceNode, ClassNode],
|
||||
symbol_name: SymbolName,
|
||||
class_info) -> ClassNode:
|
||||
properties = []
|
||||
for property in class_info.props:
|
||||
export_property_name = property.name
|
||||
if keyword.iskeyword(export_property_name):
|
||||
export_property_name += "_"
|
||||
properties.append(
|
||||
ClassProperty(
|
||||
name=export_property_name,
|
||||
type_node=create_type_node(property.tp),
|
||||
is_readonly=property.readonly
|
||||
)
|
||||
)
|
||||
class_node = scope.add_class(symbol_name.name,
|
||||
properties=properties)
|
||||
class_node.export_name = class_info.export_name
|
||||
if class_info.constructor is not None:
|
||||
create_function_node_in_scope(class_node, class_info.constructor)
|
||||
for method in class_info.methods.values():
|
||||
create_function_node_in_scope(class_node, method)
|
||||
return class_node
|
||||
|
||||
|
||||
def create_class_node(root: NamespaceNode, class_info,
|
||||
namespaces: Sequence[str]) -> ClassNode:
|
||||
symbol_name = SymbolName.parse(class_info.full_original_name, namespaces)
|
||||
scope = find_scope(root, symbol_name)
|
||||
return create_class_node_in_scope(scope, symbol_name, class_info)
|
||||
|
||||
|
||||
def resolve_enum_scopes(root: NamespaceNode,
|
||||
enums: Dict[SymbolName, EnumerationNode]):
|
||||
"""Attaches all enumeration nodes to the appropriate classes and modules
|
||||
|
||||
If classes containing enumeration can't be found in the AST - they will
|
||||
be created and marked as not exportable. This behavior is required to cover
|
||||
cases, when enumeration is defined in base class, but only its derivatives
|
||||
are used. Example:
|
||||
```cpp
|
||||
class CV_EXPORTS TermCriteria {
|
||||
public:
|
||||
enum Type { /* ... */ };
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Args:
|
||||
root (NamespaceNode): root of the reconstructed AST
|
||||
enums (Dict[SymbolName, EnumerationNode]): Mapping between enumerations
|
||||
symbol names and corresponding nodes without parents.
|
||||
"""
|
||||
|
||||
for symbol_name, enum_node in enums.items():
|
||||
if symbol_name.classes:
|
||||
try:
|
||||
scope = find_scope(root, symbol_name)
|
||||
except ScopeNotFoundError:
|
||||
# Scope can't be found if enumeration is a part of class
|
||||
# that is not exported.
|
||||
# Create class node, but mark it as not exported
|
||||
for i, class_name in enumerate(symbol_name.classes):
|
||||
scope = find_scope(root,
|
||||
SymbolName(symbol_name.namespaces,
|
||||
classes=symbol_name.classes[:i],
|
||||
name=class_name))
|
||||
if class_name in scope.classes:
|
||||
continue
|
||||
class_node = scope.add_class(class_name)
|
||||
class_node.is_exported = False
|
||||
scope = find_scope(root, symbol_name)
|
||||
else:
|
||||
scope = find_scope(root, symbol_name)
|
||||
enum_node.parent = scope
|
||||
|
||||
|
||||
def get_enclosing_namespace(
|
||||
node: ASTNode,
|
||||
class_node_callback: Optional[Callable[[ClassNode], None]] = None
|
||||
) -> NamespaceNode:
|
||||
"""Traverses up nodes hierarchy to find closest enclosing namespace of the
|
||||
passed node
|
||||
|
||||
Args:
|
||||
node (ASTNode): Node to find a namespace for.
|
||||
class_node_callback (Optional[Callable[[ClassNode], None]]): Optional
|
||||
callable object invoked for each traversed class node in bottom-up
|
||||
order. Defaults: None.
|
||||
|
||||
Returns:
|
||||
NamespaceNode: Closest enclosing namespace of the provided node.
|
||||
|
||||
Raises:
|
||||
AssertionError: if nodes hierarchy missing a namespace node.
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> feature_class = root.add_class("Feature")
|
||||
>>> get_enclosing_namespace(feature_class) == root
|
||||
True
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> feature_class = root.add_class("Feature")
|
||||
>>> feature_params_class = feature_class.add_class("Params")
|
||||
>>> serialize_params_func = feature_params_class.add_function("serialize")
|
||||
>>> get_enclosing_namespace(serialize_params_func) == root
|
||||
True
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> detail_ns = root.add_namespace('detail')
|
||||
>>> flags_enum = detail_ns.add_enumeration('Flags')
|
||||
>>> get_enclosing_namespace(flags_enum) == detail_ns
|
||||
True
|
||||
"""
|
||||
parent_node = node.parent
|
||||
while not isinstance(parent_node, NamespaceNode):
|
||||
assert parent_node is not None, \
|
||||
"Can't find enclosing namespace for '{}' known as: '{}'".format(
|
||||
node.full_export_name, node.native_name
|
||||
)
|
||||
if class_node_callback:
|
||||
class_node_callback(cast(ClassNode, parent_node))
|
||||
parent_node = parent_node.parent
|
||||
return parent_node
|
||||
|
||||
|
||||
def get_enum_module_and_export_name(enum_node: EnumerationNode) -> Tuple[str, str]:
|
||||
"""Get export name of the enum node with its module name.
|
||||
|
||||
Note: Enumeration export names are prefixed with enclosing class names.
|
||||
|
||||
Args:
|
||||
enum_node (EnumerationNode): Enumeration node to construct name for.
|
||||
|
||||
Returns:
|
||||
Tuple[str, str]: a pair of enum export name and its full module name.
|
||||
"""
|
||||
enum_export_name = enum_node.export_name
|
||||
|
||||
def update_full_export_name(class_node: ClassNode) -> None:
|
||||
nonlocal enum_export_name
|
||||
enum_export_name = class_node.export_name + "_" + enum_export_name
|
||||
|
||||
namespace_node = get_enclosing_namespace(enum_node,
|
||||
update_full_export_name)
|
||||
return enum_export_name, namespace_node.full_export_name
|
||||
|
||||
|
||||
def for_each_class(
|
||||
node: Union[NamespaceNode, ClassNode]
|
||||
) -> Generator[ClassNode, None, None]:
|
||||
for cls in node.classes.values():
|
||||
yield cls
|
||||
if len(cls.classes):
|
||||
yield from for_each_class(cls)
|
||||
|
||||
|
||||
def for_each_function(
|
||||
node: Union[NamespaceNode, ClassNode],
|
||||
traverse_class_nodes: bool = True
|
||||
) -> Generator[FunctionNode, None, None]:
|
||||
yield from node.functions.values()
|
||||
if traverse_class_nodes:
|
||||
for cls in for_each_class(node):
|
||||
yield from for_each_function(cls)
|
||||
|
||||
|
||||
def for_each_function_overload(
|
||||
node: Union[NamespaceNode, ClassNode],
|
||||
traverse_class_nodes: bool = True
|
||||
) -> Generator[FunctionNode.Overload, None, None]:
|
||||
for func in for_each_function(node, traverse_class_nodes):
|
||||
yield from func.overloads
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,864 @@
|
||||
__all__ = ("generate_typing_stubs", )
|
||||
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
from typing import (Callable, NamedTuple, Union, Set, Dict,
|
||||
Collection, Tuple, List)
|
||||
import warnings
|
||||
|
||||
from .ast_utils import (get_enclosing_namespace,
|
||||
get_enum_module_and_export_name,
|
||||
for_each_function_overload,
|
||||
for_each_class)
|
||||
|
||||
from .predefined_types import PREDEFINED_TYPES
|
||||
from .api_refinement import apply_manual_api_refinement
|
||||
|
||||
from .nodes import (ASTNode, ASTNodeType, NamespaceNode, ClassNode,
|
||||
FunctionNode, EnumerationNode, ConstantNode,
|
||||
ProtocolClassNode)
|
||||
|
||||
from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode,
|
||||
AggregatedTypeNode, ASTNodeTypeNode,
|
||||
ConditionalAliasTypeNode, PrimitiveTypeNode)
|
||||
|
||||
|
||||
def _clean_stale_stubs_dirs(stubs_root: Path) -> None:
|
||||
"""Remove all subdirectories under stubs_root.
|
||||
|
||||
During incremental builds, disabling a previously enabled module leaves
|
||||
behind its typing stub directory (e.g. cv2/gapi/). Removing all
|
||||
subdirectories before regeneration ensures only stubs for currently
|
||||
enabled modules are present. Top-level files (py.typed, __init__.pyi)
|
||||
are kept because they are managed separately.
|
||||
"""
|
||||
if not stubs_root.is_dir():
|
||||
return
|
||||
for item in stubs_root.iterdir():
|
||||
if item.is_dir():
|
||||
shutil.rmtree(item)
|
||||
|
||||
|
||||
def generate_typing_stubs(root: NamespaceNode, output_path: Path):
|
||||
"""Generates typing stubs for the AST with root `root` and outputs
|
||||
created files tree to directory pointed by `output_path`.
|
||||
|
||||
Stubs generation consist from 4 steps:
|
||||
1. Reconstruction of AST tree for header parser output.
|
||||
2. "Lazy" AST nodes resolution (type nodes used as function arguments
|
||||
and return types). Resolution procedure attaches every "lazy"
|
||||
AST node to the corresponding node in the AST created during step 1.
|
||||
3. Generation of the typing module content. Typing module doesn't exist
|
||||
in library code, but is essential place to define aliases widely used
|
||||
in stub files.
|
||||
4. Generation of typing stubs from the reconstructed AST.
|
||||
Every namespace corresponds to a Python module with the same name.
|
||||
Generation procedure is recursive repetition of the following steps
|
||||
for each namespace (module):
|
||||
- Collect and write required imports for the module
|
||||
- Write all module constants stubs
|
||||
- Write all module enumerations stubs
|
||||
- Write all module classes stubs, preserving correct declaration
|
||||
order, when base classes go before their derivatives.
|
||||
- Write all module functions stubs
|
||||
- Repeat steps above for nested namespaces
|
||||
|
||||
Args:
|
||||
root (NamespaceNode): Root namespace node of the library AST.
|
||||
output_path (Path): Path to output directory.
|
||||
"""
|
||||
# Perform special handling for function arguments that has some conventions
|
||||
# not expressed in their API e.g. optionality of mutually exclusive arguments
|
||||
# without default values:
|
||||
# ```cxx
|
||||
# cv::resize(cv::InputArray src, cv::OutputArray dst, cv::Size dsize,
|
||||
# double fx = 0.0, double fy = 0.0, int interpolation);
|
||||
# ```
|
||||
# should accept `None` as `dsize`:
|
||||
# ```python
|
||||
# cv2.resize(image, dsize=None, fx=0.5, fy=0.5)
|
||||
# ```
|
||||
apply_manual_api_refinement(root)
|
||||
# Most of the time type nodes miss their full name (especially function
|
||||
# arguments and return types), so resolution should start from the narrowest
|
||||
# scope and gradually expanded.
|
||||
# Example:
|
||||
# ```cpp
|
||||
# namespace cv {
|
||||
# enum AlgorithmType {
|
||||
# // ...
|
||||
# };
|
||||
# namespace detail {
|
||||
# struct Algorithm {
|
||||
# static Ptr<Algorithm> create(AlgorithmType alg_type);
|
||||
# };
|
||||
# } // namespace detail
|
||||
# } // namespace cv
|
||||
# ```
|
||||
# To resolve `alg_type` argument of function `create` having `AlgorithmType`
|
||||
# type from above example the following steps are done:
|
||||
# 1. Try to resolve against `cv::detail::Algorithm` - fail
|
||||
# 2. Try to resolve against `cv::detail` - fail
|
||||
# 3. Try to resolve against `cv` - success
|
||||
# The whole process should fail !only! when all possible scopes are
|
||||
# checked and at least 1 node is still unresolved.
|
||||
root.resolve_type_nodes()
|
||||
# Remove stale typing stub subdirectories from previous builds.
|
||||
# In incremental builds, disabling a module (e.g. -DBUILD_opencv_gapi=OFF)
|
||||
# no longer generates its stubs, but leftover directories from a previous
|
||||
# build persist and propagate through the copy/install steps, causing
|
||||
# type-checker errors for stubs referencing unavailable modules.
|
||||
_clean_stale_stubs_dirs(Path(output_path) / root.export_name)
|
||||
_generate_typing_module(root, output_path)
|
||||
_populate_reexported_symbols(root)
|
||||
_generate_typing_stubs(root, output_path)
|
||||
|
||||
|
||||
def _generate_typing_stubs(root: NamespaceNode, output_path: Path) -> None:
|
||||
output_path = Path(output_path) / root.export_name
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Collect all imports required for module items declaration
|
||||
required_imports = _collect_required_imports(root)
|
||||
|
||||
output_stream = StringIO()
|
||||
|
||||
# Add empty __all__ dunder on top of the module
|
||||
output_stream.write("__all__: list[str] = []\n\n")
|
||||
|
||||
# Write required imports at the top of file
|
||||
_write_required_imports(required_imports, output_stream)
|
||||
|
||||
_write_reexported_symbols_section(root, output_stream)
|
||||
|
||||
# NOTE: Enumerations require special handling, because all enumeration
|
||||
# constants are exposed as module attributes
|
||||
has_enums = _generate_section_stub(
|
||||
StubSection("# Enumerations", ASTNodeType.Enumeration), root,
|
||||
output_stream, 0
|
||||
)
|
||||
# Collect all enums from class level and export them to module level
|
||||
for class_node in root.classes.values():
|
||||
if _generate_enums_from_classes_tree(class_node, output_stream,
|
||||
indent=0):
|
||||
has_enums = True
|
||||
# 2 empty lines between enum and classes definitions
|
||||
if has_enums:
|
||||
output_stream.write("\n")
|
||||
|
||||
# Write the rest of module content - classes and functions
|
||||
for section in STUB_SECTIONS:
|
||||
_generate_section_stub(section, root, output_stream, 0)
|
||||
# Dump content to the output file
|
||||
(output_path / "__init__.pyi").write_text(output_stream.getvalue())
|
||||
# Process nested namespaces
|
||||
for ns in root.namespaces.values():
|
||||
_generate_typing_stubs(ns, output_path)
|
||||
|
||||
|
||||
class StubSection(NamedTuple):
|
||||
name: str
|
||||
node_type: ASTNodeType
|
||||
|
||||
|
||||
STUB_SECTIONS = (
|
||||
StubSection("# Constants", ASTNodeType.Constant),
|
||||
# Enumerations are skipped due to special handling rules
|
||||
# StubSection("# Enumerations", ASTNodeType.Enumeration),
|
||||
StubSection("# Classes", ASTNodeType.Class),
|
||||
StubSection("# Functions", ASTNodeType.Function)
|
||||
)
|
||||
|
||||
|
||||
def _generate_section_stub(section: StubSection, node: ASTNode,
|
||||
output_stream: StringIO, indent: int) -> bool:
|
||||
"""Generates stub for a single type of children nodes of the provided node.
|
||||
|
||||
Args:
|
||||
section (StubSection): section identifier that carries section name and
|
||||
type its nodes.
|
||||
node (ASTNode): root node with children nodes used for
|
||||
output_stream (StringIO): Output stream for all nodes stubs related to
|
||||
the given section.
|
||||
indent (int): Indent used for each line written to `output_stream`.
|
||||
|
||||
Returns:
|
||||
bool: `True` if section has a content, `False` otherwise.
|
||||
"""
|
||||
if section.node_type not in node._children:
|
||||
return False
|
||||
|
||||
children = node._children[section.node_type]
|
||||
if len(children) == 0:
|
||||
return False
|
||||
|
||||
output_stream.write(" " * indent)
|
||||
output_stream.write(section.name)
|
||||
output_stream.write("\n")
|
||||
stub_generator = NODE_TYPE_TO_STUB_GENERATOR[section.node_type]
|
||||
children = filter(lambda c: c.is_exported, children.values()) # type: ignore
|
||||
if hasattr(section.node_type, "weight"):
|
||||
children = sorted(children, key=lambda child: getattr(child, "weight")) # type: ignore
|
||||
for child in children:
|
||||
stub_generator(child, output_stream, indent) # type: ignore
|
||||
output_stream.write("\n")
|
||||
return True
|
||||
|
||||
|
||||
def _generate_class_stub(class_node: ClassNode, output_stream: StringIO,
|
||||
indent: int = 0) -> None:
|
||||
"""Generates stub for the provided class node.
|
||||
|
||||
Rules:
|
||||
- Read/write properties are converted to object attributes.
|
||||
- Readonly properties are converted to functions decorated with `@property`.
|
||||
- When return type of static functions matches class name - these functions
|
||||
are treated as factory functions and annotated with `@classmethod`.
|
||||
- In contrast to implicit `this` argument in C++ methods, in Python all
|
||||
"normal" methods have explicit `self` as their first argument.
|
||||
- Body of empty classes is replaced with `...`
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
struct Object : public BaseObject {
|
||||
struct InnerObject {
|
||||
int param;
|
||||
bool param2;
|
||||
|
||||
float readonlyParam();
|
||||
};
|
||||
|
||||
Object(int param, bool param2 = false);
|
||||
|
||||
Object(InnerObject obj);
|
||||
|
||||
static Object create();
|
||||
|
||||
};
|
||||
```
|
||||
becomes
|
||||
```python
|
||||
class Object(BaseObject):
|
||||
class InnerObject:
|
||||
param: int
|
||||
param2: bool
|
||||
|
||||
@property
|
||||
def readonlyParam() -> float: ...
|
||||
|
||||
@typing.override
|
||||
def __init__(self, param: int, param2: bool = ...) -> None: ...
|
||||
|
||||
@typing.override
|
||||
def __init__(self, obj: "Object.InnerObject") -> None: ...
|
||||
|
||||
@classmethod
|
||||
def create(cls) -> Object: ...
|
||||
```
|
||||
|
||||
Args:
|
||||
class_node (ClassNode): Class node to generate stub entry for.
|
||||
output_stream (StringIO): Output stream for class stub.
|
||||
indent (int, optional): Indent used for each line written to
|
||||
`output_stream`. Defaults to 0.
|
||||
"""
|
||||
|
||||
class_module = get_enclosing_namespace(class_node)
|
||||
class_module_name = class_module.full_export_name
|
||||
|
||||
if len(class_node.bases) > 0:
|
||||
bases = []
|
||||
for base in class_node.bases:
|
||||
base_module = get_enclosing_namespace(base) # type: ignore
|
||||
if base_module != class_module:
|
||||
bases.append(base.full_export_name)
|
||||
else:
|
||||
bases.append(base.export_name)
|
||||
|
||||
inheritance_str = f"({', '.join(bases)})"
|
||||
elif isinstance(class_node, ProtocolClassNode):
|
||||
inheritance_str = "(Protocol)"
|
||||
else:
|
||||
inheritance_str = ""
|
||||
|
||||
output_stream.write(
|
||||
"{indent}class {name}{bases}:\n".format(
|
||||
indent=" " * indent,
|
||||
name=class_node.export_name,
|
||||
bases=inheritance_str
|
||||
)
|
||||
)
|
||||
has_content = len(class_node.properties) > 0
|
||||
|
||||
# Processing class properties
|
||||
for property in class_node.properties:
|
||||
if property.is_readonly:
|
||||
template = "{indent}@property\n{indent}def {name}(self) -> {type}: ...\n"
|
||||
else:
|
||||
template = "{indent}{name}: {type}\n"
|
||||
|
||||
output_stream.write(
|
||||
template.format(indent=" " * (indent + 4),
|
||||
name=property.name,
|
||||
type=property.relative_typename(class_module_name))
|
||||
)
|
||||
if len(class_node.properties) > 0:
|
||||
output_stream.write("\n")
|
||||
|
||||
for section in STUB_SECTIONS:
|
||||
if _generate_section_stub(section, class_node,
|
||||
output_stream, indent + 4):
|
||||
has_content = True
|
||||
if not has_content:
|
||||
output_stream.write(" " * (indent + 4))
|
||||
output_stream.write("...\n\n")
|
||||
|
||||
|
||||
def _generate_constant_stub(constant_node: ConstantNode,
|
||||
output_stream: StringIO, indent: int = 0,
|
||||
extra_export_prefix: str = "",
|
||||
generate_uppercase_version: bool = True) -> Tuple[str, ...]:
|
||||
"""Generates stub for the provided constant node.
|
||||
|
||||
Args:
|
||||
constant_node (ConstantNode): Constant node to generate stub entry for.
|
||||
output_stream (StringIO): Output stream for constant stub.
|
||||
indent (int, optional): Indent used for each line written to
|
||||
`output_stream`. Defaults to 0.
|
||||
extra_export_prefix (str, optional): Extra prefix added to the export
|
||||
constant name. Defaults to empty string.
|
||||
generate_uppercase_version (bool, optional): Generate uppercase version
|
||||
alongside the normal one. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Tuple[str, ...]: exported constants names.
|
||||
"""
|
||||
|
||||
def write_constant_to_stream(export_name: str) -> None:
|
||||
output_stream.write(
|
||||
"{indent}{name}: {value_type}\n".format(
|
||||
name=export_name,
|
||||
value_type=constant_node.value_type,
|
||||
indent=" " * indent
|
||||
)
|
||||
)
|
||||
|
||||
export_name = extra_export_prefix + constant_node.export_name
|
||||
write_constant_to_stream(export_name)
|
||||
if generate_uppercase_version:
|
||||
# Handle Python "magic" constants like __version__
|
||||
if re.match(r"^__.*__$", export_name) is not None:
|
||||
return export_name,
|
||||
|
||||
uppercase_name = re.sub(r"([a-z])([A-Z])", r"\1_\2", export_name).upper()
|
||||
if export_name != uppercase_name:
|
||||
write_constant_to_stream(uppercase_name)
|
||||
return export_name, uppercase_name
|
||||
return export_name,
|
||||
|
||||
|
||||
def _generate_enumeration_stub(enumeration_node: EnumerationNode,
|
||||
output_stream: StringIO, indent: int = 0,
|
||||
extra_export_prefix: str = "") -> None:
|
||||
"""Generates stub for the provided enumeration node. In contrast to the
|
||||
Python `enum.Enum` class, C++ enumerations are exported as module-level
|
||||
(or class-level) constants.
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
enum Flags {
|
||||
Flag1 = 0,
|
||||
Flag2 = 1,
|
||||
Flag3
|
||||
};
|
||||
```
|
||||
becomes
|
||||
```python
|
||||
Flag1: int
|
||||
Flag2: int
|
||||
Flag3: int
|
||||
Flags = int # One of [Flag1, Flag2, Flag3]
|
||||
```
|
||||
|
||||
Unnamed enumerations don't export their names to Python:
|
||||
```cpp
|
||||
enum {
|
||||
Flag1 = 0,
|
||||
Flag2 = 1
|
||||
};
|
||||
```
|
||||
becomes
|
||||
```python
|
||||
Flag1: int
|
||||
Flag2: int
|
||||
```
|
||||
|
||||
Scoped enumeration adds its name before each item name:
|
||||
```cpp
|
||||
enum struct ScopedEnum {
|
||||
Flag1,
|
||||
Flag2
|
||||
};
|
||||
```
|
||||
becomes
|
||||
```python
|
||||
ScopedEnum_Flag1: int
|
||||
ScopedEnum_Flag2: int
|
||||
ScopedEnum = int # One of [ScopedEnum_Flag1, ScopedEnum_Flag2]
|
||||
```
|
||||
|
||||
Args:
|
||||
enumeration_node (EnumerationNode): Enumeration node to generate stub entry for.
|
||||
output_stream (StringIO): Output stream for enumeration stub.
|
||||
indent (int, optional): Indent used for each line written to `output_stream`.
|
||||
Defaults to 0.
|
||||
extra_export_prefix (str, optional) Extra prefix added to the export
|
||||
enumeration name. Defaults to empty string.
|
||||
"""
|
||||
|
||||
entries_extra_prefix = extra_export_prefix
|
||||
if enumeration_node.is_scoped:
|
||||
entries_extra_prefix += enumeration_node.export_name + "_"
|
||||
generated_constants_entries: List[str] = []
|
||||
for entry in enumeration_node.constants.values():
|
||||
generated_constants_entries.extend(
|
||||
_generate_constant_stub(entry, output_stream, indent, entries_extra_prefix)
|
||||
)
|
||||
# Unnamed enumerations are skipped as definition
|
||||
if enumeration_node.export_name.endswith("<unnamed>"):
|
||||
output_stream.write("\n")
|
||||
return
|
||||
output_stream.write(
|
||||
'{indent}{export_prefix}{name} = int\n{indent}"""One of [{entries}]"""\n\n'.format(
|
||||
export_prefix=extra_export_prefix,
|
||||
name=enumeration_node.export_name,
|
||||
entries=", ".join(generated_constants_entries),
|
||||
indent=" " * indent
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _generate_function_stub(function_node: FunctionNode,
|
||||
output_stream: StringIO, indent: int = 0) -> None:
|
||||
"""Generates stub entry for the provided function node. Function node can
|
||||
refer free function or class method.
|
||||
|
||||
Args:
|
||||
function_node (FunctionNode): Function node to generate stub entry for.
|
||||
output_stream (StringIO): Output stream for function stub.
|
||||
indent (int, optional): Indent used for each line written to
|
||||
`output_stream`. Defaults to 0.
|
||||
"""
|
||||
|
||||
# Function is a stub without any arguments information
|
||||
if not function_node.overloads:
|
||||
warnings.warn(
|
||||
'Function node "{}" exported as "{}" has no overloads'.format(
|
||||
function_node.full_name, function_node.full_export_name
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
decorators = []
|
||||
if function_node.is_classmethod:
|
||||
decorators.append(" " * indent + "@classmethod")
|
||||
elif function_node.is_static:
|
||||
decorators.append(" " * indent + "@staticmethod")
|
||||
if len(function_node.overloads) > 1:
|
||||
decorators.append(" " * indent + "@_typing.overload")
|
||||
|
||||
function_module = get_enclosing_namespace(function_node)
|
||||
function_module_name = function_module.full_export_name
|
||||
|
||||
for overload in function_node.overloads:
|
||||
# Annotate every function argument
|
||||
annotated_args = []
|
||||
for arg in overload.arguments:
|
||||
annotated_arg = arg.name
|
||||
typename = arg.relative_typename(function_module_name)
|
||||
if typename is not None:
|
||||
annotated_arg += ": " + typename
|
||||
if arg.default_value is not None:
|
||||
annotated_arg += " = ..."
|
||||
annotated_args.append(annotated_arg)
|
||||
|
||||
# And convert return type to the actual type
|
||||
if overload.return_type is not None:
|
||||
ret_type = overload.return_type.relative_typename(function_module_name)
|
||||
else:
|
||||
ret_type = "None"
|
||||
|
||||
output_stream.write(
|
||||
"{decorators}"
|
||||
"{indent}def {name}({args}) -> {ret_type}: ...\n".format(
|
||||
decorators="\n".join(decorators) +
|
||||
"\n" if len(decorators) > 0 else "",
|
||||
name=function_node.export_name,
|
||||
args=", ".join(annotated_args),
|
||||
ret_type=ret_type,
|
||||
indent=" " * indent
|
||||
)
|
||||
)
|
||||
output_stream.write("\n")
|
||||
|
||||
|
||||
def _generate_enums_from_classes_tree(class_node: ClassNode,
|
||||
output_stream: StringIO,
|
||||
indent: int = 0,
|
||||
class_name_prefix: str = "") -> bool:
|
||||
"""Recursively generates class-level enumerations on the module level
|
||||
starting from the `class_node`.
|
||||
|
||||
NOTE: This function is required, because all enumerations are exported as
|
||||
module-level constants.
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
namespace cv {
|
||||
struct TermCriteria {
|
||||
enum Type {
|
||||
COUNT = 1,
|
||||
MAX_ITER = COUNT,
|
||||
EPS = 2
|
||||
};
|
||||
};
|
||||
} // namespace cv
|
||||
```
|
||||
is exported to `__init__.pyi` of `cv` module as as
|
||||
```python
|
||||
TermCriteria_COUNT: int
|
||||
TermCriteria_MAX_ITER: int
|
||||
TermCriteria_EPS: int
|
||||
TermCriteria_Type = int # One of [COUNT, MAX_ITER, EPS]
|
||||
```
|
||||
|
||||
Args:
|
||||
class_node (ClassNode): Class node to generate enumerations stubs for.
|
||||
output_stream (StringIO): Output stream for enumerations stub.
|
||||
indent (int, optional): Indent used for each line written to
|
||||
`output_stream`. Defaults to 0.
|
||||
class_name_prefix (str, optional): Prefix used for enumerations and
|
||||
constants names. Defaults to "".
|
||||
|
||||
Returns:
|
||||
bool: `True` if classes tree declares at least 1 enum, `False` otherwise.
|
||||
"""
|
||||
|
||||
class_name_prefix = class_node.export_name + "_" + class_name_prefix
|
||||
has_content = len(class_node.enumerations) > 0
|
||||
for enum_node in class_node.enumerations.values():
|
||||
_generate_enumeration_stub(enum_node, output_stream, indent,
|
||||
class_name_prefix)
|
||||
for cls in class_node.classes.values():
|
||||
if _generate_enums_from_classes_tree(cls, output_stream, indent,
|
||||
class_name_prefix):
|
||||
has_content = True
|
||||
return has_content
|
||||
|
||||
|
||||
def check_overload_presence(node: Union[NamespaceNode, ClassNode]) -> bool:
|
||||
"""Checks that node has at least 1 function with overload.
|
||||
|
||||
Args:
|
||||
node (Union[NamespaceNode, ClassNode]): Node to check for overload
|
||||
presence.
|
||||
|
||||
Returns:
|
||||
bool: True if input node has at least 1 function with overload, False
|
||||
otherwise.
|
||||
"""
|
||||
for func_node in node.functions.values():
|
||||
if len(func_node.overloads) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _collect_required_imports(root: NamespaceNode) -> Collection[str]:
|
||||
"""Collects all imports required for classes and functions typing stubs
|
||||
declarations.
|
||||
|
||||
Args:
|
||||
root (NamespaceNode): Namespace node to collect imports for
|
||||
|
||||
Returns:
|
||||
Collection[str]: Collection of unique `import smth` statements required
|
||||
for classes and function declarations of `root` node.
|
||||
"""
|
||||
|
||||
def _add_required_usage_imports(type_node: TypeNode, imports: Set[str]):
|
||||
for required_import in type_node.required_usage_imports:
|
||||
imports.add(required_import)
|
||||
|
||||
required_imports: Set[str] = set()
|
||||
# Check if typing module is required due to @overload decorator usage
|
||||
# Looking for module-level function with at least 1 overload
|
||||
has_overload = check_overload_presence(root)
|
||||
# if there is no module-level functions with overload, check its presence
|
||||
# during class traversing, including their inner-classes
|
||||
has_protocol = False
|
||||
for cls in for_each_class(root):
|
||||
if not has_overload and check_overload_presence(cls):
|
||||
has_overload = True
|
||||
required_imports.add("import typing as _typing")
|
||||
# Add required imports for class properties
|
||||
for prop in cls.properties:
|
||||
_add_required_usage_imports(prop.type_node, required_imports)
|
||||
# Add required imports for class bases
|
||||
for base in cls.bases:
|
||||
base_namespace = get_enclosing_namespace(base) # type: ignore
|
||||
if base_namespace != root:
|
||||
required_imports.add(
|
||||
"import " + base_namespace.full_export_name
|
||||
)
|
||||
if isinstance(cls, ProtocolClassNode):
|
||||
has_protocol = True
|
||||
|
||||
if has_overload:
|
||||
required_imports.add("import typing as _typing")
|
||||
# Importing modules required to resolve functions arguments
|
||||
for overload in for_each_function_overload(root):
|
||||
for arg in filter(lambda a: a.type_node is not None,
|
||||
overload.arguments):
|
||||
_add_required_usage_imports(arg.type_node, required_imports) # type: ignore
|
||||
if overload.return_type is not None:
|
||||
_add_required_usage_imports(overload.return_type.type_node,
|
||||
required_imports)
|
||||
|
||||
root_import = "import " + root.full_export_name
|
||||
if root_import in required_imports:
|
||||
required_imports.remove(root_import)
|
||||
|
||||
if has_protocol:
|
||||
required_imports.add("import sys")
|
||||
ordered_required_imports = sorted(required_imports)
|
||||
|
||||
# Protocol import always goes as last import statement
|
||||
if has_protocol:
|
||||
ordered_required_imports.append(
|
||||
"""if sys.version_info >= (3, 8):
|
||||
from typing import Protocol
|
||||
else:
|
||||
from typing_extensions import Protocol"""
|
||||
)
|
||||
|
||||
return ordered_required_imports
|
||||
|
||||
|
||||
def _populate_reexported_symbols(root: NamespaceNode) -> None:
|
||||
# Re-export all submodules to allow referencing symbols in submodules
|
||||
# without submodule import. Example:
|
||||
# `cv2.aruco.ArucoDetector` should be accessible without `import cv2.aruco`
|
||||
def _reexport_submodule(ns: NamespaceNode) -> None:
|
||||
for submodule in ns.namespaces.values():
|
||||
ns.reexported_submodules.append(submodule.export_name)
|
||||
_reexport_submodule(submodule)
|
||||
|
||||
_reexport_submodule(root)
|
||||
|
||||
root.reexported_submodules.append("typing")
|
||||
|
||||
# Special cases, symbols defined in possible pure Python submodules
|
||||
# should be
|
||||
root.reexported_submodules_symbols["mat_wrapper"].append("Mat")
|
||||
|
||||
|
||||
def _write_reexported_symbols_section(module: NamespaceNode,
|
||||
output_stream: StringIO) -> None:
|
||||
"""Write re-export section for the given module.
|
||||
|
||||
Re-export statements have from `from module_name import smth as smth`.
|
||||
Example:
|
||||
```python
|
||||
from cv2 import aruco as aruco
|
||||
from cv2 import cuda as cuda
|
||||
from cv2 import ml as ml
|
||||
from cv2.mat_wrapper import Mat as Mat
|
||||
```
|
||||
|
||||
Args:
|
||||
module (NamespaceNode): Module with re-exported symbols.
|
||||
output_stream (StringIO): Output stream for re-export statements.
|
||||
"""
|
||||
|
||||
parent_name = module.full_export_name
|
||||
for submodule in sorted(module.reexported_submodules):
|
||||
output_stream.write(
|
||||
"from {0} import {1} as {1}\n".format(parent_name, submodule)
|
||||
)
|
||||
|
||||
for submodule, symbols in sorted(module.reexported_submodules_symbols.items(),
|
||||
key=lambda kv: kv[0]):
|
||||
for symbol in symbols:
|
||||
output_stream.write(
|
||||
"from {0}.{1} import {2} as {2}\n".format(
|
||||
parent_name, submodule, symbol
|
||||
)
|
||||
)
|
||||
|
||||
if len(module.reexported_submodules) or \
|
||||
len(module.reexported_submodules_symbols):
|
||||
output_stream.write("\n\n")
|
||||
|
||||
|
||||
def _write_required_imports(required_imports: Collection[str],
|
||||
output_stream: StringIO) -> None:
|
||||
"""Writes all entries of `required_imports` to the `output_stream`.
|
||||
|
||||
Args:
|
||||
required_imports (Collection[str]): Imports to write into the output
|
||||
stream.
|
||||
output_stream (StringIO): Output stream for import statements.
|
||||
"""
|
||||
|
||||
for required_import in required_imports:
|
||||
output_stream.write(required_import)
|
||||
output_stream.write("\n")
|
||||
if len(required_imports):
|
||||
output_stream.write("\n\n")
|
||||
|
||||
|
||||
def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None:
|
||||
"""Generates stub file for typings module.
|
||||
Actual module doesn't exist, but it is an appropriate place to define
|
||||
all widely-used aliases.
|
||||
|
||||
Args:
|
||||
root (NamespaceNode): AST root node used for type nodes resolution.
|
||||
output_path (Path): Path to typing module directory, where __init__.pyi
|
||||
will be written.
|
||||
"""
|
||||
|
||||
def has_all_required_modules(type_node: TypeNode) -> bool:
|
||||
return all(em in root.namespaces for em in type_node.required_modules)
|
||||
|
||||
def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
|
||||
assert isinstance(type_node, AggregatedTypeNode), \
|
||||
f"Provided type node '{type_node.ctype_name}' is not an aggregated type"
|
||||
|
||||
for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node):
|
||||
type_node = PREDEFINED_TYPES[item.ctype_name]
|
||||
if isinstance(type_node, AliasTypeNode):
|
||||
register_alias(type_node)
|
||||
elif isinstance(type_node, ConditionalAliasTypeNode):
|
||||
conditional_type_nodes[type_node.ctype_name] = type_node
|
||||
|
||||
def create_alias_for_enum_node(enum_node_alias: AliasTypeNode) -> ConditionalAliasTypeNode:
|
||||
"""Create conditional int alias corresponding to the given enum node.
|
||||
|
||||
Args:
|
||||
enum_node (AliasTypeNode): Enumeration node to create conditional
|
||||
int alias for.
|
||||
|
||||
Returns:
|
||||
ConditionalAliasTypeNode: conditional int alias node with same
|
||||
export name as enum.
|
||||
"""
|
||||
enum_node = enum_node_alias.ast_node
|
||||
assert enum_node.node_type == ASTNodeType.Enumeration, \
|
||||
f"{enum_node} has wrong node type. Expected type: Enumeration."
|
||||
|
||||
enum_export_name, enum_module_name = get_enum_module_and_export_name(
|
||||
enum_node
|
||||
)
|
||||
return ConditionalAliasTypeNode(
|
||||
enum_export_name,
|
||||
"_typing.TYPE_CHECKING",
|
||||
positive_branch_type=enum_node_alias,
|
||||
negative_branch_type=PrimitiveTypeNode.int_(enum_export_name),
|
||||
condition_required_imports=("import typing as _typing", )
|
||||
)
|
||||
|
||||
def register_alias(alias_node: AliasTypeNode) -> None:
|
||||
typename = alias_node.typename
|
||||
# Check if alias is already registered
|
||||
if typename in aliases:
|
||||
return
|
||||
|
||||
# Collect required imports for alias definition
|
||||
for required_import in alias_node.required_definition_imports:
|
||||
required_imports.add(required_import)
|
||||
|
||||
if isinstance(alias_node.value, AggregatedTypeNode):
|
||||
# Check if collection contains a link to another alias
|
||||
register_alias_links_from_aggregated_type(alias_node.value)
|
||||
|
||||
# Remove references to alias nodes
|
||||
for i, item in enumerate(alias_node.value.items):
|
||||
# Process enumerations only
|
||||
if not isinstance(item, ASTNodeTypeNode) or item.ast_node is None:
|
||||
continue
|
||||
if item.ast_node.node_type != ASTNodeType.Enumeration:
|
||||
continue
|
||||
enum_node = create_alias_for_enum_node(item)
|
||||
alias_node.value.items[i] = enum_node
|
||||
conditional_type_nodes[enum_node.ctype_name] = enum_node
|
||||
|
||||
if isinstance(alias_node.value, ASTNodeTypeNode) \
|
||||
and alias_node.value.ast_node == ASTNodeType.Enumeration:
|
||||
enum_node = create_alias_for_enum_node(alias_node.ast_node)
|
||||
conditional_type_nodes[enum_node.ctype_name] = enum_node
|
||||
return
|
||||
|
||||
# Strip module prefix from aliased types
|
||||
aliases[typename] = alias_node.value.full_typename.replace(
|
||||
root.export_name + ".typing.", ""
|
||||
)
|
||||
if alias_node.doc is not None:
|
||||
aliases[typename] += f'\n"""{alias_node.doc}"""'
|
||||
|
||||
output_path = Path(output_path) / root.export_name / "typing"
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
required_imports: Set[str] = set()
|
||||
aliases: Dict[str, str] = {}
|
||||
conditional_type_nodes: Dict[str, ConditionalAliasTypeNode] = {}
|
||||
|
||||
# Resolve each node and register aliases
|
||||
TypeNode.compatible_to_runtime_usage = True
|
||||
for node in PREDEFINED_TYPES.values():
|
||||
# if node does not have at least one required module skip it
|
||||
# e.g. GArgs requires G-API module, so if build without G-API GArgs is not included
|
||||
if not has_all_required_modules(node):
|
||||
continue
|
||||
node.resolve(root)
|
||||
if isinstance(node, AliasTypeNode):
|
||||
register_alias(node)
|
||||
elif isinstance(node, ConditionalAliasTypeNode):
|
||||
conditional_type_nodes[node.ctype_name] = node
|
||||
|
||||
for node in conditional_type_nodes.values():
|
||||
for required_import in node.required_definition_imports:
|
||||
required_imports.add(required_import)
|
||||
|
||||
output_stream = StringIO()
|
||||
output_stream.write("__all__ = [\n")
|
||||
for alias_name in aliases:
|
||||
output_stream.write(f' "{alias_name}",\n')
|
||||
output_stream.write("]\n\n")
|
||||
|
||||
_write_required_imports(required_imports, output_stream)
|
||||
|
||||
# Add type checking time definitions as generated __init__.py content
|
||||
for _, type_node in conditional_type_nodes.items():
|
||||
output_stream.write(f"if {type_node.condition}:\n ")
|
||||
output_stream.write(f"{type_node.typename} = {type_node.positive_branch_type.full_typename}\nelse:\n")
|
||||
output_stream.write(f" {type_node.typename} = {type_node.negative_branch_type.full_typename}\n\n\n")
|
||||
|
||||
for alias_name, alias_type in aliases.items():
|
||||
output_stream.write(f"{alias_name} = {alias_type}\n")
|
||||
|
||||
TypeNode.compatible_to_runtime_usage = False
|
||||
(output_path / "__init__.py").write_text(output_stream.getvalue())
|
||||
|
||||
|
||||
StubGenerator = Callable[[ASTNode, StringIO, int], None]
|
||||
|
||||
|
||||
NODE_TYPE_TO_STUB_GENERATOR = {
|
||||
ASTNodeType.Class: _generate_class_stub,
|
||||
ASTNodeType.Constant: _generate_constant_stub,
|
||||
ASTNodeType.Enumeration: _generate_enumeration_stub,
|
||||
ASTNodeType.Function: _generate_function_stub
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from .namespace_node import NamespaceNode
|
||||
from .class_node import ClassNode, ClassProperty, ProtocolClassNode
|
||||
from .function_node import FunctionNode
|
||||
from .enumeration_node import EnumerationNode
|
||||
from .constant_node import ConstantNode
|
||||
from .type_node import (
|
||||
TypeNode, OptionalTypeNode, UnionTypeNode, NoneTypeNode, TupleTypeNode,
|
||||
ASTNodeTypeNode, AliasTypeNode, SequenceTypeNode, AnyTypeNode,
|
||||
AggregatedTypeNode, NDArrayTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
|
||||
CallableTypeNode, DictTypeNode, ClassTypeNode, PathLikeTypeNode
|
||||
)
|
||||
@@ -0,0 +1,190 @@
|
||||
from typing import Type, Sequence, NamedTuple, Optional, Tuple, Dict
|
||||
import itertools
|
||||
|
||||
import weakref
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
|
||||
from .function_node import FunctionNode
|
||||
from .enumeration_node import EnumerationNode
|
||||
from .constant_node import ConstantNode
|
||||
|
||||
from .type_node import TypeNode, TypeResolutionError
|
||||
|
||||
|
||||
class ClassProperty(NamedTuple):
|
||||
name: str
|
||||
type_node: TypeNode
|
||||
is_readonly: bool
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self.type_node.full_typename
|
||||
|
||||
def resolve_type_nodes(self, root: ASTNode) -> None:
|
||||
try:
|
||||
self.type_node.resolve(root)
|
||||
except TypeResolutionError as e:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve "{}" property'.format(self.name)
|
||||
) from e
|
||||
|
||||
def relative_typename(self, full_node_name: str) -> str:
|
||||
"""Typename relative to the passed AST node name.
|
||||
|
||||
Args:
|
||||
full_node_name (str): Full export name of the AST node
|
||||
|
||||
Returns:
|
||||
str: typename relative to the passed AST node name
|
||||
"""
|
||||
return self.type_node.relative_typename(full_node_name)
|
||||
|
||||
|
||||
class ClassNode(ASTNode):
|
||||
"""Represents a C++ class that is also a class in Python.
|
||||
|
||||
ClassNode can have functions (methods), enumerations, constants and other
|
||||
classes as its children nodes.
|
||||
|
||||
Class properties are not treated as a part of AST for simplicity and have
|
||||
extra handling if required.
|
||||
"""
|
||||
def __init__(self, name: str, parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None,
|
||||
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
|
||||
properties: Sequence[ClassProperty] = ()) -> None:
|
||||
super().__init__(name, parent, export_name)
|
||||
self.bases = list(bases)
|
||||
self.properties = properties
|
||||
|
||||
@property
|
||||
def weight(self) -> int:
|
||||
return 1 + sum(base.weight for base in self.bases)
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return (ASTNodeType.Class, ASTNodeType.Function,
|
||||
ASTNodeType.Enumeration, ASTNodeType.Constant)
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
return ASTNodeType.Class
|
||||
|
||||
@property
|
||||
def classes(self) -> Dict[str, "ClassNode"]:
|
||||
return self._children[ASTNodeType.Class]
|
||||
|
||||
@property
|
||||
def functions(self) -> Dict[str, FunctionNode]:
|
||||
return self._children[ASTNodeType.Function]
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Dict[str, EnumerationNode]:
|
||||
return self._children[ASTNodeType.Enumeration]
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_class(self, name: str,
|
||||
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
|
||||
properties: Sequence[ClassProperty] = ()) -> "ClassNode":
|
||||
return self._add_child(ClassNode, name, bases=bases,
|
||||
properties=properties)
|
||||
|
||||
def add_function(self, name: str, arguments: Sequence[FunctionNode.Arg] = (),
|
||||
return_type: Optional[FunctionNode.RetType] = None,
|
||||
is_static: bool = False) -> FunctionNode:
|
||||
"""Adds function as a child node of a class.
|
||||
|
||||
Function is classified in 3 categories:
|
||||
1. Instance method.
|
||||
If function is an instance method then `self` argument is
|
||||
inserted at the beginning of its arguments list.
|
||||
|
||||
2. Class method (or factory method)
|
||||
If `is_static` flag is `True` and typename of the function
|
||||
return type matches name of the class then function is treated
|
||||
as class method.
|
||||
|
||||
If function is a class method then `cls` argument is inserted
|
||||
at the beginning of its arguments list.
|
||||
|
||||
3. Static method
|
||||
|
||||
Args:
|
||||
name (str): Name of the function.
|
||||
arguments (Sequence[FunctionNode.Arg], optional): Function arguments.
|
||||
Defaults to ().
|
||||
return_type (Optional[FunctionNode.RetType], optional): Function
|
||||
return type. Defaults to None.
|
||||
is_static (bool, optional): Flag whenever function is static or not.
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
FunctionNode: created function node.
|
||||
"""
|
||||
|
||||
arguments = list(arguments)
|
||||
if return_type is not None:
|
||||
is_classmethod = return_type.typename == self.name
|
||||
else:
|
||||
is_classmethod = False
|
||||
if not is_static:
|
||||
arguments.insert(0, FunctionNode.Arg("self"))
|
||||
elif is_classmethod:
|
||||
is_static = False
|
||||
arguments.insert(0, FunctionNode.Arg("cls"))
|
||||
return self._add_child(FunctionNode, name, arguments=arguments,
|
||||
return_type=return_type, is_static=is_static,
|
||||
is_classmethod=is_classmethod)
|
||||
|
||||
def add_enumeration(self, name: str) -> EnumerationNode:
|
||||
return self._add_child(EnumerationNode, name)
|
||||
|
||||
def add_constant(self, name: str, value: str) -> ConstantNode:
|
||||
return self._add_child(ConstantNode, name, value=value)
|
||||
|
||||
def add_base(self, base_class_node: "ClassNode") -> None:
|
||||
self.bases.append(weakref.proxy(base_class_node))
|
||||
|
||||
def resolve_type_nodes(self, root: ASTNode) -> None:
|
||||
"""Resolves type nodes for all inner-classes, methods and properties
|
||||
in 2 steps:
|
||||
1. Resolve against `self` as a tree root
|
||||
2. Resolve against `root` as a tree root
|
||||
Type resolution errors are postponed until all children nodes are
|
||||
examined.
|
||||
|
||||
Args:
|
||||
root (Optional[ASTNode], optional): Root of the AST sub-tree.
|
||||
Defaults to None.
|
||||
"""
|
||||
|
||||
errors = []
|
||||
for child in itertools.chain(self.properties,
|
||||
self.functions.values(),
|
||||
self.classes.values()):
|
||||
try:
|
||||
try:
|
||||
# Give priority to narrowest scope (class-level scope in this case)
|
||||
child.resolve_type_nodes(self) # type: ignore
|
||||
except TypeResolutionError:
|
||||
child.resolve_type_nodes(root) # type: ignore
|
||||
except TypeResolutionError as e:
|
||||
errors.append(str(e))
|
||||
if len(errors) > 0:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve "{}" class against "{}". Errors: {}'.format(
|
||||
self.full_export_name, root.full_export_name, errors
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ProtocolClassNode(ClassNode):
|
||||
def __init__(self, name: str, parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None,
|
||||
properties: Sequence[ClassProperty] = ()) -> None:
|
||||
super().__init__(name, parent, export_name, bases=(),
|
||||
properties=properties)
|
||||
@@ -0,0 +1,31 @@
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
|
||||
|
||||
class ConstantNode(ASTNode):
|
||||
"""Represents C++ constant that is also a constant in Python.
|
||||
"""
|
||||
def __init__(self, name: str, value: str,
|
||||
parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None) -> None:
|
||||
super().__init__(name, parent, export_name)
|
||||
self.value = value
|
||||
self._value_type = "int"
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return ()
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
return ASTNodeType.Constant
|
||||
|
||||
@property
|
||||
def value_type(self) -> str:
|
||||
return self._value_type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Constant('{}' exported as '{}': {})".format(
|
||||
self.name, self.export_name, self.value
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
from typing import Type, Tuple, Optional, Dict
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
|
||||
from .constant_node import ConstantNode
|
||||
|
||||
|
||||
class EnumerationNode(ASTNode):
|
||||
"""Represents C++ enumeration that treated as named set of constants in
|
||||
Python.
|
||||
|
||||
EnumerationNode can have only constants as its children nodes.
|
||||
"""
|
||||
def __init__(self, name: str, is_scoped: bool = False,
|
||||
parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None) -> None:
|
||||
super().__init__(name, parent, export_name)
|
||||
self.is_scoped = is_scoped
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return (ASTNodeType.Constant, )
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
return ASTNodeType.Enumeration
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_constant(self, name: str, value: str) -> ConstantNode:
|
||||
return self._add_child(ConstantNode, name, value=value)
|
||||
@@ -0,0 +1,140 @@
|
||||
from typing import NamedTuple, Sequence, Optional, Tuple, List
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from .type_node import TypeNode, NoneTypeNode, TypeResolutionError
|
||||
|
||||
|
||||
class FunctionNode(ASTNode):
|
||||
"""Represents a function (or class method) in both C++ and Python.
|
||||
|
||||
This class defines an overload set rather then function itself, because
|
||||
function without overloads is represented as FunctionNode with 1 overload.
|
||||
"""
|
||||
class Arg:
|
||||
def __init__(self, name: str, type_node: Optional[TypeNode] = None,
|
||||
default_value: Optional[str] = None) -> None:
|
||||
self.name = name
|
||||
self.type_node = type_node
|
||||
self.default_value = default_value
|
||||
|
||||
@property
|
||||
def typename(self) -> Optional[str]:
|
||||
return getattr(self.type_node, "full_typename", None)
|
||||
|
||||
def relative_typename(self, root: str) -> Optional[str]:
|
||||
if self.type_node is not None:
|
||||
return self.type_node.relative_typename(root)
|
||||
return None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"Arg(name={self.name}, type_node={self.type_node},"
|
||||
f" default_value={self.default_value})"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
class RetType:
|
||||
def __init__(self, type_node: TypeNode = NoneTypeNode("void")) -> None:
|
||||
self.type_node = type_node
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self.type_node.full_typename
|
||||
|
||||
def relative_typename(self, root: str) -> Optional[str]:
|
||||
return self.type_node.relative_typename(root)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"RetType(type_node={self.type_node})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
class Overload(NamedTuple):
|
||||
arguments: Sequence["FunctionNode.Arg"] = ()
|
||||
return_type: Optional["FunctionNode.RetType"] = None
|
||||
|
||||
def __init__(self, name: str,
|
||||
arguments: Optional[Sequence["FunctionNode.Arg"]] = None,
|
||||
return_type: Optional["FunctionNode.RetType"] = None,
|
||||
is_static: bool = False,
|
||||
is_classmethod: bool = False,
|
||||
parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None) -> None:
|
||||
"""Function node initializer
|
||||
|
||||
Args:
|
||||
name (str): Name of the function overload set
|
||||
arguments (Optional[Sequence[FunctionNode.Arg]], optional): Function
|
||||
arguments. If this argument is None, then no overloads are
|
||||
added and node should be treated like a "function stub" rather
|
||||
than function. This might be helpful if there is a knowledge
|
||||
that function with the defined name exists, but information
|
||||
about its interface is not available at that moment.
|
||||
Defaults to None.
|
||||
return_type (Optional[FunctionNode.RetType], optional): Function
|
||||
return type. Defaults to None.
|
||||
is_static (bool, optional): Flag pointing that function is
|
||||
a static method of some class. Defaults to False.
|
||||
is_classmethod (bool, optional): Flag pointing that function is
|
||||
a class method of some class. Defaults to False.
|
||||
parent (Optional[ASTNode], optional): Parent ASTNode of the function.
|
||||
Can be class or namespace. Defaults to None.
|
||||
export_name (Optional[str], optional): Export name of the function.
|
||||
Defaults to None.
|
||||
"""
|
||||
|
||||
super().__init__(name, parent, export_name)
|
||||
self.overloads: List[FunctionNode.Overload] = []
|
||||
self.is_static = is_static
|
||||
self.is_classmethod = is_classmethod
|
||||
if arguments is not None:
|
||||
self.add_overload(arguments, return_type)
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
return ASTNodeType.Function
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return ()
|
||||
|
||||
def add_overload(self, arguments: Sequence["FunctionNode.Arg"] = (),
|
||||
return_type: Optional["FunctionNode.RetType"] = None):
|
||||
self.overloads.append(FunctionNode.Overload(arguments, return_type))
|
||||
|
||||
def resolve_type_nodes(self, root: ASTNode):
|
||||
"""Resolves type nodes in all overloads against `root`
|
||||
|
||||
Type resolution errors are postponed until all type nodes are examined.
|
||||
|
||||
Args:
|
||||
root (ASTNode): Root of AST sub-tree used for type nodes resolution.
|
||||
"""
|
||||
def has_unresolved_type_node(item) -> bool:
|
||||
return item.type_node is not None and not item.type_node.is_resolved
|
||||
|
||||
errors = []
|
||||
for overload in self.overloads:
|
||||
for arg in filter(has_unresolved_type_node, overload.arguments):
|
||||
try:
|
||||
arg.type_node.resolve(root) # type: ignore
|
||||
except TypeResolutionError as e:
|
||||
errors.append(
|
||||
'Failed to resolve "{}" argument: {}'.format(arg.name, e)
|
||||
)
|
||||
if overload.return_type is not None and \
|
||||
has_unresolved_type_node(overload.return_type):
|
||||
try:
|
||||
overload.return_type.type_node.resolve(root)
|
||||
except TypeResolutionError as e:
|
||||
errors.append('Failed to resolve return type: {}'.format(e))
|
||||
if len(errors) > 0:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve "{}" function against "{}". Errors: {}'.format(
|
||||
self.full_export_name, root.full_export_name,
|
||||
", ".join("[{}]: {}".format(i, e) for i, e in enumerate(errors))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
import itertools
|
||||
import weakref
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from .class_node import ClassNode, ClassProperty
|
||||
from .constant_node import ConstantNode
|
||||
from .enumeration_node import EnumerationNode
|
||||
from .function_node import FunctionNode
|
||||
from .node import ASTNode, ASTNodeType
|
||||
from .type_node import TypeResolutionError
|
||||
|
||||
|
||||
class NamespaceNode(ASTNode):
|
||||
"""Represents C++ namespace that treated as module in Python.
|
||||
|
||||
NamespaceNode can have other namespaces, classes, functions, enumerations
|
||||
and global constants as its children nodes.
|
||||
"""
|
||||
def __init__(self, name: str, parent: Optional[ASTNode] = None,
|
||||
export_name: Optional[str] = None) -> None:
|
||||
super().__init__(name, parent, export_name)
|
||||
self.reexported_submodules: List[str] = []
|
||||
"""List of reexported submodules"""
|
||||
|
||||
self.reexported_submodules_symbols: Dict[str, List[str]] = defaultdict(list)
|
||||
"""Mapping between submodules export names and their symbols re-exported
|
||||
in this module"""
|
||||
|
||||
|
||||
@property
|
||||
def node_type(self) -> ASTNodeType:
|
||||
return ASTNodeType.Namespace
|
||||
|
||||
@property
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
return (ASTNodeType.Namespace, ASTNodeType.Class, ASTNodeType.Function,
|
||||
ASTNodeType.Enumeration, ASTNodeType.Constant)
|
||||
|
||||
@property
|
||||
def namespaces(self) -> Dict[str, "NamespaceNode"]:
|
||||
return self._children[ASTNodeType.Namespace]
|
||||
|
||||
@property
|
||||
def classes(self) -> Dict[str, ClassNode]:
|
||||
return self._children[ASTNodeType.Class]
|
||||
|
||||
@property
|
||||
def functions(self) -> Dict[str, FunctionNode]:
|
||||
return self._children[ASTNodeType.Function]
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Dict[str, EnumerationNode]:
|
||||
return self._children[ASTNodeType.Enumeration]
|
||||
|
||||
@property
|
||||
def constants(self) -> Dict[str, ConstantNode]:
|
||||
return self._children[ASTNodeType.Constant]
|
||||
|
||||
def add_namespace(self, name: str) -> "NamespaceNode":
|
||||
return self._add_child(NamespaceNode, name)
|
||||
|
||||
def add_class(self, name: str,
|
||||
bases: Sequence["weakref.ProxyType[ClassNode]"] = (),
|
||||
properties: Sequence[ClassProperty] = ()) -> "ClassNode":
|
||||
return self._add_child(ClassNode, name, bases=bases,
|
||||
properties=properties)
|
||||
|
||||
def add_function(self, name: str, arguments: Sequence[FunctionNode.Arg] = (),
|
||||
return_type: Optional[FunctionNode.RetType] = None) -> FunctionNode:
|
||||
return self._add_child(FunctionNode, name, arguments=arguments,
|
||||
return_type=return_type)
|
||||
|
||||
def add_enumeration(self, name: str) -> EnumerationNode:
|
||||
return self._add_child(EnumerationNode, name)
|
||||
|
||||
def add_constant(self, name: str, value: str) -> ConstantNode:
|
||||
return self._add_child(ConstantNode, name, value=value)
|
||||
|
||||
def resolve_type_nodes(self, root: Optional[ASTNode] = None) -> None:
|
||||
"""Resolves type nodes for all children nodes in 2 steps:
|
||||
1. Resolve against `self` as a tree root
|
||||
2. Resolve against `root` as a tree root
|
||||
Type resolution errors are postponed until all children nodes are
|
||||
examined.
|
||||
|
||||
Args:
|
||||
root (Optional[ASTNode], optional): Root of the AST sub-tree.
|
||||
Defaults to None.
|
||||
"""
|
||||
errors = []
|
||||
for child in itertools.chain(self.functions.values(),
|
||||
self.classes.values(),
|
||||
self.namespaces.values()):
|
||||
try:
|
||||
try:
|
||||
child.resolve_type_nodes(self) # type: ignore
|
||||
except TypeResolutionError:
|
||||
if root is not None:
|
||||
child.resolve_type_nodes(root) # type: ignore
|
||||
else:
|
||||
raise
|
||||
except TypeResolutionError as e:
|
||||
errors.append(str(e))
|
||||
if len(errors) > 0:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve "{}" namespace against "{}". '
|
||||
'Errors: {}'.format(
|
||||
self.full_export_name,
|
||||
root if root is None else root.full_export_name,
|
||||
errors
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,233 @@
|
||||
import abc
|
||||
import enum
|
||||
import itertools
|
||||
from typing import (Iterator, Type, TypeVar, Dict,
|
||||
Optional, Tuple, DefaultDict)
|
||||
from collections import defaultdict
|
||||
|
||||
import weakref
|
||||
|
||||
|
||||
ASTNodeSubtype = TypeVar("ASTNodeSubtype", bound="ASTNode")
|
||||
NodeType = Type["ASTNode"]
|
||||
NameToNode = Dict[str, ASTNodeSubtype]
|
||||
|
||||
|
||||
class ASTNodeType(enum.Enum):
|
||||
Namespace = enum.auto()
|
||||
Class = enum.auto()
|
||||
Function = enum.auto()
|
||||
Enumeration = enum.auto()
|
||||
Constant = enum.auto()
|
||||
|
||||
|
||||
class ASTNode:
|
||||
"""Represents an element of the Abstract Syntax Tree produced by parsing
|
||||
public C++ headers.
|
||||
|
||||
NOTE: Every node manages a lifetime of its children nodes. Children nodes
|
||||
contain only weak references to their direct parents, so there are no
|
||||
circular dependencies.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, parent: Optional["ASTNode"] = None,
|
||||
export_name: Optional[str] = None) -> None:
|
||||
"""ASTNode initializer
|
||||
|
||||
Args:
|
||||
name (str): name of the node, should be unique inside enclosing
|
||||
context (There can't be 2 classes with the same name defined
|
||||
in the same namespace).
|
||||
parent (ASTNode, optional): parent node expressing node context.
|
||||
None corresponds to globally defined object e.g. root namespace
|
||||
or function without namespace. Defaults to None.
|
||||
export_name (str, optional): export name of the node used to resolve
|
||||
issues in languages without proper overload resolution and
|
||||
provide more meaningful naming. Defaults to None.
|
||||
"""
|
||||
|
||||
FORBIDDEN_SYMBOLS = ";,*&#/|\\@!()[]^% "
|
||||
for forbidden_symbol in FORBIDDEN_SYMBOLS:
|
||||
assert forbidden_symbol not in name, \
|
||||
"Invalid node identifier '{}' - contains 1 or more "\
|
||||
"forbidden symbols: ({})".format(name, FORBIDDEN_SYMBOLS)
|
||||
|
||||
assert ":" not in name, \
|
||||
"Name '{}' contains C++ scope symbols (':'). Convert the name to "\
|
||||
"Python style and create appropriate parent nodes".format(name)
|
||||
|
||||
assert "." not in name, \
|
||||
"Trying to create a node with '.' symbols in its name ({}). " \
|
||||
"Dots are supposed to be a scope delimiters, so create all nodes in ('{}') " \
|
||||
"and add '{}' as a last child node".format(
|
||||
name,
|
||||
"->".join(name.split('.')[:-1]),
|
||||
name.rsplit('.', maxsplit=1)[-1]
|
||||
)
|
||||
|
||||
self.__name = name
|
||||
self.export_name = name if export_name is None else export_name
|
||||
self._parent: Optional["ASTNode"] = None
|
||||
self.parent = parent
|
||||
self.is_exported = True
|
||||
self._children: DefaultDict[ASTNodeType, NameToNode] = defaultdict(dict)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "{}('{}' exported as '{}')".format(
|
||||
self.node_type.name, self.name, self.export_name
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
@abc.abstractproperty
|
||||
def children_types(self) -> Tuple[ASTNodeType, ...]:
|
||||
"""Set of ASTNode types that are allowed to be children of this node
|
||||
|
||||
Returns:
|
||||
Tuple[ASTNodeType, ...]: Types of children nodes
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractproperty
|
||||
def node_type(self) -> ASTNodeType:
|
||||
"""Type of the ASTNode that can be used to distinguish nodes without
|
||||
importing all subclasses of ASTNode
|
||||
|
||||
Returns:
|
||||
ASTNodeType: Current node type
|
||||
"""
|
||||
pass
|
||||
|
||||
def node_type_name(self) -> str:
|
||||
return f"{self.node_type.name}::{self.name}"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
@property
|
||||
def native_name(self) -> str:
|
||||
return self.full_name.replace(".", "::")
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
return self._construct_full_name("name")
|
||||
|
||||
@property
|
||||
def full_export_name(self) -> str:
|
||||
return self._construct_full_name("export_name")
|
||||
|
||||
@property
|
||||
def parent(self) -> Optional["ASTNode"]:
|
||||
return self._parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, value: Optional["ASTNode"]) -> None:
|
||||
assert value is None or isinstance(value, ASTNode), \
|
||||
"ASTNode.parent should be None or another ASTNode, " \
|
||||
"but got: {}".format(type(value))
|
||||
|
||||
if value is not None:
|
||||
value.__check_child_before_add(self, self.name)
|
||||
|
||||
# Detach from previous parent
|
||||
if self._parent is not None:
|
||||
self._parent._children[self.node_type].pop(self.name)
|
||||
|
||||
if value is None:
|
||||
self._parent = None
|
||||
return
|
||||
|
||||
# Set a weak reference to a new parent and add self to its children
|
||||
self._parent = weakref.proxy(value)
|
||||
value._children[self.node_type][self.name] = self
|
||||
|
||||
def __check_child_before_add(self, child: ASTNodeSubtype,
|
||||
name: str) -> None:
|
||||
assert len(self.children_types) > 0, (
|
||||
f"Trying to add child node '{child.node_type_name}' to node "
|
||||
f"'{self.node_type_name}' that can't have children nodes"
|
||||
)
|
||||
|
||||
assert child.node_type in self.children_types, \
|
||||
"Trying to add child node '{}' to node '{}' " \
|
||||
"that supports only ({}) as its children types".format(
|
||||
child.node_type_name, self.node_type_name,
|
||||
",".join(t.name for t in self.children_types)
|
||||
)
|
||||
|
||||
if self._find_child(child.node_type, name) is not None:
|
||||
raise ValueError(
|
||||
f"Node '{self.node_type_name}' already has a "
|
||||
f"child '{child.node_type_name}'"
|
||||
)
|
||||
|
||||
def _add_child(self, child_type: Type[ASTNodeSubtype], name: str,
|
||||
**kwargs) -> ASTNodeSubtype:
|
||||
"""Creates a child of the node with the given type and performs common
|
||||
validation checks:
|
||||
- Node can have children of the provided type
|
||||
- Node doesn't have child with the same name
|
||||
|
||||
NOTE: Shouldn't be used directly by a user.
|
||||
|
||||
Args:
|
||||
child_type (Type[ASTNodeSubtype]): Type of the child to create.
|
||||
name (str): Name of the child.
|
||||
**kwargs: Extra keyword arguments supplied to child_type.__init__
|
||||
method.
|
||||
|
||||
Returns:
|
||||
ASTNodeSubtype: Created ASTNode
|
||||
"""
|
||||
return child_type(name, parent=self, **kwargs)
|
||||
|
||||
def _find_child(self, child_type: ASTNodeType,
|
||||
name: str) -> Optional[ASTNodeSubtype]:
|
||||
"""Looks for child node with the given type and name.
|
||||
|
||||
Args:
|
||||
child_type (ASTNodeType): Type of the child node.
|
||||
name (str): Name of the child node.
|
||||
|
||||
Returns:
|
||||
Optional[ASTNodeSubtype]: child node if it can be found, None
|
||||
otherwise.
|
||||
"""
|
||||
if child_type not in self._children:
|
||||
return None
|
||||
return self._children[child_type].get(name, None)
|
||||
|
||||
def _construct_full_name(self, property_name: str) -> str:
|
||||
"""Traverses nodes hierarchy upright to the root node and constructs a
|
||||
full name of the node using original or export names depending on the
|
||||
provided `property_name` argument.
|
||||
|
||||
Args:
|
||||
property_name (str): Name of the property to quire from node to get
|
||||
its name. Should be `name` or `export_name`.
|
||||
|
||||
Returns:
|
||||
str: full node name where each node part is divided with a dot.
|
||||
"""
|
||||
def get_name(node: ASTNode) -> str:
|
||||
return getattr(node, property_name)
|
||||
|
||||
assert property_name in ('name', 'export_name'), 'Invalid name property'
|
||||
|
||||
name_parts = [get_name(self), ]
|
||||
parent = self.parent
|
||||
while parent is not None:
|
||||
name_parts.append(get_name(parent))
|
||||
parent = parent.parent
|
||||
return ".".join(reversed(name_parts))
|
||||
|
||||
def __iter__(self) -> Iterator["ASTNode"]:
|
||||
return iter(itertools.chain.from_iterable(
|
||||
node
|
||||
# Iterate over mapping between node type and nodes dict
|
||||
for children_nodes in self._children.values()
|
||||
# Iterate over mapping between node name and node
|
||||
for node in children_nodes.values()
|
||||
))
|
||||
@@ -0,0 +1,991 @@
|
||||
from typing import Sequence, Generator, Tuple, Optional, Union
|
||||
import weakref
|
||||
import abc
|
||||
from itertools import chain
|
||||
|
||||
from .node import ASTNode, ASTNodeType
|
||||
|
||||
|
||||
class TypeResolutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TypeNode(abc.ABC):
|
||||
"""This class and its derivatives used for construction parts of AST that
|
||||
otherwise can't be constructed from the information provided by header
|
||||
parser, because this information is either not available at that moment of
|
||||
time or not available at all:
|
||||
- There is no possible way to derive correspondence between C++ type
|
||||
and its Python equivalent if it is not exposed from library
|
||||
e.g. `cv::Rect`.
|
||||
- There is no information about types visibility (see `ASTNodeTypeNode`).
|
||||
"""
|
||||
compatible_to_runtime_usage = False
|
||||
"""Class-wide property that switches exported type names for several nodes.
|
||||
Example:
|
||||
>>> node = OptionalTypeNode(ASTNodeTypeNode("Size"))
|
||||
>>> node.typename # TypeNode.compatible_to_runtime_usage == False
|
||||
"Size | None"
|
||||
>>> TypeNode.compatible_to_runtime_usage = True
|
||||
>>> node.typename
|
||||
"typing.Optional[Size]"
|
||||
"""
|
||||
|
||||
def __init__(self, ctype_name: str, required_modules: Tuple[str, ...] = ()) -> None:
|
||||
self.ctype_name = ctype_name
|
||||
self._required_modules = required_modules
|
||||
|
||||
@abc.abstractproperty
|
||||
def typename(self) -> str:
|
||||
"""Short name of the type node used that should be used in the same
|
||||
module (or a file) where type is defined.
|
||||
|
||||
Returns:
|
||||
str: short name of the type node.
|
||||
"""
|
||||
return ""
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
"""Full name of the type node including full module name starting from
|
||||
the package.
|
||||
Example: 'cv2.Algorithm', 'cv2.gapi.ie.PyParams'.
|
||||
|
||||
Returns:
|
||||
str: full name of the type node.
|
||||
"""
|
||||
return self.typename
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
"""Generator filled with import statements required for type
|
||||
node definition (especially used by `AliasTypeNode`).
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Alias defined in the `cv2.typing.__init__.pyi`
|
||||
Callback = typing.Callable[[cv2.GMat, float], None]
|
||||
|
||||
# alias definition
|
||||
callback_alias = AliasTypeNode.callable_(
|
||||
'Callback',
|
||||
arg_types=(ASTNodeTypeNode('GMat'), PrimitiveTypeNode.float_())
|
||||
)
|
||||
|
||||
# Required definition imports
|
||||
for required_import in callback_alias.required_definition_imports:
|
||||
print(required_import)
|
||||
# Outputs:
|
||||
# 'import typing'
|
||||
# 'import cv2'
|
||||
```
|
||||
|
||||
Yields:
|
||||
Generator[str, None, None]: generator filled with import statements
|
||||
required for type node definition.
|
||||
"""
|
||||
yield from ()
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
"""Generator filled with import statements required for type node
|
||||
usage.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Alias defined in the `cv2.typing.__init__.pyi`
|
||||
Callback = typing.Callable[[cv2.GMat, float], None]
|
||||
|
||||
# alias definition
|
||||
callback_alias = AliasTypeNode.callable_(
|
||||
'Callback',
|
||||
arg_types=(ASTNodeTypeNode('GMat'), PrimitiveTypeNode.float_())
|
||||
)
|
||||
|
||||
# Required usage imports
|
||||
for required_import in callback_alias.required_usage_imports:
|
||||
print(required_import)
|
||||
# Outputs:
|
||||
# 'import cv2.typing'
|
||||
```
|
||||
|
||||
Yields:
|
||||
Generator[str, None, None]: generator filled with import statements
|
||||
required for type node definition.
|
||||
"""
|
||||
yield from ()
|
||||
|
||||
@property
|
||||
def required_modules(self) -> Tuple[str, ...]:
|
||||
return self._required_modules
|
||||
|
||||
@property
|
||||
def is_resolved(self) -> bool:
|
||||
return True
|
||||
|
||||
def relative_typename(self, module: str) -> str:
|
||||
"""Type name relative to the provided module.
|
||||
|
||||
Args:
|
||||
module (str): Full export name of the module to get relative name to.
|
||||
|
||||
Returns:
|
||||
str: If module name of the type node doesn't match `module`, then
|
||||
returns class scopes + `self.typename`, otherwise
|
||||
`self.full_typename`.
|
||||
"""
|
||||
return self.full_typename
|
||||
|
||||
def resolve(self, root: ASTNode) -> None:
|
||||
"""Resolves all references to AST nodes using a top-down search
|
||||
for nodes with corresponding export names. See `_resolve_symbol` for
|
||||
more details.
|
||||
|
||||
Args:
|
||||
root (ASTNode): Node pointing to the root of a subtree in AST
|
||||
representing search scope of the symbol.
|
||||
Most of the symbols don't have full paths in their names, so
|
||||
scopes should be examined in bottom-up manner starting
|
||||
with narrowest one.
|
||||
|
||||
Raises:
|
||||
TypeResolutionError: if at least 1 reference to AST node can't
|
||||
be resolved in the subtree pointed by the root.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NoneTypeNode(TypeNode):
|
||||
"""Type node representing a None (or `void` in C++) type.
|
||||
"""
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return "None"
|
||||
|
||||
|
||||
class AnyTypeNode(TypeNode):
|
||||
"""Type node representing any type (most of the time it means unknown).
|
||||
"""
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return "_typing.Any"
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import typing as _typing"
|
||||
|
||||
|
||||
class PrimitiveTypeNode(TypeNode):
|
||||
"""Type node representing a primitive built-in types e.g. int, float, str.
|
||||
"""
|
||||
def __init__(self, ctype_name: str,
|
||||
typename: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, required_modules)
|
||||
self._typename = typename if typename is not None else ctype_name
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self._typename
|
||||
|
||||
@classmethod
|
||||
def int_(cls, ctype_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
if ctype_name is None:
|
||||
ctype_name = "int"
|
||||
return PrimitiveTypeNode(ctype_name, typename="int", required_modules=required_modules)
|
||||
|
||||
@classmethod
|
||||
def float_(cls, ctype_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
if ctype_name is None:
|
||||
ctype_name = "float"
|
||||
return PrimitiveTypeNode(ctype_name, typename="float", required_modules=required_modules)
|
||||
|
||||
@classmethod
|
||||
def bool_(cls, ctype_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
if ctype_name is None:
|
||||
ctype_name = "bool"
|
||||
return PrimitiveTypeNode(ctype_name, typename="bool", required_modules=required_modules)
|
||||
|
||||
@classmethod
|
||||
def str_(cls, ctype_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
if ctype_name is None:
|
||||
ctype_name = "string"
|
||||
return PrimitiveTypeNode(ctype_name, "str", required_modules=required_modules)
|
||||
|
||||
|
||||
class AliasRefTypeNode(TypeNode):
|
||||
"""Type node representing an alias referencing another alias. Example:
|
||||
```python
|
||||
Point2i = tuple[int, int]
|
||||
Point = Point2i
|
||||
```
|
||||
During typing stubs generation procedure above code section might be defined
|
||||
as follows
|
||||
```python
|
||||
AliasTypeNode.tuple_("Point2i",
|
||||
items=(
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.int_()
|
||||
))
|
||||
AliasTypeNode.ref_("Point", "Point2i")
|
||||
```
|
||||
"""
|
||||
def __init__(self, alias_ctype_name: str,
|
||||
alias_export_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
super().__init__(alias_ctype_name, required_modules)
|
||||
if alias_export_name is None:
|
||||
self.alias_export_name = alias_ctype_name
|
||||
else:
|
||||
self.alias_export_name = alias_export_name
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self.alias_export_name
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
return "cv2.typing." + self.typename
|
||||
|
||||
|
||||
class AliasTypeNode(TypeNode):
|
||||
"""Type node representing an alias to another type.
|
||||
Example:
|
||||
```python
|
||||
Point2i = tuple[int, int]
|
||||
```
|
||||
can be defined as
|
||||
```python
|
||||
AliasTypeNode.tuple_("Point2i",
|
||||
items=(
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.int_()
|
||||
))
|
||||
```
|
||||
Under the hood it is implemented as a container of another type node.
|
||||
"""
|
||||
def __init__(self, ctype_name: str, value: TypeNode,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, required_modules)
|
||||
self.value = value
|
||||
# If alias is exported as is - use its ctype_name
|
||||
if export_name is None:
|
||||
forbidden_symbols = (":", "*", "&")
|
||||
assert all(symbol not in ctype_name for symbol in forbidden_symbols), (
|
||||
"Failed to create AliasTypeNode without export_name. "
|
||||
f"'{ctype_name}' should not contain any of {forbidden_symbols}"
|
||||
)
|
||||
self._export_name = ctype_name
|
||||
else:
|
||||
self._export_name = export_name
|
||||
self.doc = doc
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self._export_name
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
return "cv2.typing." + self.typename
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
return self.value.required_usage_imports
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import cv2.typing"
|
||||
|
||||
@property
|
||||
def is_resolved(self) -> bool:
|
||||
return self.value.is_resolved
|
||||
|
||||
def resolve(self, root: ASTNode):
|
||||
try:
|
||||
self.value.resolve(root)
|
||||
except TypeResolutionError as e:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve alias "{}" exposed as "{}"'.format(
|
||||
self.ctype_name, self.typename
|
||||
)
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def int_(cls, ctype_name: str, export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def float_(cls, ctype_name: str, export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def array_ref_(cls, ctype_name: str, array_ref_name: str,
|
||||
shape: Optional[Tuple[int, ...]],
|
||||
dtype: Optional[str] = None,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
"""Create alias to array reference alias `array_ref_name`.
|
||||
|
||||
This is required to preserve backward compatibility with Python < 3.9
|
||||
and NumPy 1.20, when NumPy module introduces generics support.
|
||||
|
||||
Args:
|
||||
ctype_name (str): Name of the alias.
|
||||
array_ref_name (str): Name of the conditional array alias.
|
||||
shape (Optional[Tuple[int, ...]]): Array shape.
|
||||
dtype (Optional[str], optional): Array type. Defaults to None.
|
||||
export_name (Optional[str], optional): Alias export name.
|
||||
Defaults to None.
|
||||
doc (Optional[str], optional): Documentation string for alias.
|
||||
Defaults to None.
|
||||
"""
|
||||
if doc is None:
|
||||
doc = f"NDArray(shape={shape}, dtype={dtype})"
|
||||
else:
|
||||
doc += f". NDArray(shape={shape}, dtype={dtype})"
|
||||
return cls(ctype_name, AliasRefTypeNode(array_ref_name),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, UnionTypeNode(ctype_name, items),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def optional_(cls, ctype_name: str, item: TypeNode,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, OptionalTypeNode(item), export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def sequence_(cls, ctype_name: str, item: TypeNode,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, SequenceTypeNode(ctype_name, item),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...],
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, TupleTypeNode(ctype_name, items),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def class_(cls, ctype_name: str, class_name: str,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, ASTNodeTypeNode(class_name),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def callable_(cls, ctype_name: str,
|
||||
arg_types: Union[TypeNode, Sequence[TypeNode]],
|
||||
ret_type: TypeNode = NoneTypeNode("void"),
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name,
|
||||
CallableTypeNode(ctype_name, arg_types, ret_type),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def ref_(cls, ctype_name: str, alias_ctype_name: str,
|
||||
alias_export_name: Optional[str] = None,
|
||||
export_name: Optional[str] = None,
|
||||
doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name,
|
||||
AliasRefTypeNode(alias_ctype_name, alias_export_name),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
@classmethod
|
||||
def dict_(cls, ctype_name: str, key_type: TypeNode, value_type: TypeNode,
|
||||
export_name: Optional[str] = None, doc: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()):
|
||||
return cls(ctype_name, DictTypeNode(ctype_name, key_type, value_type),
|
||||
export_name, doc, required_modules)
|
||||
|
||||
|
||||
class ConditionalAliasTypeNode(TypeNode):
|
||||
"""Type node representing an alias protected by condition checked in runtime.
|
||||
For typing-related conditions, prefer using typing.TYPE_CHECKING. For a full explanation, see:
|
||||
https://github.com/opencv/opencv/pull/23927#discussion_r1256326835
|
||||
|
||||
Example:
|
||||
```python
|
||||
if typing.TYPE_CHECKING
|
||||
NumPyArray = numpy.ndarray[typing.Any, numpy.dtype[numpy.generic]]
|
||||
else:
|
||||
NumPyArray = numpy.ndarray
|
||||
```
|
||||
is defined as follows:
|
||||
```python
|
||||
|
||||
ConditionalAliasTypeNode(
|
||||
"NumPyArray",
|
||||
'typing.TYPE_CHECKING',
|
||||
NDArrayTypeNode("NumPyArray"),
|
||||
NDArrayTypeNode("NumPyArray", use_numpy_generics=False),
|
||||
condition_required_imports=("import typing",)
|
||||
)
|
||||
```
|
||||
"""
|
||||
def __init__(self, ctype_name: str, condition: str,
|
||||
positive_branch_type: TypeNode,
|
||||
negative_branch_type: TypeNode,
|
||||
export_name: Optional[str] = None,
|
||||
condition_required_imports: Sequence[str] = ()) -> None:
|
||||
super().__init__(ctype_name)
|
||||
self.condition = condition
|
||||
self.positive_branch_type = positive_branch_type
|
||||
self.positive_branch_type.ctype_name = self.ctype_name
|
||||
self.negative_branch_type = negative_branch_type
|
||||
self.negative_branch_type.ctype_name = self.ctype_name
|
||||
self._export_name = export_name
|
||||
self._condition_required_imports = condition_required_imports
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
if self._export_name is not None:
|
||||
return self._export_name
|
||||
return self.ctype_name
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
return "cv2.typing." + self.typename
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
yield from self.positive_branch_type.required_usage_imports
|
||||
yield from self.negative_branch_type.required_usage_imports
|
||||
yield from self._condition_required_imports
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import cv2.typing"
|
||||
|
||||
@property
|
||||
def required_modules(self) -> Tuple[str, ...]:
|
||||
return (*self.positive_branch_type.required_modules,
|
||||
*self.negative_branch_type.required_modules)
|
||||
|
||||
@property
|
||||
def is_resolved(self) -> bool:
|
||||
return self.positive_branch_type.is_resolved \
|
||||
and self.negative_branch_type.is_resolved
|
||||
|
||||
def resolve(self, root: ASTNode):
|
||||
try:
|
||||
self.positive_branch_type.resolve(root)
|
||||
self.negative_branch_type.resolve(root)
|
||||
except TypeResolutionError as e:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve alias "{}" exposed as "{}"'.format(
|
||||
self.ctype_name, self.typename
|
||||
)
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def numpy_array_(cls, ctype_name: str, export_name: Optional[str] = None,
|
||||
shape: Optional[Tuple[int, ...]] = None,
|
||||
dtype: Optional[str] = None):
|
||||
"""Type subscription is not possible in python 3.8 and older numpy versions."""
|
||||
return cls(
|
||||
ctype_name,
|
||||
"_typing.TYPE_CHECKING",
|
||||
NDArrayTypeNode(ctype_name, shape, dtype),
|
||||
NDArrayTypeNode(ctype_name, shape, dtype,
|
||||
use_numpy_generics=False),
|
||||
condition_required_imports=("import typing as _typing",)
|
||||
)
|
||||
|
||||
|
||||
class NDArrayTypeNode(TypeNode):
|
||||
"""Type node representing NumPy ndarray.
|
||||
"""
|
||||
def __init__(self, ctype_name: str,
|
||||
shape: Optional[Tuple[int, ...]] = None,
|
||||
dtype: Optional[str] = None,
|
||||
use_numpy_generics: bool = True) -> None:
|
||||
super().__init__(ctype_name)
|
||||
self.shape = shape
|
||||
self.dtype = dtype
|
||||
self._use_numpy_generics = use_numpy_generics
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
if self._use_numpy_generics:
|
||||
# NOTE: Shape is not fully supported yet
|
||||
dtype = self.dtype if self.dtype is not None else "numpy.generic"
|
||||
return f"numpy.ndarray[_typing.Any, numpy.dtype[{dtype}]]"
|
||||
return "numpy.ndarray"
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import numpy"
|
||||
# if self.shape is None:
|
||||
yield "import typing as _typing"
|
||||
|
||||
|
||||
class ASTNodeTypeNode(TypeNode):
|
||||
"""Type node representing a lazy ASTNode corresponding to type of
|
||||
function argument or its return type or type of class property.
|
||||
Introduced laziness nature resolves the types visibility issue - all types
|
||||
should be known during function declaration to select an appropriate node
|
||||
from the AST. Such knowledge leads to evaluation of all preprocessor
|
||||
directives (`#include` particularly) for each processed header and might be
|
||||
too expensive and error prone.
|
||||
"""
|
||||
def __init__(self, ctype_name: str, typename: Optional[str] = None,
|
||||
module_name: Optional[str] = None,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, required_modules)
|
||||
self._typename = typename if typename is not None else ctype_name
|
||||
self._module_name = module_name
|
||||
self._ast_node: Optional[weakref.ProxyType[ASTNode]] = None
|
||||
|
||||
@property
|
||||
def ast_node(self):
|
||||
return self._ast_node
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
if self._ast_node is None:
|
||||
return self._typename
|
||||
typename = self._ast_node.export_name
|
||||
if self._ast_node.node_type is not ASTNodeType.Enumeration:
|
||||
return typename
|
||||
# NOTE: Special handling for enums
|
||||
parent = self._ast_node.parent
|
||||
while parent.node_type is ASTNodeType.Class:
|
||||
typename = parent.export_name + "_" + typename
|
||||
parent = parent.parent
|
||||
return typename
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
if self._ast_node is not None:
|
||||
if self._ast_node.node_type is not ASTNodeType.Enumeration:
|
||||
return self._ast_node.full_export_name
|
||||
# NOTE: enumerations are exported to module scope
|
||||
typename = self._ast_node.export_name
|
||||
parent = self._ast_node.parent
|
||||
while parent.node_type is ASTNodeType.Class:
|
||||
typename = parent.export_name + "_" + typename
|
||||
parent = parent.parent
|
||||
return parent.full_export_name + "." + typename
|
||||
if self._module_name is not None:
|
||||
return self._module_name + "." + self._typename
|
||||
return self._typename
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
if self._module_name is None:
|
||||
assert self._ast_node is not None, \
|
||||
"Can't find a module for class '{}' exported as '{}'".format(
|
||||
self.ctype_name, self.typename,
|
||||
)
|
||||
module = self._ast_node.parent
|
||||
while module.node_type is not ASTNodeType.Namespace:
|
||||
module = module.parent
|
||||
yield "import " + module.full_export_name
|
||||
else:
|
||||
yield "import " + self._module_name
|
||||
|
||||
@property
|
||||
def is_resolved(self) -> bool:
|
||||
return self._ast_node is not None or self._module_name is not None
|
||||
|
||||
def resolve(self, root: ASTNode):
|
||||
if self.is_resolved:
|
||||
return
|
||||
|
||||
node = _resolve_symbol(root, self.typename)
|
||||
if node is None:
|
||||
raise TypeResolutionError('Failed to resolve "{}" exposed as "{}"'.format(
|
||||
self.ctype_name, self.typename
|
||||
))
|
||||
self._ast_node = weakref.proxy(node)
|
||||
|
||||
def relative_typename(self, module: str) -> str:
|
||||
assert self._ast_node is not None or self._module_name is not None, \
|
||||
"'{}' exported as '{}' is not resolved yet".format(self.ctype_name,
|
||||
self.typename)
|
||||
if self._module_name is None:
|
||||
type_module = self._ast_node.parent # type: ignore
|
||||
while type_module.node_type is not ASTNodeType.Namespace:
|
||||
type_module = type_module.parent
|
||||
module_name = type_module.full_export_name
|
||||
else:
|
||||
module_name = self._module_name
|
||||
if module_name != module:
|
||||
return self.full_typename
|
||||
return self.full_typename[len(module_name) + 1:]
|
||||
|
||||
|
||||
class AggregatedTypeNode(TypeNode):
|
||||
"""Base type node for type nodes representing an aggregation of another
|
||||
type nodes e.g. tuple, sequence or callable."""
|
||||
def __init__(self, ctype_name: str, items: Sequence[TypeNode],
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, required_modules)
|
||||
self.items = list(items)
|
||||
|
||||
@property
|
||||
def is_resolved(self) -> bool:
|
||||
return all(item.is_resolved for item in self.items)
|
||||
|
||||
@property
|
||||
def required_modules(self) -> Tuple[str, ...]:
|
||||
return (*chain.from_iterable(item.required_modules for item in self.items),
|
||||
*self._required_modules)
|
||||
|
||||
def resolve(self, root: ASTNode) -> None:
|
||||
errors = []
|
||||
for item in filter(lambda item: not item.is_resolved, self):
|
||||
try:
|
||||
item.resolve(root)
|
||||
except TypeResolutionError as e:
|
||||
errors.append(str(e))
|
||||
if len(errors) > 0:
|
||||
raise TypeResolutionError(
|
||||
'Failed to resolve one of "{}" items. Errors: {}'.format(
|
||||
self.full_typename, errors
|
||||
)
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.items)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.items)
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
for item in self:
|
||||
yield from item.required_definition_imports
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
for item in self:
|
||||
yield from item.required_usage_imports
|
||||
|
||||
|
||||
class ContainerTypeNode(AggregatedTypeNode):
|
||||
"""Base type node for all type nodes representing a container type.
|
||||
"""
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return self.type_format.format(self.types_separator.join(
|
||||
item.typename for item in self
|
||||
))
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
return self.type_format.format(self.types_separator.join(
|
||||
item.full_typename for item in self
|
||||
))
|
||||
|
||||
def relative_typename(self, module: str) -> str:
|
||||
return self.type_format.format(self.types_separator.join(
|
||||
item.relative_typename(module) for item in self
|
||||
))
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
yield "import typing as _typing"
|
||||
yield from super().required_definition_imports
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
yield "import typing as _typing"
|
||||
yield from super().required_usage_imports
|
||||
|
||||
@abc.abstractproperty
|
||||
def type_format(self) -> str:
|
||||
return ""
|
||||
|
||||
@abc.abstractproperty
|
||||
def types_separator(self) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
class SequenceTypeNode(ContainerTypeNode):
|
||||
"""Type node representing a homogeneous collection of elements with
|
||||
possible unknown length.
|
||||
"""
|
||||
def __init__(self, ctype_name: str, item: TypeNode,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, (item, ), required_modules)
|
||||
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
return "_typing.Sequence[{}]"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
class TupleTypeNode(ContainerTypeNode):
|
||||
"""Type node representing possibly heterogeneous collection of types with
|
||||
possibly unspecified length.
|
||||
"""
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
return "_typing.Tuple[{}]"
|
||||
return "tuple[{}]"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
class UnionTypeNode(ContainerTypeNode):
|
||||
"""Type node representing type that can be one of the predefined set of types.
|
||||
"""
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
return "_typing.Union[{}]"
|
||||
return "{}"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
return ", "
|
||||
return " | "
|
||||
|
||||
|
||||
class OptionalTypeNode(ContainerTypeNode):
|
||||
"""Type node representing optional type which is effectively is a union
|
||||
of value type node and None.
|
||||
"""
|
||||
def __init__(self, value: TypeNode,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(value.ctype_name, (value,), required_modules)
|
||||
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
return "_typing.Optional[{}]"
|
||||
return "{} | None"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
class DictTypeNode(ContainerTypeNode):
|
||||
"""Type node representing a homogeneous key-value mapping.
|
||||
"""
|
||||
def __init__(self, ctype_name: str, key_type: TypeNode,
|
||||
value_type: TypeNode,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(ctype_name, (key_type, value_type), required_modules)
|
||||
|
||||
@property
|
||||
def key_type(self) -> TypeNode:
|
||||
return self.items[0]
|
||||
|
||||
@property
|
||||
def value_type(self) -> TypeNode:
|
||||
return self.items[1]
|
||||
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
if TypeNode.compatible_to_runtime_usage:
|
||||
return "_typing.Dict[{}]"
|
||||
return "dict[{}]"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
class CallableTypeNode(AggregatedTypeNode):
|
||||
"""Type node representing a callable type (most probably a function).
|
||||
|
||||
```python
|
||||
CallableTypeNode(
|
||||
'image_reading_callback',
|
||||
arg_types=(ASTNodeTypeNode('Image'), PrimitiveTypeNode.float_())
|
||||
)
|
||||
```
|
||||
defines a callable type node representing a function with the same
|
||||
interface as the following
|
||||
```python
|
||||
def image_reading_callback(image: Image, timestamp: float) -> None: ...
|
||||
```
|
||||
"""
|
||||
def __init__(self, ctype_name: str,
|
||||
arg_types: Union[TypeNode, Sequence[TypeNode]],
|
||||
ret_type: TypeNode = NoneTypeNode("void"),
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
if isinstance(arg_types, TypeNode):
|
||||
super().__init__(ctype_name, (arg_types, ret_type), required_modules)
|
||||
else:
|
||||
super().__init__(ctype_name, (*arg_types, ret_type), required_modules)
|
||||
|
||||
@property
|
||||
def arg_types(self) -> Sequence[TypeNode]:
|
||||
return self.items[:-1]
|
||||
|
||||
@property
|
||||
def ret_type(self) -> TypeNode:
|
||||
return self.items[-1]
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return '_typing.Callable[[{}], {}]'.format(
|
||||
', '.join(arg.typename for arg in self.arg_types),
|
||||
self.ret_type.typename
|
||||
)
|
||||
|
||||
@property
|
||||
def full_typename(self) -> str:
|
||||
return '_typing.Callable[[{}], {}]'.format(
|
||||
', '.join(arg.full_typename for arg in self.arg_types),
|
||||
self.ret_type.full_typename
|
||||
)
|
||||
|
||||
def relative_typename(self, module: str) -> str:
|
||||
return '_typing.Callable[[{}], {}]'.format(
|
||||
', '.join(arg.relative_typename(module) for arg in self.arg_types),
|
||||
self.ret_type.relative_typename(module)
|
||||
)
|
||||
|
||||
@property
|
||||
def required_definition_imports(self) -> Generator[str, None, None]:
|
||||
yield "import typing as _typing"
|
||||
yield from super().required_definition_imports
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import typing as _typing"
|
||||
yield from super().required_usage_imports
|
||||
|
||||
|
||||
class ClassTypeNode(ContainerTypeNode):
|
||||
"""Type node representing types themselves (refer to typing.Type)
|
||||
"""
|
||||
def __init__(self, value: TypeNode,
|
||||
required_modules: Tuple[str, ...] = ()) -> None:
|
||||
super().__init__(value.ctype_name, (value,), required_modules)
|
||||
|
||||
@property
|
||||
def type_format(self) -> str:
|
||||
return "_typing.Type[{}]"
|
||||
|
||||
@property
|
||||
def types_separator(self) -> str:
|
||||
return ", "
|
||||
|
||||
|
||||
class PathLikeTypeNode(TypeNode):
|
||||
"""Type node representing a PathLike object.
|
||||
"""
|
||||
def __init__(self, ctype_name: str) -> None:
|
||||
super().__init__(ctype_name)
|
||||
|
||||
@property
|
||||
def typename(self) -> str:
|
||||
return "os.PathLike[str]"
|
||||
|
||||
@property
|
||||
def required_usage_imports(self) -> Generator[str, None, None]:
|
||||
yield "import os"
|
||||
|
||||
@staticmethod
|
||||
def string_or_pathlike_(ctype_name: str = "string") -> UnionTypeNode:
|
||||
return UnionTypeNode(
|
||||
ctype_name,
|
||||
items=(
|
||||
PrimitiveTypeNode.str_(ctype_name),
|
||||
PathLikeTypeNode(ctype_name)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _resolve_symbol(root: Optional[ASTNode], full_symbol_name: str) -> Optional[ASTNode]:
|
||||
"""Searches for a symbol with the given full export name in the AST
|
||||
starting from the `root`.
|
||||
|
||||
Args:
|
||||
root (Optional[ASTNode]): Root of the examining AST.
|
||||
full_symbol_name (str): Full export name of the symbol to find. Path
|
||||
components can be divided by '.' or '_'.
|
||||
|
||||
Returns:
|
||||
Optional[ASTNode]: ASTNode with full export name equal to
|
||||
`full_symbol_name`, None otherwise.
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> cls = root.add_class('Algorithm').add_class('Params')
|
||||
>>> _resolve_symbol(root, 'cv.Algorithm.Params') == cls
|
||||
True
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> enum = root.add_namespace('detail').add_enumeration('AlgorithmType')
|
||||
>>> _resolve_symbol(root, 'cv_detail_AlgorithmType') == enum
|
||||
True
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> _resolve_symbol(root, 'cv.detail.Algorithm')
|
||||
None
|
||||
|
||||
>>> root = NamespaceNode('cv')
|
||||
>>> enum = root.add_namespace('detail').add_enumeration('AlgorithmType')
|
||||
>>> _resolve_symbol(root, 'AlgorithmType')
|
||||
None
|
||||
"""
|
||||
def search_down_symbol(scope: Optional[ASTNode],
|
||||
scope_sep: str) -> Optional[ASTNode]:
|
||||
parts = full_symbol_name.split(scope_sep, maxsplit=1)
|
||||
while len(parts) == 2:
|
||||
# Try to find narrow scope
|
||||
scope = _resolve_symbol(scope, parts[0])
|
||||
if scope is None:
|
||||
return None
|
||||
# and resolve symbol in it
|
||||
node = _resolve_symbol(scope, parts[1])
|
||||
if node is not None:
|
||||
return node
|
||||
# symbol is not found, but narrowed scope is valid - diving further
|
||||
parts = parts[1].split(scope_sep, maxsplit=1)
|
||||
return None
|
||||
|
||||
assert root is not None, \
|
||||
"Can't resolve symbol '{}' from NONE root".format(full_symbol_name)
|
||||
# Looking for exact symbol match
|
||||
for attr in filter(lambda attr: hasattr(root, attr),
|
||||
("namespaces", "classes", "enumerations")):
|
||||
nodes_dict = getattr(root, attr)
|
||||
node = nodes_dict.get(full_symbol_name, None)
|
||||
if node is not None:
|
||||
return node
|
||||
# Symbol is not found, looking for more fine-grained scope if possible
|
||||
for scope_sep in ("_", "."):
|
||||
node = search_down_symbol(root, scope_sep)
|
||||
if node is not None:
|
||||
return node
|
||||
return None
|
||||
@@ -0,0 +1,273 @@
|
||||
from .nodes.type_node import (
|
||||
AliasTypeNode, AliasRefTypeNode, PrimitiveTypeNode,
|
||||
ASTNodeTypeNode, NDArrayTypeNode, NoneTypeNode, SequenceTypeNode,
|
||||
TupleTypeNode, UnionTypeNode, AnyTypeNode, ConditionalAliasTypeNode
|
||||
)
|
||||
|
||||
# Set of predefined types used to cover cases when library doesn't
|
||||
# directly exports a type and equivalent one should be used instead.
|
||||
# Example: Instead of C++ `cv::Rect(1, 1, 5, 6)` in Python any sequence type
|
||||
# with length 4 can be used: tuple `(1, 1, 5, 6)` or list `[1, 1, 5, 6]`.
|
||||
# Predefined type might be:
|
||||
# - alias - defines a Python synonym for a native type name.
|
||||
# Example: `cv::Rect` and `cv::Size` are both `Sequence[int]` in Python, but
|
||||
# with different length constraints (4 and 2 accordingly).
|
||||
# - direct substitution - just a plain type replacement without any credits to
|
||||
# native type. Example:
|
||||
# * `std::vector<uchar>` is `np.ndarray` with `dtype == np.uint8` in Python
|
||||
# * `double` is a Python `float`
|
||||
# * `std::string` is a Python `str`
|
||||
_PREDEFINED_TYPES = (
|
||||
PrimitiveTypeNode.int_("int"),
|
||||
PrimitiveTypeNode.int_("uchar"),
|
||||
PrimitiveTypeNode.int_("unsigned"),
|
||||
PrimitiveTypeNode.int_("int64"),
|
||||
PrimitiveTypeNode.int_("uint8_t"),
|
||||
PrimitiveTypeNode.int_("int8_t"),
|
||||
PrimitiveTypeNode.int_("int32_t"),
|
||||
PrimitiveTypeNode.int_("uint32_t"),
|
||||
PrimitiveTypeNode.int_("size_t"),
|
||||
PrimitiveTypeNode.int_("int64_t"),
|
||||
PrimitiveTypeNode.int_("long long"),
|
||||
PrimitiveTypeNode.float_("float"),
|
||||
PrimitiveTypeNode.float_("double"),
|
||||
PrimitiveTypeNode.bool_("bool"),
|
||||
PrimitiveTypeNode.str_("string"),
|
||||
PrimitiveTypeNode.str_("char"),
|
||||
PrimitiveTypeNode.str_("String"),
|
||||
PrimitiveTypeNode.str_("c_string"),
|
||||
ConditionalAliasTypeNode.numpy_array_(
|
||||
"NumPyArrayNumeric",
|
||||
dtype="numpy.integer[_typing.Any] | numpy.floating[_typing.Any]"
|
||||
),
|
||||
ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat32", dtype="numpy.float32"),
|
||||
ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat64", dtype="numpy.float64"),
|
||||
NoneTypeNode("void"),
|
||||
AliasTypeNode.int_("void*", "IntPointer", "Represents an arbitrary pointer"),
|
||||
AliasTypeNode.union_(
|
||||
"Mat",
|
||||
items=(ASTNodeTypeNode("Mat", module_name="cv2.mat_wrapper"),
|
||||
AliasRefTypeNode("NumPyArrayNumeric")),
|
||||
export_name="MatLike"
|
||||
),
|
||||
AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()),
|
||||
AliasTypeNode.sequence_("Size", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Size2f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.union_(
|
||||
"Scalar",
|
||||
items=(SequenceTypeNode("Scalar", PrimitiveTypeNode.float_()),
|
||||
PrimitiveTypeNode.float_()),
|
||||
doc="Max sequence length is at most 4"
|
||||
),
|
||||
AliasTypeNode.sequence_("Point", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.ref_("Point2i", "Point"),
|
||||
AliasTypeNode.sequence_("Point2f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Point2d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Point3i", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Point3f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Point3d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Range", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Rect", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Rect2i", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Rect2f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Rect2d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.dict_("Moments", PrimitiveTypeNode.str_("Moments::key"),
|
||||
PrimitiveTypeNode.float_("Moments::value")),
|
||||
AliasTypeNode.tuple_("RotatedRect",
|
||||
items=(AliasRefTypeNode("Point2f"),
|
||||
AliasRefTypeNode("Size2f"),
|
||||
PrimitiveTypeNode.float_()),
|
||||
doc="Any type providing sequence protocol is supported"),
|
||||
AliasTypeNode.tuple_("TermCriteria",
|
||||
items=(
|
||||
ASTNodeTypeNode("TermCriteria.Type"),
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.float_()),
|
||||
doc="Any type providing sequence protocol is supported"),
|
||||
AliasTypeNode.sequence_("Vec2i", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Vec2f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Vec2d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 2"),
|
||||
AliasTypeNode.sequence_("Vec3i", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Vec3f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Vec3d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 3"),
|
||||
AliasTypeNode.sequence_("Vec4i", PrimitiveTypeNode.int_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Vec4f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Vec4d", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 4"),
|
||||
AliasTypeNode.sequence_("Vec6f", PrimitiveTypeNode.float_(),
|
||||
doc="Required length is 6"),
|
||||
AliasTypeNode.class_("FeatureDetector", "Feature2D",
|
||||
export_name="FeatureDetector"),
|
||||
AliasTypeNode.class_("DescriptorExtractor", "Feature2D",
|
||||
export_name="DescriptorExtractor"),
|
||||
AliasTypeNode.class_("FeatureExtractor", "Feature2D",
|
||||
export_name="FeatureExtractor"),
|
||||
AliasTypeNode.array_ref_("Matx33f",
|
||||
array_ref_name="NumPyArrayFloat32",
|
||||
shape=(3, 3),
|
||||
dtype="numpy.float32"),
|
||||
AliasTypeNode.array_ref_("Matx33d",
|
||||
array_ref_name="NumPyArrayFloat64",
|
||||
shape=(3, 3),
|
||||
dtype="numpy.float64"),
|
||||
AliasTypeNode.array_ref_("Matx44f",
|
||||
array_ref_name="NumPyArrayFloat32",
|
||||
shape=(4, 4),
|
||||
dtype="numpy.float32"),
|
||||
AliasTypeNode.array_ref_("Matx44d",
|
||||
array_ref_name="NumPyArrayFloat64",
|
||||
shape=(4, 4),
|
||||
dtype="numpy.float64"),
|
||||
NDArrayTypeNode("vector<uchar>", dtype="numpy.uint8"),
|
||||
NDArrayTypeNode("vector_uchar", dtype="numpy.uint8"),
|
||||
|
||||
# DNN, optional
|
||||
AliasTypeNode.class_("LayerId", "DictValue", required_modules=("dnn",)),
|
||||
AliasTypeNode.dict_("LayerParams",
|
||||
key_type=PrimitiveTypeNode.str_(),
|
||||
value_type=UnionTypeNode("DictValue", items=(
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.float_(),
|
||||
PrimitiveTypeNode.str_())
|
||||
),
|
||||
required_modules=("dnn",)),
|
||||
|
||||
# Flann, optional
|
||||
PrimitiveTypeNode.int_("cvflann_flann_distance_t", required_modules=("flann",)),
|
||||
PrimitiveTypeNode.int_("flann_flann_distance_t", required_modules=("flann",)),
|
||||
PrimitiveTypeNode.int_("cvflann_flann_algorithm_t", required_modules=("flann",)),
|
||||
PrimitiveTypeNode.int_("flann_flann_algorithm_t", required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("flann_IndexParams",
|
||||
key_type=PrimitiveTypeNode.str_(),
|
||||
value_type=UnionTypeNode("flann_IndexParams::value", items=(
|
||||
PrimitiveTypeNode.bool_(),
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.float_(),
|
||||
PrimitiveTypeNode.str_())
|
||||
),
|
||||
export_name="IndexParams",
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("flann_SearchParams",
|
||||
key_type=PrimitiveTypeNode.str_(),
|
||||
value_type=UnionTypeNode("flann_IndexParams::value", items=(
|
||||
PrimitiveTypeNode.bool_(),
|
||||
PrimitiveTypeNode.int_(),
|
||||
PrimitiveTypeNode.float_(),
|
||||
PrimitiveTypeNode.str_())
|
||||
),
|
||||
export_name="SearchParams",
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("map_string_and_string",
|
||||
PrimitiveTypeNode.str_("map_string_and_string::key"),
|
||||
PrimitiveTypeNode.str_("map_string_and_string::value"),
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("map_string_and_int",
|
||||
PrimitiveTypeNode.str_("map_string_and_int::key"),
|
||||
PrimitiveTypeNode.int_("map_string_and_int::value"),
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("map_string_and_vector_size_t",
|
||||
PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"),
|
||||
SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t")),
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("map_string_and_vector_float",
|
||||
PrimitiveTypeNode.str_("map_string_and_vector_float::key"),
|
||||
SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_()),
|
||||
required_modules=("flann",)),
|
||||
AliasTypeNode.dict_("map_int_and_double",
|
||||
PrimitiveTypeNode.int_("map_int_and_double::key"),
|
||||
PrimitiveTypeNode.float_("map_int_and_double::value"),
|
||||
required_modules=("flann",)),
|
||||
|
||||
# G-API from opencv_contrib
|
||||
AliasTypeNode.union_("GProtoArg",
|
||||
items=(AliasRefTypeNode("Scalar"),
|
||||
ASTNodeTypeNode("GMat"),
|
||||
ASTNodeTypeNode("GOpaqueT"),
|
||||
ASTNodeTypeNode("GArrayT")),
|
||||
required_modules=("gapi",)),
|
||||
SequenceTypeNode("GProtoArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
|
||||
AliasTypeNode.sequence_("GProtoInputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
|
||||
AliasTypeNode.sequence_("GProtoOutputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)),
|
||||
AliasTypeNode.union_(
|
||||
"GRunArg",
|
||||
items=(AliasRefTypeNode("Mat", "MatLike"),
|
||||
AliasRefTypeNode("Scalar"),
|
||||
ASTNodeTypeNode("GOpaqueT"),
|
||||
ASTNodeTypeNode("GArrayT"),
|
||||
SequenceTypeNode("GRunArg", AnyTypeNode("GRunArg")),
|
||||
NoneTypeNode("GRunArg")),
|
||||
required_modules=("gapi",)
|
||||
),
|
||||
AliasTypeNode.optional_("GOptRunArg", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)),
|
||||
AliasTypeNode.union_("GMetaArg",
|
||||
items=(ASTNodeTypeNode("GMat"),
|
||||
AliasRefTypeNode("Scalar"),
|
||||
ASTNodeTypeNode("GOpaqueT"),
|
||||
ASTNodeTypeNode("GArrayT")),
|
||||
required_modules=("gapi",)),
|
||||
AliasTypeNode.union_("Prim",
|
||||
items=(ASTNodeTypeNode("gapi.wip.draw.Text"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Circle"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Image"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Line"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Rect"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Mosaic"),
|
||||
ASTNodeTypeNode("gapi.wip.draw.Poly")),
|
||||
required_modules=("gapi",)),
|
||||
SequenceTypeNode("Prims", AliasRefTypeNode("Prim"), required_modules=("gapi",)),
|
||||
TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"),
|
||||
ASTNodeTypeNode("GMat")), required_modules=("gapi",)),
|
||||
ASTNodeTypeNode("GOpaque", "GOpaqueT", required_modules=("gapi",)),
|
||||
ASTNodeTypeNode("GArray", "GArrayT", required_modules=("gapi",)),
|
||||
AliasTypeNode.union_("GTypeInfo",
|
||||
items=(ASTNodeTypeNode("GMat"),
|
||||
AliasRefTypeNode("Scalar"),
|
||||
ASTNodeTypeNode("GOpaqueT"),
|
||||
ASTNodeTypeNode("GArrayT")),
|
||||
required_modules=("gapi",)),
|
||||
SequenceTypeNode("GCompileArgs", ASTNodeTypeNode("GCompileArg"), required_modules=("gapi",)),
|
||||
SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo"), required_modules=("gapi",)),
|
||||
SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)),
|
||||
SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg"), required_modules=("gapi",)),
|
||||
SequenceTypeNode("GOptRunArgs", AliasRefTypeNode("GOptRunArg"), required_modules=("gapi",)),
|
||||
AliasTypeNode.callable_(
|
||||
"detail_ExtractArgsCallback",
|
||||
arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")),
|
||||
ret_type=SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg")),
|
||||
export_name="ExtractArgsCallback",
|
||||
required_modules=("gapi",)
|
||||
),
|
||||
AliasTypeNode.callable_(
|
||||
"detail_ExtractMetaCallback",
|
||||
arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")),
|
||||
ret_type=SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg")),
|
||||
export_name="ExtractMetaCallback",
|
||||
required_modules=("gapi",)
|
||||
),
|
||||
PrimitiveTypeNode("NativeByteArray", "bytes"),
|
||||
)
|
||||
|
||||
PREDEFINED_TYPES = dict(
|
||||
zip((t.ctype_name for t in _PREDEFINED_TYPES), _PREDEFINED_TYPES)
|
||||
)
|
||||
@@ -0,0 +1,333 @@
|
||||
from typing import Tuple, List, Optional
|
||||
|
||||
from .predefined_types import PREDEFINED_TYPES
|
||||
from .nodes.type_node import (
|
||||
TypeNode, UnionTypeNode, SequenceTypeNode, ASTNodeTypeNode, TupleTypeNode
|
||||
)
|
||||
|
||||
|
||||
def replace_template_parameters_with_placeholders(string: str) \
|
||||
-> Tuple[str, Tuple[str, ...]]:
|
||||
"""Replaces template parameters with `format` placeholders for all template
|
||||
instantiations in provided string.
|
||||
Only outermost template parameters are replaced.
|
||||
|
||||
Args:
|
||||
string (str): input string containing C++ template instantiations
|
||||
|
||||
Returns:
|
||||
tuple[str, tuple[str, ...]]: string with '{}' placeholders template
|
||||
instead of instantiation types and a tuple of extracted types.
|
||||
|
||||
>>> template_string, args = replace_template_parameters_with_placeholders(
|
||||
... "std::vector<cv::Point<int>>, test<int>"
|
||||
... )
|
||||
>>> template_string.format(*args) == "std::vector<cv::Point<int>>, test<int>"
|
||||
True
|
||||
|
||||
>>> replace_template_parameters_with_placeholders(
|
||||
... "cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>"
|
||||
... )
|
||||
('cv::util::variant<{}>', ('cv::GRunArgs, cv::GOptRunArgs',))
|
||||
|
||||
>>> replace_template_parameters_with_placeholders("vector<Point<int>>")
|
||||
('vector<{}>', ('Point<int>',))
|
||||
|
||||
>>> replace_template_parameters_with_placeholders(
|
||||
... "vector<Point<int>>, vector<float>"
|
||||
... )
|
||||
('vector<{}>, vector<{}>', ('Point<int>', 'float'))
|
||||
|
||||
>>> replace_template_parameters_with_placeholders("string without templates")
|
||||
('string without templates', ())
|
||||
"""
|
||||
|
||||
template_brackets_indices = []
|
||||
template_instantiations_count = 0
|
||||
template_start_index = 0
|
||||
for i, c in enumerate(string):
|
||||
if c == "<":
|
||||
template_instantiations_count += 1
|
||||
if template_instantiations_count == 1:
|
||||
# + 1 - because left bound is included in substring range
|
||||
template_start_index = i + 1
|
||||
elif c == ">":
|
||||
template_instantiations_count -= 1
|
||||
assert template_instantiations_count >= 0, \
|
||||
"Provided string is ill-formed. There are more '>' than '<'."
|
||||
if template_instantiations_count == 0:
|
||||
template_brackets_indices.append((template_start_index, i))
|
||||
assert template_instantiations_count == 0, \
|
||||
"Provided string is ill-formed. There are more '<' than '>'."
|
||||
template_args: List[str] = []
|
||||
# Reversed loop is required to preserve template start/end indices
|
||||
for i, j in reversed(template_brackets_indices):
|
||||
template_args.insert(0, string[i:j])
|
||||
string = string[:i] + "{}" + string[j:]
|
||||
return string, tuple(template_args)
|
||||
|
||||
|
||||
def get_template_instantiation_type(typename: str) -> str:
|
||||
"""Extracts outermost template instantiation type from provided string
|
||||
|
||||
Args:
|
||||
typename (str): String containing C++ template instantiation.
|
||||
|
||||
Returns:
|
||||
str: String containing template instantiation type
|
||||
|
||||
>>> get_template_instantiation_type("std::vector<cv::Point<int>>")
|
||||
'cv::Point<int>'
|
||||
>>> get_template_instantiation_type("std::vector<uchar>")
|
||||
'uchar'
|
||||
>>> get_template_instantiation_type("std::map<int, float>")
|
||||
'int, float'
|
||||
>>> get_template_instantiation_type("uchar")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: typename ('uchar') doesn't contain template instantiations
|
||||
>>> get_template_instantiation_type("std::vector<int>, std::vector<float>")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: typename ('std::vector<int>, std::vector<float>') contains more than 1 template instantiation
|
||||
"""
|
||||
|
||||
_, args = replace_template_parameters_with_placeholders(typename)
|
||||
if len(args) == 0:
|
||||
raise ValueError(
|
||||
"typename ('{}') doesn't contain template instantiations".format(typename)
|
||||
)
|
||||
if len(args) > 1:
|
||||
raise ValueError(
|
||||
"typename ('{}') contains more than 1 template instantiation".format(typename)
|
||||
)
|
||||
return args[0]
|
||||
|
||||
|
||||
def normalize_ctype_name(typename: str) -> str:
|
||||
"""Normalizes C++ name by removing unnecessary namespace prefixes and possible
|
||||
pointer/reference qualification. '::' are replaced with '_'.
|
||||
|
||||
NOTE: Pointer decay for 'void*' is not performed.
|
||||
|
||||
Args:
|
||||
typename (str): Name of the C++ type for normalization
|
||||
|
||||
Returns:
|
||||
str: Normalized C++ type name.
|
||||
|
||||
>>> normalize_ctype_name('std::vector<cv::Point2f>&')
|
||||
'vector<cv_Point2f>'
|
||||
>>> normalize_ctype_name('AKAZE::DescriptorType')
|
||||
'AKAZE_DescriptorType'
|
||||
>>> normalize_ctype_name('std::vector<Mat>')
|
||||
'vector<Mat>'
|
||||
>>> normalize_ctype_name('std::string')
|
||||
'string'
|
||||
>>> normalize_ctype_name('void*') # keep void* as is - special case
|
||||
'void*'
|
||||
>>> normalize_ctype_name('Ptr<AKAZE>')
|
||||
'AKAZE'
|
||||
>>> normalize_ctype_name('Algorithm_Ptr')
|
||||
'Algorithm'
|
||||
"""
|
||||
for prefix_to_remove in ("cv", "std"):
|
||||
if typename.startswith(prefix_to_remove):
|
||||
typename = typename[len(prefix_to_remove):]
|
||||
typename = typename.replace("::", "_").lstrip("_")
|
||||
if typename.endswith('&'):
|
||||
typename = typename[:-1]
|
||||
typename = typename.strip()
|
||||
|
||||
if typename == 'void*':
|
||||
return typename
|
||||
|
||||
if is_pointer_type(typename):
|
||||
# Case for "type*", "type_Ptr", "typePtr"
|
||||
for suffix in ("*", "_Ptr", "Ptr"):
|
||||
if typename.endswith(suffix):
|
||||
return typename[:-len(suffix)]
|
||||
# Case Ptr<Type>
|
||||
if _is_template_instantiation(typename):
|
||||
return normalize_ctype_name(
|
||||
get_template_instantiation_type(typename)
|
||||
)
|
||||
# Case Ptr_Type
|
||||
return typename.split("_", maxsplit=1)[-1]
|
||||
|
||||
# special normalization for several G-API Types
|
||||
if typename.startswith("GArray_") or typename.startswith("GArray<"):
|
||||
return "GArrayT"
|
||||
if typename.startswith("GOpaque_") or typename.startswith("GOpaque<"):
|
||||
return "GOpaqueT"
|
||||
if typename == "GStreamerPipeline" or typename.startswith("GStreamerSource"):
|
||||
return "gst_" + typename
|
||||
|
||||
return typename
|
||||
|
||||
|
||||
def is_tuple_type(typename: str) -> bool:
|
||||
return typename.startswith("tuple") or typename.startswith("pair")
|
||||
|
||||
|
||||
def is_sequence_type(typename: str) -> bool:
|
||||
return typename.startswith("vector")
|
||||
|
||||
|
||||
def is_pointer_type(typename: str) -> bool:
|
||||
return typename.endswith("Ptr") or typename.endswith("*") \
|
||||
or typename.startswith("Ptr")
|
||||
|
||||
|
||||
def is_union_type(typename: str) -> bool:
|
||||
return typename.startswith('util_variant')
|
||||
|
||||
|
||||
def _is_template_instantiation(typename: str) -> bool:
|
||||
"""Fast, but unreliable check whenever provided typename is a template
|
||||
instantiation.
|
||||
|
||||
Args:
|
||||
typename (str): typename to check against template instantiation.
|
||||
|
||||
Returns:
|
||||
bool: True if provided `typename` contains template instantiation,
|
||||
False otherwise
|
||||
"""
|
||||
|
||||
if "<" in typename:
|
||||
assert ">" in typename, \
|
||||
"Wrong template class instantiation: {}. '>' is missing".format(typename)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_type_nodes_from_template_arguments(template_args_str: str) \
|
||||
-> List[TypeNode]:
|
||||
"""Creates a list of type nodes corresponding to the argument types
|
||||
used for template instantiation.
|
||||
This method correctly addresses the situation when arguments of the input
|
||||
template are also templates.
|
||||
Example:
|
||||
if `create_type_node` is called with
|
||||
`std::tuple<std::variant<int, Point2i>, int, std::vector<int>>`
|
||||
this function will be called with
|
||||
`std::variant<int, Point<int>>, int, std::vector<int>`
|
||||
that produces the following order of types resolution
|
||||
`std::variant` ~ `Union`
|
||||
`std::variant<int, Point2i>` -> `int` ~ `int` -> `Union[int, Point2i]`
|
||||
`Point2i` ~ `Point2i`
|
||||
`int` -> `int`
|
||||
`std::vector<int>` -> `std::vector` ~ `Sequence` -> `Sequence[int]`
|
||||
`int` ~ `int`
|
||||
|
||||
Returns:
|
||||
List[TypeNode]: set of type nodes used for template instantiation.
|
||||
List is empty if input string doesn't contain template instantiation.
|
||||
"""
|
||||
|
||||
type_nodes = []
|
||||
template_args_str, templated_args_types = replace_template_parameters_with_placeholders(
|
||||
template_args_str
|
||||
)
|
||||
template_index = 0
|
||||
# For each template argument
|
||||
for template_arg in template_args_str.split(","):
|
||||
template_arg = template_arg.strip()
|
||||
# Check if argument requires type substitution
|
||||
if _is_template_instantiation(template_arg):
|
||||
# Reconstruct the original type
|
||||
template_arg = template_arg.format(templated_args_types[template_index])
|
||||
template_index += 1
|
||||
# create corresponding type node
|
||||
type_nodes.append(create_type_node(template_arg))
|
||||
return type_nodes
|
||||
|
||||
|
||||
def create_type_node(typename: str,
|
||||
original_ctype_name: Optional[str] = None) -> TypeNode:
|
||||
"""Converts C++ type name to appropriate type used in Python library API.
|
||||
|
||||
Conversion procedure:
|
||||
1. Normalize typename: remove redundant prefixes, unify name
|
||||
components delimiters, remove reference qualifications.
|
||||
2. Check whenever typename has a known predefined conversion or exported
|
||||
as alias e.g.
|
||||
- C++ `double` -> Python `float`
|
||||
- C++ `cv::Rect` -> Python `Sequence[int]`
|
||||
- C++ `std::vector<char>` -> Python `np.ndarray`
|
||||
return TypeNode corresponding to the appropriate type.
|
||||
3. Check whenever typename is a container of types e.g. variant,
|
||||
sequence or tuple. If so, select appropriate Python container type
|
||||
and perform arguments conversion.
|
||||
4. Create a type node corresponding to the AST node passing normalized
|
||||
typename as its name.
|
||||
|
||||
Args:
|
||||
typename (str): C++ type name to convert.
|
||||
original_ctype_name (Optional[str]): Original C++ name of the type.
|
||||
`original_ctype_name` == `typename` if provided argument is None.
|
||||
Default is None.
|
||||
|
||||
Returns:
|
||||
TypeNode: type node that wraps C++ type exposed to Python
|
||||
|
||||
>>> create_type_node('Ptr<AKAZE>').typename
|
||||
'AKAZE'
|
||||
>>> create_type_node('std::vector<Ptr<cv::Algorithm>>').typename
|
||||
'typing.Sequence[Algorithm]'
|
||||
"""
|
||||
|
||||
if original_ctype_name is None:
|
||||
original_ctype_name = typename
|
||||
|
||||
typename = normalize_ctype_name(typename.strip())
|
||||
|
||||
# if typename is a known alias or has explicitly defined substitution
|
||||
type_node = PREDEFINED_TYPES.get(typename)
|
||||
if type_node is not None:
|
||||
type_node.ctype_name = original_ctype_name
|
||||
return type_node
|
||||
|
||||
# If typename is a known exported alias name (e.g. IndexParams or SearchParams)
|
||||
for alias in PREDEFINED_TYPES.values():
|
||||
if alias.typename == typename:
|
||||
return alias
|
||||
|
||||
if is_union_type(typename):
|
||||
union_types = get_template_instantiation_type(typename)
|
||||
return UnionTypeNode(
|
||||
original_ctype_name,
|
||||
items=create_type_nodes_from_template_arguments(union_types)
|
||||
)
|
||||
|
||||
# if typename refers to a sequence type e.g. vector<int>
|
||||
if is_sequence_type(typename):
|
||||
# Recursively convert sequence element type
|
||||
if _is_template_instantiation(typename):
|
||||
inner_sequence_type = create_type_node(
|
||||
get_template_instantiation_type(typename)
|
||||
)
|
||||
else:
|
||||
# Handle vector_Type cases
|
||||
# maxsplit=1 is required to handle sequence of sequence e.g:
|
||||
# vector_vector_Mat -> Sequence[Sequence[Mat]]
|
||||
inner_sequence_type = create_type_node(typename.split("_", 1)[-1])
|
||||
return SequenceTypeNode(original_ctype_name, inner_sequence_type)
|
||||
|
||||
# If typename refers to a heterogeneous container
|
||||
# (can contain elements of different types)
|
||||
if is_tuple_type(typename):
|
||||
tuple_types = get_template_instantiation_type(typename)
|
||||
return TupleTypeNode(
|
||||
original_ctype_name,
|
||||
items=create_type_nodes_from_template_arguments(tuple_types)
|
||||
)
|
||||
# If everything else is False, it means that input typename refers to a
|
||||
# class or enum of the library.
|
||||
return ASTNodeTypeNode(original_ctype_name, typename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,180 @@
|
||||
"""Contains a class used to resolve compatibility issues with old Python versions.
|
||||
|
||||
Typing stubs generation is available starting from Python 3.6 only.
|
||||
For other versions all calls to functions are noop.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
from contextlib import contextmanager
|
||||
|
||||
from typing import Dict, Set, Any, Sequence, Generator, Union
|
||||
import traceback
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from typing_stubs_generation import (
|
||||
generate_typing_stubs,
|
||||
NamespaceNode,
|
||||
EnumerationNode,
|
||||
SymbolName,
|
||||
ClassNode,
|
||||
create_function_node,
|
||||
create_class_node,
|
||||
find_class_node,
|
||||
resolve_enum_scopes
|
||||
)
|
||||
|
||||
import functools
|
||||
|
||||
class FailuresWrapper:
|
||||
def __init__(self, exceptions_as_warnings=True):
|
||||
self.has_failure = False
|
||||
self.exceptions_as_warnings = exceptions_as_warnings
|
||||
|
||||
def wrap_exceptions_as_warnings(self, original_func=None,
|
||||
ret_type_on_failure=None):
|
||||
def parametrized_wrapper(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped_func(*args, **kwargs):
|
||||
if self.has_failure:
|
||||
if ret_type_on_failure is None:
|
||||
return None
|
||||
return ret_type_on_failure()
|
||||
|
||||
try:
|
||||
ret_type = func(*args, **kwargs)
|
||||
except Exception:
|
||||
self.has_failure = True
|
||||
warnings.warn(
|
||||
"Typing stubs generation has failed.\n{}".format(
|
||||
traceback.format_exc()
|
||||
)
|
||||
)
|
||||
if ret_type_on_failure is None:
|
||||
return None
|
||||
return ret_type_on_failure()
|
||||
return ret_type
|
||||
|
||||
if self.exceptions_as_warnings:
|
||||
return wrapped_func
|
||||
else:
|
||||
return original_func
|
||||
|
||||
if original_func:
|
||||
return parametrized_wrapper(original_func)
|
||||
return parametrized_wrapper
|
||||
|
||||
@contextmanager
|
||||
def delete_on_failure(self, file_path):
|
||||
# type: (Path) -> Generator[None, None, None]
|
||||
# There is no errors during stubs generation and file doesn't exist
|
||||
if not self.has_failure and not file_path.is_file():
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.touch()
|
||||
try:
|
||||
# continue execution
|
||||
yield
|
||||
finally:
|
||||
# If failure is occurred - delete file if exists
|
||||
if self.has_failure and file_path.is_file():
|
||||
file_path.unlink()
|
||||
|
||||
failures_wrapper = FailuresWrapper(exceptions_as_warnings=True)
|
||||
|
||||
class ClassNodeStub:
|
||||
def add_base(self, base_node):
|
||||
pass
|
||||
|
||||
class TypingStubsGenerator:
|
||||
def __init__(self):
|
||||
self.cv_root = NamespaceNode("cv", export_name="cv2")
|
||||
self.exported_enums = {} # type: Dict[SymbolName, EnumerationNode]
|
||||
self.type_hints_ignored_functions = set() # type: Set[str]
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings
|
||||
def add_enum(self, symbol_name, is_scoped_enum, entries):
|
||||
# type: (SymbolName, bool, Dict[str, str]) -> None
|
||||
if symbol_name in self.exported_enums:
|
||||
assert symbol_name.name == "<unnamed>", \
|
||||
"Trying to export 2 enums with same symbol " \
|
||||
"name: {}".format(symbol_name)
|
||||
enumeration_node = self.exported_enums[symbol_name]
|
||||
else:
|
||||
enumeration_node = EnumerationNode(symbol_name.name,
|
||||
is_scoped_enum)
|
||||
self.exported_enums[symbol_name] = enumeration_node
|
||||
for entry_name, entry_value in entries.items():
|
||||
enumeration_node.add_constant(entry_name, entry_value)
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings
|
||||
def add_ignored_function_name(self, function_name):
|
||||
# type: (str) -> None
|
||||
self.type_hints_ignored_functions.add(function_name)
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings
|
||||
def create_function_node(self, func_info):
|
||||
# type: (Any) -> None
|
||||
create_function_node(self.cv_root, func_info)
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
|
||||
def find_class_node(self, class_info, namespaces):
|
||||
# type: (Any, Sequence[str]) -> ClassNode
|
||||
return find_class_node(
|
||||
self.cv_root,
|
||||
SymbolName.parse(class_info.full_original_name, namespaces),
|
||||
create_missing_namespaces=True
|
||||
)
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub)
|
||||
def create_class_node(self, class_info, namespaces):
|
||||
# type: (Any, Sequence[str]) -> ClassNode
|
||||
return create_class_node(self.cv_root, class_info, namespaces)
|
||||
|
||||
def generate(self, output_path):
|
||||
# type: (Union[str, Path]) -> None
|
||||
output_path = Path(output_path)
|
||||
py_typed_path = output_path / self.cv_root.export_name / 'py.typed'
|
||||
with failures_wrapper.delete_on_failure(py_typed_path):
|
||||
self._generate(output_path)
|
||||
|
||||
@failures_wrapper.wrap_exceptions_as_warnings
|
||||
def _generate(self, output_path):
|
||||
# type: (Path) -> None
|
||||
resolve_enum_scopes(self.cv_root, self.exported_enums)
|
||||
generate_typing_stubs(self.cv_root, output_path)
|
||||
|
||||
|
||||
else:
|
||||
class ClassNode:
|
||||
def add_base(self, base_node):
|
||||
pass
|
||||
|
||||
class TypingStubsGenerator:
|
||||
def __init__(self):
|
||||
self.type_hints_ignored_functions = set() # type: Set[str]
|
||||
print(
|
||||
'WARNING! Typing stubs can be generated only with Python 3.6 or higher. '
|
||||
'Current version {}'.format(sys.version_info)
|
||||
)
|
||||
|
||||
def add_enum(self, symbol_name, is_scoped_enum, entries):
|
||||
pass
|
||||
|
||||
def add_ignored_function_name(self, function_name):
|
||||
pass
|
||||
|
||||
def create_function_node(self, func_info):
|
||||
pass
|
||||
|
||||
def create_class_node(self, class_info, namespaces):
|
||||
return ClassNode()
|
||||
|
||||
def find_class_node(self, class_info, namespaces):
|
||||
return ClassNode()
|
||||
|
||||
def generate(self, output_path):
|
||||
pass
|
||||
@@ -0,0 +1,65 @@
|
||||
if(NOT DEFINED OpenCV_BINARY_DIR)
|
||||
message(FATAL_ERROR "Define OpenCV_BINARY_DIR")
|
||||
endif()
|
||||
include("${OpenCV_BINARY_DIR}/opencv_python_config.cmake")
|
||||
if(NOT DEFINED OpenCV_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Missing OpenCV_SOURCE_DIR")
|
||||
endif()
|
||||
if(DEFINED OPENCV_PYTHON_STANDALONE_INSTALL_PATH)
|
||||
set(OPENCV_PYTHON_INSTALL_PATH "${OPENCV_PYTHON_STANDALONE_INSTALL_PATH}")
|
||||
elseif(NOT OPENCV_PYTHON_INSTALL_PATH)
|
||||
message(FATAL_ERROR "Missing OPENCV_PYTHON_STANDALONE_INSTALL_PATH / OPENCV_PYTHON_INSTALL_PATH")
|
||||
endif()
|
||||
|
||||
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVUtils.cmake")
|
||||
|
||||
set(OPENCV_PYTHON_SKIP_DETECTION ON)
|
||||
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVDetectPython.cmake")
|
||||
find_python("${OPENCV_PYTHON_VERSION}" "${OPENCV_PYTHON_VERSION}" PYTHON_LIBRARY PYTHON_INCLUDE_DIR
|
||||
PYTHONINTERP_FOUND PYTHON_EXECUTABLE PYTHON_VERSION_STRING
|
||||
PYTHON_VERSION_MAJOR PYTHON_VERSION_MINOR PYTHONLIBS_FOUND
|
||||
PYTHONLIBS_VERSION_STRING PYTHON_LIBRARIES PYTHON_LIBRARY
|
||||
PYTHON_DEBUG_LIBRARIES PYTHON_LIBRARY_DEBUG PYTHON_INCLUDE_PATH
|
||||
PYTHON_INCLUDE_DIR PYTHON_INCLUDE_DIR2 PYTHON_PACKAGES_PATH
|
||||
PYTHON_NUMPY_INCLUDE_DIRS PYTHON_NUMPY_VERSION)
|
||||
if(NOT PYTHON_EXECUTABLE OR NOT PYTHON_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "Can't find Python development files")
|
||||
endif()
|
||||
if(NOT PYTHON_NUMPY_INCLUDE_DIRS)
|
||||
message(FATAL_ERROR "Can't find Python 'numpy' development files")
|
||||
endif()
|
||||
|
||||
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVInstallLayout.cmake")
|
||||
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVDetectDLPack.cmake")
|
||||
|
||||
status("-----------------------------------------------------------------")
|
||||
status(" Python:")
|
||||
status(" Interpreter:" "${PYTHON_EXECUTABLE} (ver ${PYTHON_VERSION_STRING})")
|
||||
status(" Libraries:" "${PYTHON_LIBRARIES} (ver ${PYTHONLIBS_VERSION_STRING})")
|
||||
status(" numpy:" "${PYTHON_NUMPY_INCLUDE_DIRS} (ver ${PYTHON_NUMPY_VERSION})")
|
||||
status("")
|
||||
status(" Install to:" "${CMAKE_INSTALL_PREFIX}")
|
||||
status("-----------------------------------------------------------------")
|
||||
|
||||
set(OpenCV_DIR "${OpenCV_BINARY_DIR}")
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
set(PYTHON PYTHON)
|
||||
|
||||
macro(ocv_add_module module_name)
|
||||
set(the_module opencv_${module_name})
|
||||
project(${the_module} CXX)
|
||||
endmacro()
|
||||
|
||||
macro(ocv_module_include_directories module)
|
||||
include_directories(${ARGN})
|
||||
endmacro()
|
||||
|
||||
set(MODULE_NAME python)
|
||||
set(MODULE_INSTALL_SUBDIR "")
|
||||
set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/lib")
|
||||
set(deps ${OpenCV_LIBRARIES})
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") # generate python target
|
||||
|
||||
# done, cleanup
|
||||
unset(OPENCV_BUILD_INFO_STR CACHE) # remove from cache
|
||||
@@ -0,0 +1,32 @@
|
||||
set(MODULE_NAME "python_tests")
|
||||
set(OPENCV_MODULE_IS_PART_OF_WORLD FALSE)
|
||||
ocv_add_module(${MODULE_NAME} INTERNAL)
|
||||
|
||||
set(OPENCV_PYTHON_TESTS_CONFIG_FILE_DIR "${OpenCV_BINARY_DIR}" CACHE INTERNAL "")
|
||||
set(OPENCV_PYTHON_TESTS_CONFIG_FILE "${OPENCV_PYTHON_TESTS_CONFIG_FILE_DIR}/opencv_python_tests.cfg" CACHE INTERNAL "")
|
||||
|
||||
# get list of modules to wrap
|
||||
set(OPENCV_PYTHON_MODULES)
|
||||
foreach(m ${OPENCV_MODULES_BUILD})
|
||||
if(";${OPENCV_MODULE_${m}_WRAPPERS};" MATCHES ";python.*;" AND HAVE_${m})
|
||||
list(APPEND OPENCV_PYTHON_MODULES ${m})
|
||||
#message(STATUS "\t${m}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
file(RELATIVE_PATH __loc_relative "${OPENCV_PYTHON_TESTS_CONFIG_FILE_DIR}" "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(opencv_tests_locations "${__loc_relative}")
|
||||
foreach(m ${OPENCV_PYTHON_MODULES})
|
||||
set(__loc "${OPENCV_MODULE_${m}_LOCATION}/misc/python/test")
|
||||
if(EXISTS "${__loc}")
|
||||
file(RELATIVE_PATH __loc_relative "${OPENCV_PYTHON_TESTS_CONFIG_FILE_DIR}" "${__loc}")
|
||||
list(APPEND opencv_tests_locations "${__loc_relative}")
|
||||
endif()
|
||||
endforeach(m)
|
||||
|
||||
string(REPLACE ";" "\n" opencv_tests_locations_ "${opencv_tests_locations}")
|
||||
ocv_update_file("${OPENCV_PYTHON_TESTS_CONFIG_FILE}" "${opencv_tests_locations_}")
|
||||
|
||||
#
|
||||
# TODO: Install rules (with test data?)
|
||||
#
|
||||
@@ -0,0 +1,2 @@
|
||||
pyyml
|
||||
numpy
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Location of tests:
|
||||
- <opencv_src>/modules/python/test
|
||||
- <opencv_src>/modules/<module>/misc/python/test/
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
sys.dont_write_bytecode = True # Don't generate .pyc files / __pycache__ directories
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
# Python 3 moved urlopen to urllib.requests
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib import urlopen
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
cwd = os.getcwd()
|
||||
config_file = 'opencv_python_tests.cfg'
|
||||
locations = [cwd, basedir]
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
locations += [str(s).strip() for s in f.readlines()]
|
||||
else:
|
||||
print('WARNING: OpenCV tests config file ({}) is missing, running subset of tests'.format(config_file))
|
||||
|
||||
tests_pattern = os.environ.get('OPENCV_PYTEST_FILTER', 'test_*') + '.py'
|
||||
if tests_pattern != 'test_*.py':
|
||||
print('Tests filter: {}'.format(tests_pattern))
|
||||
|
||||
processed = set()
|
||||
for l in locations:
|
||||
if not os.path.isabs(l):
|
||||
l = os.path.normpath(os.path.join(cwd, l))
|
||||
if l in processed:
|
||||
continue
|
||||
processed.add(l)
|
||||
print('Discovering python tests from: {}'.format(l))
|
||||
sys_path_modify = l not in sys.path
|
||||
if sys_path_modify:
|
||||
sys.path.append(l) # Hack python loader
|
||||
discovered_tests = loader.discover(l, pattern=tests_pattern, top_level_dir=l)
|
||||
print(' found {} tests'.format(discovered_tests.countTestCases()))
|
||||
tests.addTests(loader.discover(l, pattern=tests_pattern))
|
||||
if sys_path_modify:
|
||||
sys.path.remove(l)
|
||||
return tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
"""Algorithm serialization test."""
|
||||
import tempfile
|
||||
import os
|
||||
import cv2 as cv
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
class algorithm_rw_test(NewOpenCVTests):
|
||||
def test_algorithm_rw(self):
|
||||
fd, fname = tempfile.mkstemp(prefix="opencv_python_algorithm_", suffix=".yml")
|
||||
os.close(fd)
|
||||
|
||||
# some arbitrary non-default parameters
|
||||
gold = cv.ORB_create(nfeatures=200, scaleFactor=1.3, nlevels=5, edgeThreshold=28)
|
||||
gold.write(cv.FileStorage(fname, cv.FILE_STORAGE_WRITE), "ORB")
|
||||
|
||||
fs = cv.FileStorage(fname, cv.FILE_STORAGE_READ)
|
||||
algorithm = cv.ORB_create()
|
||||
algorithm.read(fs.getNode("ORB"))
|
||||
|
||||
self.assertEqual(algorithm.getMaxFeatures(), 200)
|
||||
self.assertAlmostEqual(algorithm.getScaleFactor(), 1.3, places=6)
|
||||
self.assertEqual(algorithm.getNLevels(), 5)
|
||||
self.assertEqual(algorithm.getEdgeThreshold(), 28)
|
||||
|
||||
os.remove(fname)
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class AsyncTest(NewOpenCVTests):
|
||||
|
||||
def test_async_simple(self):
|
||||
m = np.array([[1,2],[3,4],[5,6]])
|
||||
async_result = cv.utils.testAsyncArray(m)
|
||||
self.assertTrue(async_result.valid())
|
||||
ret, result = async_result.get(timeoutNs=10**6) # 1ms
|
||||
self.assertTrue(ret)
|
||||
self.assertFalse(async_result.valid())
|
||||
self.assertEqual(cv.norm(m, result, cv.NORM_INF), 0)
|
||||
|
||||
|
||||
def test_async_exception(self):
|
||||
async_result = cv.utils.testAsyncException()
|
||||
self.assertTrue(async_result.valid())
|
||||
try:
|
||||
_ret, _result = async_result.get(timeoutNs=10**6) # 1ms
|
||||
self.fail("Exception expected")
|
||||
except cv.error as e:
|
||||
self.assertEqual(cv.Error.StsOk, e.code)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Camshift tracker
|
||||
================
|
||||
|
||||
This is a demo that shows mean-shift based tracking
|
||||
You select a color objects such as your face and it tracks it.
|
||||
This reads from video camera (0 by default, or the camera number the user enters)
|
||||
|
||||
http://www.robinhewitt.com/research/track/camshift.html
|
||||
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
xrange = range
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
from tst_scene_render import TestSceneRender
|
||||
|
||||
from tests_common import NewOpenCVTests, intersectionRate
|
||||
|
||||
class camshift_test(NewOpenCVTests):
|
||||
|
||||
framesNum = 300
|
||||
frame = None
|
||||
selection = None
|
||||
drag_start = None
|
||||
show_backproj = False
|
||||
track_window = None
|
||||
render = None
|
||||
errors = 0
|
||||
|
||||
def prepareRender(self):
|
||||
|
||||
self.render = TestSceneRender(self.get_sample('samples/data/pca_test1.jpg'), deformation = True)
|
||||
|
||||
def runTracker(self):
|
||||
|
||||
framesCounter = 0
|
||||
self.selection = True
|
||||
|
||||
xmin, ymin, xmax, ymax = self.render.getCurrentRect()
|
||||
|
||||
self.track_window = (xmin, ymin, xmax - xmin, ymax - ymin)
|
||||
|
||||
while True:
|
||||
framesCounter += 1
|
||||
self.frame = self.render.getNextFrame()
|
||||
hsv = cv.cvtColor(self.frame, cv.COLOR_BGR2HSV)
|
||||
mask = cv.inRange(hsv, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
|
||||
|
||||
if self.selection:
|
||||
x0, y0, x1, y1 = self.render.getCurrentRect() + 50
|
||||
x0 -= 100
|
||||
y0 -= 100
|
||||
|
||||
hsv_roi = hsv[y0:y1, x0:x1]
|
||||
mask_roi = mask[y0:y1, x0:x1]
|
||||
hist = cv.calcHist( [hsv_roi], [0], mask_roi, [16], [0, 180] )
|
||||
cv.normalize(hist, hist, 0, 255, cv.NORM_MINMAX)
|
||||
self.hist = hist.reshape(-1)
|
||||
self.selection = False
|
||||
|
||||
if self.track_window and self.track_window[2] > 0 and self.track_window[3] > 0:
|
||||
self.selection = None
|
||||
prob = cv.calcBackProject([hsv], [0], self.hist, [0, 180], 1)
|
||||
prob &= mask
|
||||
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
|
||||
_track_box, self.track_window = cv.CamShift(prob, self.track_window, term_crit)
|
||||
|
||||
trackingRect = np.array(self.track_window)
|
||||
trackingRect[2] += trackingRect[0]
|
||||
trackingRect[3] += trackingRect[1]
|
||||
|
||||
if intersectionRate(self.render.getCurrentRect(), trackingRect) < 0.4:
|
||||
self.errors += 1
|
||||
|
||||
if framesCounter > self.framesNum:
|
||||
break
|
||||
|
||||
self.assertLess(float(self.errors) / self.framesNum, 0.4)
|
||||
|
||||
def test_camshift(self):
|
||||
self.prepareRender()
|
||||
self.runTracker()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import tempfile
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class photo_test(NewOpenCVTests):
|
||||
|
||||
def setUp(self):
|
||||
super(photo_test, self).setUp()
|
||||
self.image_cache = {}
|
||||
|
||||
def test_model(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
colorCorrectionMat = model.compute()
|
||||
|
||||
src_rgbl = np.array([
|
||||
[0.68078957, 0.12382801, 0.01514889],
|
||||
[0.81177942, 0.32550452, 0.089818],
|
||||
[0.61259378, 0.2831933, 0.07478902],
|
||||
[0.52696493, 0.20105976, 0.00958657],
|
||||
[0.80402284, 0.30419523, 0.12989841],
|
||||
[0.78658646, 0.63184111, 0.12062068],
|
||||
[0.78999637, 0.25520249, 0.03462853],
|
||||
[0.51866697, 0.16114393, 0.1078387],
|
||||
[0.74820768, 0.11770076, 0.06862177],
|
||||
[0.59776825, 0.05765816, 0.02886627],
|
||||
[0.8793145, 0.56346033, 0.0403954],
|
||||
[0.84124847, 0.42120746, 0.03287592],
|
||||
[0.23333214, 0.06780408, 0.05612276],
|
||||
[0.5176423, 0.41210976, 0.01896255],
|
||||
[0.73888613, 0.06575388, 0.06181293],
|
||||
[0.88326036, 0.58018751, 0.04321991],
|
||||
[0.75922531, 0.13149072, 0.1282041],
|
||||
[0.4345097, 0.32331019, 0.10494139],
|
||||
[0.94110142, 0.77941419, 0.26946323],
|
||||
[0.88438952, 0.5949049, 0.17536928],
|
||||
[0.84722687, 0.44160449, 0.09834799],
|
||||
[0.66743106, 0.24076803, 0.03394333],
|
||||
[0.47141286, 0.13592419, 0.01362205],
|
||||
[0.17377101, 0.03256864, 0.00203026]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(src_rgbl, model.getSrcLinearRGB().reshape(-1, 3), rtol=1e-4, atol=1e-4)
|
||||
|
||||
dst_rgbl = np.array([
|
||||
[0.17303173, 0.08211037, 0.05672686],
|
||||
[0.56832031, 0.29269488, 0.21835529],
|
||||
[0.10365019, 0.19588357, 0.33140475],
|
||||
[0.10159676, 0.14892193, 0.05188294],
|
||||
[0.22159627, 0.21584476, 0.43461196],
|
||||
[0.10806379, 0.51437196, 0.41264213],
|
||||
[0.74736423, 0.20062878, 0.02807988],
|
||||
[0.05757947, 0.10516793, 0.40296109],
|
||||
[0.56676218, 0.08424805, 0.11969461],
|
||||
[0.11099515, 0.04230796, 0.14292554],
|
||||
[0.34546869, 0.50872001, 0.04944204],
|
||||
[0.79461323, 0.35942459, 0.02051968],
|
||||
[0.01710416, 0.05022043, 0.29220674],
|
||||
[0.05598012, 0.30021149, 0.06871162],
|
||||
[0.45585457, 0.03033727, 0.04085654],
|
||||
[0.85737614, 0.56757335, 0.0068503],
|
||||
[0.53348585, 0.08861148, 0.30750446],
|
||||
[-0.0374061, 0.24699498, 0.40041217],
|
||||
[0.91262695, 0.91493909, 0.89367049],
|
||||
[0.57981916, 0.59200418, 0.59328881],
|
||||
[0.35490581, 0.36544831, 0.36755375],
|
||||
[0.19007357, 0.19186587, 0.19308397],
|
||||
[0.08529188, 0.08887994, 0.09257601],
|
||||
[0.0303193, 0.03113818, 0.03274845]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(dst_rgbl, model.getRefLinearRGB().reshape(-1, 3), rtol=1e-4, atol=1e-4)
|
||||
|
||||
mask = np.ones((24, 1), dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask, rtol=0.0, atol=0.0)
|
||||
|
||||
# Test reference color matrix
|
||||
refColorMat = np.array([
|
||||
[0.37406520, 0.02066507, 0.05804047],
|
||||
[0.12719672, 0.77389268, -0.01569404],
|
||||
[-0.27627010, 0.00603427, 2.74272981]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(colorCorrectionMat, refColorMat, rtol=1e-4, atol=1e-4)
|
||||
|
||||
def test_masks_weights_1(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
weightsList = np.array([1.1, 0, 0, 1.2, 0, 0, 1.3, 0, 0, 1.4, 0, 0,
|
||||
0.5, 0, 0, 0.6, 0, 0, 0.7, 0, 0, 0.8, 0, 0], dtype=np.float64)
|
||||
weightsList = weightsList.reshape(-1, 1)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
model.setColorSpace(cv.ccm.COLOR_SPACE_SRGB)
|
||||
model.setCcmType(cv.ccm.CCM_LINEAR)
|
||||
model.setDistance(cv.ccm.DISTANCE_CIE2000)
|
||||
model.setLinearization(cv.ccm.LINEARIZATION_GAMMA)
|
||||
model.setLinearizationGamma(2.2)
|
||||
model.setLinearizationDegree(3)
|
||||
model.setSaturatedThreshold(0, 0.98)
|
||||
model.setWeightsList(weightsList)
|
||||
model.setWeightCoeff(1.5)
|
||||
_ = model.compute()
|
||||
|
||||
weights = np.array([1.15789474, 1.26315789, 1.36842105, 1.47368421,
|
||||
0.52631579, 0.63157895, 0.73684211, 0.84210526], dtype=np.float64)
|
||||
np.testing.assert_allclose(model.getWeights(), weights.reshape(-1, 1), rtol=1e-4, atol=1e-4)
|
||||
|
||||
mask = np.array([True, False, False, True, False, False,
|
||||
True, False, False, True, False, False,
|
||||
True, False, False, True, False, False,
|
||||
True, False, False, True, False, False], dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask.reshape(-1, 1), rtol=0.0, atol=0.0)
|
||||
|
||||
def test_masks_weights_2(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
model.setCcmType(cv.ccm.CCM_LINEAR)
|
||||
model.setDistance(cv.ccm.DISTANCE_CIE2000)
|
||||
model.setLinearization(cv.ccm.LINEARIZATION_GAMMA)
|
||||
model.setLinearizationGamma(2.2)
|
||||
model.setLinearizationDegree(3)
|
||||
model.setSaturatedThreshold(0.05, 0.93)
|
||||
model.setWeightsList(np.array([]))
|
||||
model.setWeightCoeff(1.5)
|
||||
_ = model.compute()
|
||||
|
||||
weights = np.array([
|
||||
0.65554256, 1.49454705, 1.00499244, 0.79735434, 1.16327759,
|
||||
1.68623868, 1.37973155, 0.73213388, 1.0169629, 0.47430246,
|
||||
1.70312161, 0.45414218, 1.15910007, 0.7540434, 1.05049802,
|
||||
1.04551645, 1.54082353, 1.02453421, 0.6015915, 0.26154558
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(model.getWeights(), weights.reshape(-1, 1), rtol=1e-4, atol=1e-4)
|
||||
|
||||
# Test mask
|
||||
mask = np.array([True, True, True, True, True, True,
|
||||
True, True, True, True, False, True,
|
||||
True, True, True, False, True, True,
|
||||
False, False, True, True, True, True], dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask.reshape(-1, 1), rtol=0.0, atol=0.0)
|
||||
|
||||
def test_compute_color_correction_matrix(self):
|
||||
path = self.find_file('cv/mcc/mcc_ccm_test.yml')
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
|
||||
src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
colorCorrectionMat = model.compute()
|
||||
|
||||
gold_ccm = fs.getNode("ccm").mat()
|
||||
fs.release()
|
||||
|
||||
np.testing.assert_allclose(gold_ccm, colorCorrectionMat, rtol=1e-8, atol=1e-8)
|
||||
|
||||
gold_loss = 4.6386569120323129
|
||||
loss = model.getLoss()
|
||||
self.assertAlmostEqual(gold_loss, loss, places=8)
|
||||
|
||||
def test_correctImage(self):
|
||||
img = self.get_sample('cv/mcc/mcc_ccm_test.jpg')
|
||||
self.assertIsNotNone(img, "Test image can't be loaded: ")
|
||||
|
||||
gold_img = self.get_sample('cv/mcc/mcc_ccm_test_res.png')
|
||||
self.assertIsNotNone(gold_img, "Ground truth for test image can't be loaded: ")
|
||||
|
||||
path = self.find_file("cv/mcc/mcc_ccm_test.yml")
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
fs.release()
|
||||
|
||||
src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
|
||||
np.savetxt('src_test_correct.txt',src.reshape(-1,3),fmt="%.2f")
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
calibratedImage = np.zeros_like(img)
|
||||
model.correctImage(img, calibratedImage)
|
||||
|
||||
np.testing.assert_allclose(gold_img, calibratedImage, rtol=0.1, atol=0.1)
|
||||
|
||||
def test_mcc_ccm_combined(self):
|
||||
detector = cv.mcc_CCheckerDetector.create()
|
||||
|
||||
img = self.get_sample('cv/mcc/mcc_ccm_test.jpg')
|
||||
self.assertIsNotNone(img, "Test image can't be loaded: ")
|
||||
|
||||
gold_img = self.get_sample('cv/mcc/mcc_ccm_test_res.png')
|
||||
self.assertIsNotNone(gold_img, "Ground truth for test image can't be loaded: ")
|
||||
|
||||
detector.setColorChartType(cv.mcc.MCC24)
|
||||
self.assertTrue(detector.process(img))
|
||||
|
||||
checkers = detector.getListColorChecker()
|
||||
# Get colors from detector and save for debugging
|
||||
src = checkers[0].getChartsRGB(False).reshape(-1, 1, 3) / 255.
|
||||
src = src.astype(np.float64)
|
||||
|
||||
# Load reference colors from file for comparison
|
||||
path = self.find_file('cv/mcc/mcc_ccm_test.yml')
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
ref_src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
fs.release()
|
||||
|
||||
# Verify that detected colors are close to reference colors
|
||||
np.testing.assert_allclose(src, ref_src, rtol=0.01, atol=0.01)
|
||||
|
||||
# Use reference colors for model computation
|
||||
model = cv.ccm.ColorCorrectionModel(ref_src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
calibratedImage = np.zeros_like(img)
|
||||
model.correctImage(img, calibratedImage)
|
||||
|
||||
np.testing.assert_allclose(gold_img, calibratedImage, rtol=0.1, atol=0.1)
|
||||
|
||||
def test_serialization(self):
|
||||
path1 = self.find_file("cv/mcc/mcc_ccm_test.yml")
|
||||
fs = cv.FileStorage(path1, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
fs.release()
|
||||
|
||||
model = cv.ccm.ColorCorrectionModel(chartsRGB[:, 1].reshape(-1, 1, 3) / 255., cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
path1 = tempfile.mktemp(suffix='.yaml')
|
||||
fs1 = cv.FileStorage(path1, cv.FileStorage_WRITE)
|
||||
model.write(fs1)
|
||||
fs1.release()
|
||||
|
||||
model1 = cv.ccm.ColorCorrectionModel()
|
||||
fs2 = cv.FileStorage(path1, cv.FileStorage_READ)
|
||||
modelNode = fs2.getNode("ColorCorrectionModel")
|
||||
model1.read(modelNode)
|
||||
fs2.release()
|
||||
|
||||
path2 = tempfile.mktemp(suffix='.yaml')
|
||||
fs3 = cv.FileStorage(path2, cv.FileStorage_WRITE)
|
||||
model1.write(fs3)
|
||||
fs3.release()
|
||||
|
||||
with open(path1, 'r') as file1:
|
||||
str1 = file1.read()
|
||||
with open(path2, 'r') as file2:
|
||||
str2 = file2.read()
|
||||
self.assertEqual(str1, str2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
# This file is part of OpenCV project.
|
||||
# It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
# of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
EXPECTED_COEFFS_SIZE = 78
|
||||
|
||||
class ChromaticAberrationTest(NewOpenCVTests):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.test_yaml_file = self.find_file(
|
||||
"cv/cameracalibration/chromatic_aberration/ca_photo_calib.yaml"
|
||||
)
|
||||
|
||||
self.test_image = self.get_sample(
|
||||
"cv/cameracalibration/chromatic_aberration/ca_photo.png", 1
|
||||
)
|
||||
self.assertIsNotNone(self.test_image, "Failed to load test image")
|
||||
self.assertFalse(self.test_image.size == 0, "Failed to load test image")
|
||||
|
||||
def test_load_calib_and_correct_image(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
coeffMat, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
self.assertIsInstance(coeffMat, np.ndarray)
|
||||
self.assertEqual(coeffMat.dtype, np.float32)
|
||||
self.assertEqual(coeffMat.shape[0], 4)
|
||||
self.assertGreater(coeffMat.shape[1], 0)
|
||||
self.assertGreater(degree, 0)
|
||||
self.assertGreater(calib_size[0], 0)
|
||||
self.assertGreater(calib_size[1], 0)
|
||||
self.assertEqual(coeffMat.shape[1], EXPECTED_COEFFS_SIZE)
|
||||
|
||||
self.assertEqual(self.test_image.shape[1], calib_size[0])
|
||||
self.assertEqual(self.test_image.shape[0], calib_size[1])
|
||||
|
||||
corrected = cv.correctChromaticAberration(self.test_image, coeffMat, calib_size, degree)
|
||||
|
||||
self.assertEqual(corrected.shape[:2], self.test_image.shape[:2])
|
||||
self.assertEqual(corrected.dtype, self.test_image.dtype)
|
||||
|
||||
diff = cv.absdiff(self.test_image, corrected)
|
||||
sum_diff = cv.sumElems(diff)
|
||||
self.assertGreater(sum(sum_diff[:3]), 0.0)
|
||||
|
||||
def test_yaml_contents_as_expected(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
|
||||
red_node = fs.getNode("red_channel")
|
||||
blue_node = fs.getNode("blue_channel")
|
||||
self.assertTrue(red_node.isMap())
|
||||
self.assertTrue(blue_node.isMap())
|
||||
|
||||
coeffs_x = red_node.getNode("coeffs_x")
|
||||
self.assertIsNotNone(coeffs_x)
|
||||
self.assertEqual(coeffs_x.size(), EXPECTED_COEFFS_SIZE)
|
||||
|
||||
coeffs_x = blue_node.getNode("coeffs_x")
|
||||
self.assertIsNotNone(coeffs_x)
|
||||
self.assertEqual(coeffs_x.size(), EXPECTED_COEFFS_SIZE)
|
||||
|
||||
coeffs_y = red_node.getNode("coeffs_y")
|
||||
self.assertIsNotNone(coeffs_y)
|
||||
self.assertEqual(coeffs_y.size(), EXPECTED_COEFFS_SIZE)
|
||||
|
||||
coeffs_y = blue_node.getNode("coeffs_y")
|
||||
self.assertIsNotNone(coeffs_y)
|
||||
self.assertEqual(coeffs_y.size(), EXPECTED_COEFFS_SIZE)
|
||||
|
||||
fs.release()
|
||||
|
||||
def test_invalid_single_channel(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
coeffMat, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
|
||||
gray = cv.cvtColor(self.test_image, cv.COLOR_BGR2GRAY)
|
||||
with self.assertRaises(cv.error):
|
||||
_ = cv.correctChromaticAberration(gray, coeffMat, calib_size, degree)
|
||||
|
||||
def test_empty_coeff_mat(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
_, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
emptyCoeff = np.empty((0, 0), dtype=np.float32)
|
||||
with self.assertRaises(cv.error):
|
||||
_ = cv.correctChromaticAberration(self.test_image, emptyCoeff, calib_size, degree)
|
||||
|
||||
def test_mismatched_image_size(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
coeffMat, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
resized = cv.resize(self.test_image, (self.test_image.shape[1] // 2, self.test_image.shape[0] // 2))
|
||||
with self.assertRaises(cv.error):
|
||||
_ = cv.correctChromaticAberration(resized, coeffMat, calib_size, degree)
|
||||
|
||||
def test_wrong_coeff_type(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
coeffMat, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
wrongType = coeffMat.astype(np.float64)
|
||||
with self.assertRaises(cv.error):
|
||||
_ = cv.correctChromaticAberration(self.test_image, wrongType, calib_size, degree)
|
||||
|
||||
def test_degree_does_not_match_coeff_cols(self):
|
||||
fs = cv.FileStorage(self.test_yaml_file, cv.FileStorage_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
coeffMat, calib_size, degree = cv.loadChromaticAberrationParams(fs.root())
|
||||
|
||||
wrongDegree = max(1, degree - 1)
|
||||
self.assertNotEqual(wrongDegree, coeffMat.shape[1])
|
||||
with self.assertRaises(cv.error):
|
||||
_ = cv.correctChromaticAberration(self.test_image, coeffMat, calib_size, wrongDegree)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for copyto with mask
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import sys
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class copytomask_test(NewOpenCVTests):
|
||||
def test_copytomask(self):
|
||||
|
||||
img = self.get_sample('python/images/baboon.png', cv.IMREAD_COLOR)
|
||||
eps = 0.
|
||||
|
||||
#Create mask using inRange
|
||||
valeurBGRinf = np.array([0,0,100])
|
||||
valeurBGRSup = np.array([70, 70,255])
|
||||
maskRed = cv.inRange(img, valeurBGRinf, valeurBGRSup)
|
||||
#New binding
|
||||
dstcv = np.ndarray(np.array((2, 2, 1))*img.shape, dtype=img.dtype)
|
||||
dstcv.fill(255)
|
||||
cv.copyTo(img, maskRed, dstcv[:img.shape[0],:img.shape[1],:])
|
||||
#using numpy
|
||||
dstnp = np.ndarray(np.array((2, 2, 1))*img.shape, dtype=img.dtype)
|
||||
dstnp.fill(255)
|
||||
mask2=maskRed.astype(bool)
|
||||
_, mask_b = np.broadcast_arrays(img, mask2[..., None])
|
||||
np.copyto(dstnp[:img.shape[0],:img.shape[1],:], img, where=mask_b)
|
||||
self.assertEqual(cv.norm(dstnp ,dstcv), eps)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
CUDA-accelerated Computer Vision functions
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import os
|
||||
|
||||
from tests_common import NewOpenCVTests, unittest
|
||||
|
||||
class cuda_test(NewOpenCVTests):
|
||||
def setUp(self):
|
||||
super(cuda_test, self).setUp()
|
||||
if not cv.cuda.getCudaEnabledDeviceCount():
|
||||
self.skipTest("No CUDA-capable device is detected")
|
||||
|
||||
def test_cuda_upload_download(self):
|
||||
npMat = (np.random.random((128, 128, 3)) * 255).astype(np.uint8)
|
||||
cuMat = cv.cuda_GpuMat()
|
||||
cuMat.upload(npMat)
|
||||
|
||||
self.assertTrue(np.allclose(cuMat.download(), npMat))
|
||||
|
||||
def test_cuda_upload_download_stream(self):
|
||||
stream = cv.cuda_Stream()
|
||||
npMat = (np.random.random((128, 128, 3)) * 255).astype(np.uint8)
|
||||
cuMat = cv.cuda_GpuMat(128,128, cv.CV_8UC3)
|
||||
cuMat.upload(npMat, stream)
|
||||
npMat2 = cuMat.download(stream=stream)
|
||||
stream.waitForCompletion()
|
||||
self.assertTrue(np.allclose(npMat2, npMat))
|
||||
|
||||
def test_cuda_interop(self):
|
||||
npMat = (np.random.random((128, 128, 3)) * 255).astype(np.uint8)
|
||||
cuMat = cv.cuda_GpuMat()
|
||||
cuMat.upload(npMat)
|
||||
self.assertTrue(cuMat.cudaPtr() != 0)
|
||||
cuMatFromPtrSz = cv.cuda.createGpuMatFromCudaMemory(cuMat.size(),cuMat.type(),cuMat.cudaPtr(), cuMat.step)
|
||||
self.assertTrue(cuMat.cudaPtr() == cuMatFromPtrSz.cudaPtr())
|
||||
cuMatFromPtrRc = cv.cuda.createGpuMatFromCudaMemory(cuMat.size()[1],cuMat.size()[0],cuMat.type(),cuMat.cudaPtr(), cuMat.step)
|
||||
self.assertTrue(cuMat.cudaPtr() == cuMatFromPtrRc.cudaPtr())
|
||||
stream = cv.cuda_Stream()
|
||||
self.assertTrue(stream.cudaPtr() != 0)
|
||||
streamFromPtr = cv.cuda.wrapStream(stream.cudaPtr())
|
||||
self.assertTrue(stream.cudaPtr() == streamFromPtr.cudaPtr())
|
||||
asyncstream = cv.cuda_Stream(1) # cudaStreamNonBlocking
|
||||
self.assertTrue(asyncstream.cudaPtr() != 0)
|
||||
|
||||
def test_cuda_buffer_pool(self):
|
||||
stream_a = cv.cuda.Stream()
|
||||
cv.cuda.setBufferPoolUsage(True)
|
||||
cv.cuda.setBufferPoolConfig(cv.cuda.getDevice(), 1024 * 1024 * 64, 2)
|
||||
pool_a = cv.cuda.BufferPool(stream_a)
|
||||
cuMat = pool_a.getBuffer(1024, 1024, cv.CV_8UC3)
|
||||
cv.cuda.setBufferPoolUsage(False)
|
||||
self.assertEqual(cuMat.size(), (1024, 1024))
|
||||
self.assertEqual(cuMat.type(), cv.CV_8UC3)
|
||||
|
||||
def test_cuda_release(self):
|
||||
npMat = (np.random.random((128, 128, 3)) * 255).astype(np.uint8)
|
||||
cuMat = cv.cuda_GpuMat()
|
||||
cuMat.upload(npMat)
|
||||
cuMat.release()
|
||||
self.assertTrue(cuMat.cudaPtr() == 0)
|
||||
self.assertTrue(cuMat.step == 0)
|
||||
self.assertTrue(cuMat.size() == (0, 0))
|
||||
|
||||
def test_cuda_convertTo(self):
|
||||
# setup
|
||||
npMat_8UC4 = (np.random.random((128, 128, 4)) * 255).astype(np.uint8)
|
||||
npMat_32FC4 = npMat_8UC4.astype(np.single)
|
||||
new_type = cv.CV_32FC4
|
||||
|
||||
# sync
|
||||
# in/out
|
||||
cuMat_8UC4 = cv.cuda_GpuMat(npMat_8UC4)
|
||||
cuMat_32FC4 = cv.cuda_GpuMat(cuMat_8UC4.size(), new_type)
|
||||
cuMat_32FC4_out = cuMat_8UC4.convertTo(new_type, cuMat_32FC4)
|
||||
self.assertTrue(cuMat_32FC4.cudaPtr() == cuMat_32FC4_out.cudaPtr())
|
||||
npMat_32FC4_out = cuMat_32FC4.download()
|
||||
self.assertTrue(np.array_equal(npMat_32FC4, npMat_32FC4_out))
|
||||
# out
|
||||
cuMat_32FC4_out = cuMat_8UC4.convertTo(new_type)
|
||||
npMat_32FC4_out = cuMat_32FC4.download()
|
||||
self.assertTrue(np.array_equal(npMat_32FC4, npMat_32FC4_out))
|
||||
|
||||
# async
|
||||
stream = cv.cuda.Stream()
|
||||
cuMat_32FC4 = cv.cuda_GpuMat(cuMat_8UC4.size(), new_type)
|
||||
cuMat_32FC4_out = cuMat_8UC4.convertTo(new_type, cuMat_32FC4)
|
||||
# in/out
|
||||
cuMat_32FC4_out = cuMat_8UC4.convertTo(new_type, 1, 0, stream, cuMat_32FC4)
|
||||
self.assertTrue(cuMat_32FC4.cudaPtr() == cuMat_32FC4_out.cudaPtr())
|
||||
npMat_32FC4_out = cuMat_32FC4.download(stream)
|
||||
stream.waitForCompletion()
|
||||
self.assertTrue(np.array_equal(npMat_32FC4, npMat_32FC4_out))
|
||||
# out
|
||||
cuMat_32FC4_out = cuMat_8UC4.convertTo(new_type, 1, 0, stream)
|
||||
npMat_32FC4_out = cuMat_32FC4.download(stream)
|
||||
stream.waitForCompletion()
|
||||
self.assertTrue(np.array_equal(npMat_32FC4, npMat_32FC4_out))
|
||||
|
||||
def test_cuda_copyTo(self):
|
||||
# setup
|
||||
npMat_8UC4 = (np.random.random((128, 128, 4)) * 255).astype(np.uint8)
|
||||
|
||||
# sync
|
||||
# in/out
|
||||
cuMat_8UC4 = cv.cuda_GpuMat(npMat_8UC4)
|
||||
cuMat_8UC4_dst = cv.cuda_GpuMat(cuMat_8UC4.size(), cuMat_8UC4.type())
|
||||
cuMat_8UC4_out = cuMat_8UC4.copyTo(cuMat_8UC4_dst)
|
||||
self.assertTrue(cuMat_8UC4_out.cudaPtr() == cuMat_8UC4_dst.cudaPtr())
|
||||
npMat_8UC4_out = cuMat_8UC4_out.download()
|
||||
self.assertTrue(np.array_equal(npMat_8UC4, npMat_8UC4_out))
|
||||
# out
|
||||
cuMat_8UC4_out = cuMat_8UC4.copyTo()
|
||||
npMat_8UC4_out = cuMat_8UC4_out.download()
|
||||
self.assertTrue(np.array_equal(npMat_8UC4, npMat_8UC4_out))
|
||||
|
||||
# async
|
||||
stream = cv.cuda.Stream()
|
||||
# in/out
|
||||
cuMat_8UC4 = cv.cuda_GpuMat(npMat_8UC4)
|
||||
cuMat_8UC4_dst = cv.cuda_GpuMat(cuMat_8UC4.size(), cuMat_8UC4.type())
|
||||
cuMat_8UC4_out = cuMat_8UC4.copyTo(cuMat_8UC4_dst, stream)
|
||||
self.assertTrue(cuMat_8UC4_out.cudaPtr() == cuMat_8UC4_out.cudaPtr())
|
||||
npMat_8UC4_out = cuMat_8UC4_dst.download(stream)
|
||||
stream.waitForCompletion()
|
||||
self.assertTrue(np.array_equal(npMat_8UC4, npMat_8UC4_out))
|
||||
# out
|
||||
cuMat_8UC4_out = cuMat_8UC4.copyTo(stream)
|
||||
npMat_8UC4_out = cuMat_8UC4_out.download(stream)
|
||||
stream.waitForCompletion()
|
||||
self.assertTrue(np.array_equal(npMat_8UC4, npMat_8UC4_out))
|
||||
|
||||
def test_cuda_denoising(self):
|
||||
self.assertEqual(True, hasattr(cv.cuda, 'fastNlMeansDenoising'))
|
||||
self.assertEqual(True, hasattr(cv.cuda, 'fastNlMeansDenoisingColored'))
|
||||
self.assertEqual(True, hasattr(cv.cuda, 'nonLocalMeans'))
|
||||
|
||||
def test_dlpack_GpuMat(self):
|
||||
for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.float16, np.int32, np.float32, np.float64, np.int64, np.uint32, np.uint64, np.bool_]:
|
||||
for channels in [2, 3, 5]:
|
||||
ref = (np.random.random((64, 128, channels)) * 255).astype(dtype)
|
||||
src = cv.cuda_GpuMat()
|
||||
src.upload(ref)
|
||||
|
||||
# workaround int64/uint64 conversion to int32/uint32
|
||||
if dtype == np.int64:
|
||||
print("skip because of https://github.com/opencv/opencv/issues/27671")
|
||||
continue
|
||||
src = src.convertTo(cv.CV_64S)
|
||||
elif dtype == np.uint64:
|
||||
print("skip because of https://github.com/opencv/opencv/issues/27671")
|
||||
continue
|
||||
src = src.convertTo(cv.CV_64U)
|
||||
|
||||
dst = cv.cuda_GpuMat.from_dlpack(src)
|
||||
test = dst.download()
|
||||
self.assertEqual(ref.dtype, test.dtype)
|
||||
equal = np.array_equal(ref, test)
|
||||
if not equal:
|
||||
print(f"Failed test with dtype {dtype} and {channels} channels")
|
||||
self.assertTrue(equal)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for disctrete fourier transform (dft)
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class dft_test(NewOpenCVTests):
|
||||
def test_dft(self):
|
||||
|
||||
img = self.get_sample('samples/data/rubberwhale1.png', 0)
|
||||
eps = 0.001
|
||||
|
||||
#test direct transform
|
||||
refDft = np.fft.fft2(img)
|
||||
refDftShift = np.fft.fftshift(refDft)
|
||||
refMagnitide = np.log(1.0 + np.abs(refDftShift))
|
||||
|
||||
testDft = cv.dft(np.float32(img),flags = cv.DFT_COMPLEX_OUTPUT)
|
||||
testDftShift = np.fft.fftshift(testDft)
|
||||
testMagnitude = np.log(1.0 + cv.magnitude(testDftShift[:,:,0], testDftShift[:,:,1]))
|
||||
|
||||
refMagnitide = cv.normalize(refMagnitide, 0.0, 1.0, cv.NORM_MINMAX)
|
||||
testMagnitude = cv.normalize(testMagnitude, 0.0, 1.0, cv.NORM_MINMAX)
|
||||
|
||||
self.assertLess(cv.norm(refMagnitide - testMagnitude), eps)
|
||||
|
||||
#test inverse transform
|
||||
img_back = np.fft.ifft2(refDft)
|
||||
img_back = np.abs(img_back)
|
||||
|
||||
img_backTest = cv.idft(testDft)
|
||||
img_backTest = cv.magnitude(img_backTest[:,:,0], img_backTest[:,:,1])
|
||||
|
||||
img_backTest = cv.normalize(img_backTest, 0.0, 1.0, cv.NORM_MINMAX)
|
||||
img_back = cv.normalize(img_back, 0.0, 1.0, cv.NORM_MINMAX)
|
||||
|
||||
self.assertLess(cv.norm(img_back - img_backTest), eps)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
ECC multiscale alignment test
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import inspect
|
||||
import math
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class eccms_test(NewOpenCVTests):
|
||||
def test_eccms(self):
|
||||
expected_res = np.array([
|
||||
[1.0225, 0.0606, -28.6452],
|
||||
[-0.0475, 1.0314, 11.819],
|
||||
[8.21e-06, -3.65e-07, 1.0]
|
||||
], dtype=np.float32)
|
||||
|
||||
largeGray0 = self.get_sample('cv/shared/halmosh0.jpg', cv.IMREAD_GRAYSCALE)
|
||||
largeGray1 = self.get_sample('cv/shared/halmosh2.jpg', cv.IMREAD_GRAYSCALE)
|
||||
roiMask0 = self.get_sample('cv/shared/halmosh0mask.png', cv.IMREAD_GRAYSCALE)
|
||||
roiMask1 = self.get_sample('cv/shared/halmosh2mask.png', cv.IMREAD_GRAYSCALE)
|
||||
|
||||
if largeGray0 is None or largeGray1 is None or roiMask0 is None or roiMask1 is None:
|
||||
self.assertEqual(0, 1, 'Missing test data')
|
||||
|
||||
found = np.eye(3, 3, dtype=np.float32)
|
||||
n_iters = 23
|
||||
termination_eps = 1e-6
|
||||
params = cv.ECCParameters()
|
||||
params.criteria = (cv.TERM_CRITERIA_COUNT + cv.TERM_CRITERIA_EPS, n_iters, termination_eps)
|
||||
params.motionType = cv.MOTION_HOMOGRAPHY
|
||||
params.nlevels = 5
|
||||
params.itersPerLevel = [5, 10, 300, 300, 1000]
|
||||
|
||||
_, found = cv.findTransformECCMultiScale(largeGray0,largeGray1, found, params, roiMask0, roiMask1)
|
||||
|
||||
self.assertLess(cv.norm(found - expected_res, cv.NORM_L1), 0.1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class Features_Tests(NewOpenCVTests):
|
||||
|
||||
def test_issue_13406(self):
|
||||
self.assertEqual(True, hasattr(cv, 'drawKeypoints'))
|
||||
self.assertEqual(True, hasattr(cv, 'DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS'))
|
||||
self.assertEqual(True, hasattr(cv, 'DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python
|
||||
"""Algorithm serialization test."""
|
||||
from __future__ import print_function
|
||||
import base64
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class MyData:
|
||||
def __init__(self):
|
||||
self.A = 97
|
||||
self.X = np.pi
|
||||
self.name = 'mydata1234'
|
||||
|
||||
def write(self, fs, name):
|
||||
fs.startWriteStruct(name, cv.FileNode_MAP|cv.FileNode_FLOW)
|
||||
fs.write('A', self.A)
|
||||
fs.write('X', self.X)
|
||||
fs.write('name', self.name)
|
||||
fs.endWriteStruct()
|
||||
|
||||
def read(self, node):
|
||||
if (not node.empty()):
|
||||
self.A = int(node.getNode('A').real())
|
||||
self.X = node.getNode('X').real()
|
||||
self.name = node.getNode('name').string()
|
||||
else:
|
||||
self.A = self.X = 0
|
||||
self.name = ''
|
||||
|
||||
class filestorage_io_test(NewOpenCVTests):
|
||||
strings_data = ['image1.jpg', 'Awesomeness', '../data/baboon.jpg']
|
||||
R0 = np.eye(3,3)
|
||||
T0 = np.zeros((3,1))
|
||||
|
||||
def write_data(self, fname):
|
||||
fs = cv.FileStorage(fname, cv.FileStorage_WRITE)
|
||||
R = self.R0
|
||||
T = self.T0
|
||||
m = MyData()
|
||||
|
||||
fs.write('iterationNr', 100)
|
||||
|
||||
fs.startWriteStruct('strings', cv.FileNode_SEQ)
|
||||
for elem in self.strings_data:
|
||||
fs.write('', elem)
|
||||
fs.endWriteStruct()
|
||||
|
||||
fs.startWriteStruct('Mapping', cv.FileNode_MAP)
|
||||
fs.write('One', 1)
|
||||
fs.write('Two', 2)
|
||||
fs.endWriteStruct()
|
||||
|
||||
fs.write('R_MAT', R)
|
||||
fs.write('T_MAT', T)
|
||||
|
||||
m.write(fs, 'MyData')
|
||||
fs.release()
|
||||
|
||||
def read_data_and_check(self, fname):
|
||||
fs = cv.FileStorage(fname, cv.FileStorage_READ)
|
||||
|
||||
n = fs.getNode('iterationNr')
|
||||
itNr = int(n.real())
|
||||
self.assertEqual(itNr, 100)
|
||||
|
||||
n = fs.getNode('strings')
|
||||
self.assertTrue(n.isSeq())
|
||||
self.assertEqual(n.size(), len(self.strings_data))
|
||||
|
||||
for i in range(n.size()):
|
||||
self.assertEqual(n.at(i).string(), self.strings_data[i])
|
||||
|
||||
n = fs.getNode('Mapping')
|
||||
self.assertEqual(int(n.getNode('Two').real()), 2)
|
||||
self.assertEqual(int(n.getNode('One').real()), 1)
|
||||
|
||||
R = fs.getNode('R_MAT').mat()
|
||||
T = fs.getNode('T_MAT').mat()
|
||||
|
||||
self.assertEqual(cv.norm(R, self.R0, cv.NORM_INF), 0)
|
||||
self.assertEqual(cv.norm(T, self.T0, cv.NORM_INF), 0)
|
||||
|
||||
m0 = MyData()
|
||||
m = MyData()
|
||||
m.read(fs.getNode('MyData'))
|
||||
self.assertEqual(m.A, m0.A)
|
||||
self.assertEqual(m.X, m0.X)
|
||||
self.assertEqual(m.name, m0.name)
|
||||
|
||||
n = fs.getNode('NonExisting')
|
||||
self.assertTrue(n.isNone())
|
||||
fs.release()
|
||||
|
||||
def run_fs_test(self, ext):
|
||||
fd, fname = tempfile.mkstemp(prefix="opencv_python_sample_filestorage", suffix=ext)
|
||||
os.close(fd)
|
||||
self.write_data(fname)
|
||||
self.read_data_and_check(fname)
|
||||
os.remove(fname)
|
||||
|
||||
def test_xml(self):
|
||||
self.run_fs_test(".xml")
|
||||
|
||||
def test_yml(self):
|
||||
self.run_fs_test(".yml")
|
||||
|
||||
def test_json(self):
|
||||
self.run_fs_test(".json")
|
||||
|
||||
def test_base64(self):
|
||||
fd, fname = tempfile.mkstemp(prefix="opencv_python_sample_filestorage_base64", suffix=".json")
|
||||
os.close(fd)
|
||||
np.random.seed(42)
|
||||
self.write_base64_json(fname)
|
||||
os.remove(fname)
|
||||
|
||||
@staticmethod
|
||||
def get_normal_2d_mat(dtype):
|
||||
rows = 10
|
||||
cols = 20
|
||||
cn = 3
|
||||
|
||||
image = np.zeros((rows, cols, cn), dtype)
|
||||
if dtype != bool:
|
||||
image[:] = (1, 2, 127)
|
||||
else:
|
||||
image[:] = (False, True, False)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if dtype != bool:
|
||||
image[i, j, 1] = (i + j) % 256
|
||||
else:
|
||||
image[i, j, 1] = (i + j) % 2 != 0
|
||||
|
||||
return image
|
||||
|
||||
@staticmethod
|
||||
def get_normal_nd_mat():
|
||||
shape = (2, 2, 1, 2)
|
||||
cn = 4
|
||||
|
||||
image = np.zeros(shape + (cn,), np.float64)
|
||||
image[:] = (0.888, 0.111, 0.666, 0.444)
|
||||
|
||||
return image
|
||||
|
||||
@staticmethod
|
||||
def get_empty_2d_mat():
|
||||
shape = (0, 0)
|
||||
cn = 1
|
||||
|
||||
image = np.zeros(shape + (cn,), np.uint8)
|
||||
|
||||
return image
|
||||
|
||||
@staticmethod
|
||||
def get_random_mat():
|
||||
rows = 8
|
||||
cols = 16
|
||||
cn = 1
|
||||
|
||||
image = np.random.rand(rows, cols, cn)
|
||||
|
||||
return image
|
||||
|
||||
@staticmethod
|
||||
def decode(data):
|
||||
# strip $base64$
|
||||
encoded = data[8:]
|
||||
|
||||
if len(encoded) == 0:
|
||||
return b''
|
||||
|
||||
# strip info about datatype and padding
|
||||
return base64.b64decode(encoded)[24:]
|
||||
|
||||
def write_base64_json(self, fname):
|
||||
fs = cv.FileStorage(fname, cv.FileStorage_WRITE_BASE64)
|
||||
|
||||
mats = {'normal_2d_mat_u8': self.get_normal_2d_mat(np.uint8),
|
||||
'normal_2d_mat_u32': self.get_normal_2d_mat(np.uint32),
|
||||
'normal_2d_mat_bool': self.get_normal_2d_mat(bool),
|
||||
'normal_nd_mat': self.get_normal_nd_mat(),
|
||||
'empty_2d_mat': self.get_empty_2d_mat(),
|
||||
'random_mat': self.get_random_mat()}
|
||||
|
||||
for name, mat in mats.items():
|
||||
fs.write(name, mat)
|
||||
|
||||
fs.release()
|
||||
|
||||
data = {}
|
||||
with open(fname) as file:
|
||||
data = json.load(file)
|
||||
|
||||
for name, mat in mats.items():
|
||||
buffer = b''
|
||||
|
||||
if mat.size != 0:
|
||||
if hasattr(mat, 'tobytes'):
|
||||
buffer = mat.tobytes()
|
||||
else:
|
||||
buffer = mat.tostring()
|
||||
|
||||
self.assertEqual(buffer, self.decode(data[name]['data']))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Robust line fitting.
|
||||
==================
|
||||
|
||||
Example of using cv.fitLine function for fitting line
|
||||
to points in presence of outliers.
|
||||
|
||||
Switch through different M-estimator functions and see,
|
||||
how well the robust functions fit the line even
|
||||
in case of ~50% of outliers.
|
||||
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
w, h = 512, 256
|
||||
|
||||
def toint(p):
|
||||
return tuple(map(int, p))
|
||||
|
||||
def sample_line(p1, p2, n, noise=0.0):
|
||||
np.random.seed(10)
|
||||
p1 = np.float32(p1)
|
||||
t = np.random.rand(n,1)
|
||||
return p1 + (p2-p1)*t + np.random.normal(size=(n, 2))*noise
|
||||
|
||||
dist_func_names = ['DIST_L2', 'DIST_L1', 'DIST_L12', 'DIST_FAIR', 'DIST_WELSCH', 'DIST_HUBER']
|
||||
|
||||
class fitline_test(NewOpenCVTests):
|
||||
|
||||
def test_fitline(self):
|
||||
|
||||
noise = 5
|
||||
n = 200
|
||||
r = 5 / 100.0
|
||||
outn = int(n*r)
|
||||
|
||||
p0, p1 = (90, 80), (w-90, h-80)
|
||||
line_points = sample_line(p0, p1, n-outn, noise)
|
||||
outliers = np.random.rand(outn, 2) * (w, h)
|
||||
points = np.vstack([line_points, outliers])
|
||||
|
||||
lines = []
|
||||
|
||||
for name in dist_func_names:
|
||||
func = getattr(cv, name)
|
||||
vx, vy, cx, cy = cv.fitLine(np.float32(points), func, 0, 0.01, 0.01)
|
||||
line = [vx[0], vy[0], cx[0], cy[0]]
|
||||
lines.append(line)
|
||||
|
||||
eps = 0.05
|
||||
|
||||
refVec = (np.float32(p1) - p0) / cv.norm(np.float32(p1) - p0)
|
||||
|
||||
for i in range(len(lines)):
|
||||
self.assertLessEqual(cv.norm(refVec - lines[i][0:2], cv.NORM_L2), eps)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,41 @@
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import os
|
||||
import datetime
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class get_cache_dir_test(NewOpenCVTests):
|
||||
def test_get_cache_dir(self):
|
||||
#New binding
|
||||
path = cv.utils.fs.getCacheDirectoryForDownloads()
|
||||
self.assertTrue(os.path.exists(path))
|
||||
self.assertTrue(os.path.isdir(path))
|
||||
|
||||
def get_cache_dir_imread_interop(self, ext):
|
||||
path = cv.utils.fs.getCacheDirectoryForDownloads()
|
||||
gold_image = np.ones((16, 16, 3), np.uint8)
|
||||
read_from_file = np.zeros((16, 16, 3), np.uint8)
|
||||
test_file_name = os.path.join(path, "test." + ext)
|
||||
try:
|
||||
cv.imwrite(test_file_name, gold_image)
|
||||
read_from_file = cv.imread(test_file_name)
|
||||
finally:
|
||||
os.remove(test_file_name)
|
||||
|
||||
self.assertEqual(cv.norm(gold_image, read_from_file), 0)
|
||||
|
||||
def test_get_cache_dir_imread_interop_png(self):
|
||||
self.get_cache_dir_imread_interop("png")
|
||||
|
||||
def test_get_cache_dir_imread_interop_jpeg(self):
|
||||
self.get_cache_dir_imread_interop("jpg")
|
||||
|
||||
def test_get_cache_dir_imread_interop_tiff(self):
|
||||
self.get_cache_dir_imread_interop("tif")
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
===============================================================================
|
||||
Interactive Image Segmentation using GrabCut algorithm.
|
||||
===============================================================================
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import sys
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class grabcut_test(NewOpenCVTests):
|
||||
|
||||
def verify(self, mask, exp):
|
||||
|
||||
maxDiffRatio = 0.02
|
||||
expArea = np.count_nonzero(exp)
|
||||
nonIntersectArea = np.count_nonzero(mask != exp)
|
||||
curRatio = float(nonIntersectArea) / expArea
|
||||
return curRatio < maxDiffRatio
|
||||
|
||||
def scaleMask(self, mask):
|
||||
|
||||
return np.where((mask==cv.GC_FGD) + (mask==cv.GC_PR_FGD),255,0).astype('uint8')
|
||||
|
||||
def test_grabcut(self):
|
||||
|
||||
img = self.get_sample('cv/shared/airplane.png')
|
||||
mask_prob = self.get_sample("cv/grabcut/mask_probpy.png", 0)
|
||||
exp_mask1 = self.get_sample("cv/grabcut/exp_mask1py.png", 0)
|
||||
exp_mask2 = self.get_sample("cv/grabcut/exp_mask2py.png", 0)
|
||||
|
||||
if img is None:
|
||||
self.assertTrue(False, 'Missing test data')
|
||||
|
||||
rect = (24, 126, 459, 168)
|
||||
mask = np.zeros(img.shape[:2], dtype = np.uint8)
|
||||
bgdModel = np.zeros((1,65),np.float64)
|
||||
fgdModel = np.zeros((1,65),np.float64)
|
||||
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 0, cv.GC_INIT_WITH_RECT)
|
||||
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 2, cv.GC_EVAL)
|
||||
|
||||
if mask_prob is None:
|
||||
mask_prob = mask.copy()
|
||||
cv.imwrite(self.extraTestDataPath + '/cv/grabcut/mask_probpy.png', mask_prob)
|
||||
if exp_mask1 is None:
|
||||
exp_mask1 = self.scaleMask(mask)
|
||||
cv.imwrite(self.extraTestDataPath + '/cv/grabcut/exp_mask1py.png', exp_mask1)
|
||||
|
||||
self.assertEqual(self.verify(self.scaleMask(mask), exp_mask1), True)
|
||||
|
||||
mask = mask_prob
|
||||
bgdModel = np.zeros((1,65),np.float64)
|
||||
fgdModel = np.zeros((1,65),np.float64)
|
||||
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 0, cv.GC_INIT_WITH_MASK)
|
||||
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 1, cv.GC_EVAL)
|
||||
|
||||
if exp_mask2 is None:
|
||||
exp_mask2 = self.scaleMask(mask)
|
||||
cv.imwrite(self.extraTestDataPath + '/cv/grabcut/exp_mask2py.png', exp_mask2)
|
||||
|
||||
self.assertEqual(self.verify(self.scaleMask(mask), exp_mask2), True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
This example illustrates how to use cv.HoughCircles() function.
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import sys
|
||||
from numpy import pi, sin, cos
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
def circleApproximation(circle):
|
||||
|
||||
nPoints = 30
|
||||
dPhi = 2*pi / nPoints
|
||||
contour = []
|
||||
for i in range(nPoints):
|
||||
contour.append(([circle[0] + circle[2]*cos(i*dPhi),
|
||||
circle[1] + circle[2]*sin(i*dPhi)]))
|
||||
|
||||
return np.array(contour).astype(int)
|
||||
|
||||
def convContoursIntersectiponRate(c1, c2):
|
||||
|
||||
s1 = cv.contourArea(c1)
|
||||
s2 = cv.contourArea(c2)
|
||||
|
||||
s, _ = cv.intersectConvexConvex(c1, c2)
|
||||
|
||||
return 2*s/(s1+s2)
|
||||
|
||||
class houghcircles_test(NewOpenCVTests):
|
||||
|
||||
def test_houghcircles(self):
|
||||
|
||||
fn = "samples/data/board.jpg"
|
||||
|
||||
src = self.get_sample(fn, 1)
|
||||
img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
|
||||
img = cv.medianBlur(img, 5)
|
||||
|
||||
circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 1, 10, np.array([]), 100, 30, 1, 30)[0]
|
||||
|
||||
testCircles = [[38, 181, 17.6],
|
||||
[99.7, 166, 13.12],
|
||||
[142.7, 160, 13.52],
|
||||
[223.6, 110, 8.62],
|
||||
[79.1, 206.7, 8.62],
|
||||
[47.5, 351.6, 11.64],
|
||||
[189.5, 354.4, 11.64],
|
||||
[189.8, 298.9, 10.64],
|
||||
[189.5, 252.4, 14.62],
|
||||
[252.5, 393.4, 15.62],
|
||||
[602.9, 467.5, 11.42],
|
||||
[222, 210.4, 9.12],
|
||||
[263.1, 216.7, 9.12],
|
||||
[359.8, 222.6, 9.12],
|
||||
[518.9, 120.9, 9.12],
|
||||
[413.8, 113.4, 9.12],
|
||||
[489, 127.2, 9.12],
|
||||
[448.4, 121.3, 9.12],
|
||||
[384.6, 128.9, 8.62]]
|
||||
|
||||
matches_counter = 0
|
||||
|
||||
for i in range(len(testCircles)):
|
||||
for j in range(len(circles)):
|
||||
|
||||
tstCircle = circleApproximation(testCircles[i])
|
||||
circle = circleApproximation(circles[j])
|
||||
if convContoursIntersectiponRate(tstCircle, circle) > 0.6:
|
||||
matches_counter += 1
|
||||
|
||||
self.assertGreater(float(matches_counter) / len(testCircles), .5)
|
||||
self.assertLess(float(len(circles) - matches_counter) / len(circles), .75)
|
||||
|
||||
circles_acc = cv.HoughCirclesWithAccumulator(
|
||||
image=img,
|
||||
method=cv.HOUGH_GRADIENT,
|
||||
dp=1,
|
||||
minDist=10,
|
||||
circles=np.array([]),
|
||||
param1=150,
|
||||
param2=45,
|
||||
minRadius=1,
|
||||
maxRadius=30)
|
||||
|
||||
self.assertEqual(circles_acc.shape, (1, 2, 4))
|
||||
self.assertEqual(circles_acc[0, 0, 3], 66.)
|
||||
self.assertEqual(circles_acc[0, 1, 3], 62.)
|
||||
|
||||
def test_houghcircles_alt(self):
|
||||
|
||||
fn = "samples/data/board.jpg"
|
||||
|
||||
src = self.get_sample(fn, 1)
|
||||
img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
|
||||
img = cv.medianBlur(img, 5)
|
||||
|
||||
circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT_ALT, 1, 10, np.array([]), 300, 0.9, 1, 30)
|
||||
|
||||
self.assertEqual(circles.shape, (1, 18, 3))
|
||||
|
||||
circles = circles[0]
|
||||
|
||||
testCircles = [[38, 181, 17.6],
|
||||
[99.7, 166, 13.12],
|
||||
[142.7, 160, 13.52],
|
||||
[223.6, 110, 8.62],
|
||||
[79.1, 206.7, 8.62],
|
||||
[47.5, 351.6, 11.64],
|
||||
[189.5, 354.4, 11.64],
|
||||
[189.8, 298.9, 10.64],
|
||||
[189.5, 252.4, 14.62],
|
||||
[252.5, 393.4, 15.62],
|
||||
[602.9, 467.5, 11.42],
|
||||
[222, 210.4, 9.12],
|
||||
[263.1, 216.7, 9.12],
|
||||
[359.8, 222.6, 9.12],
|
||||
[518.9, 120.9, 9.12],
|
||||
[413.8, 113.4, 9.12],
|
||||
[489, 127.2, 9.12],
|
||||
[448.4, 121.3, 9.12],
|
||||
[384.6, 128.9, 8.62]]
|
||||
|
||||
matches_counter = 0
|
||||
|
||||
for i in range(len(testCircles)):
|
||||
for j in range(len(circles)):
|
||||
|
||||
tstCircle = circleApproximation(testCircles[i])
|
||||
circle = circleApproximation(circles[j])
|
||||
if convContoursIntersectiponRate(tstCircle, circle) > 0.6:
|
||||
matches_counter += 1
|
||||
|
||||
self.assertGreater(float(matches_counter) / len(testCircles), .5)
|
||||
self.assertLess(float(len(circles) - matches_counter) / len(circles), .75)
|
||||
|
||||
circles_acc = cv.HoughCirclesWithAccumulator(
|
||||
image=img,
|
||||
method=cv.HOUGH_GRADIENT_ALT,
|
||||
dp=1,
|
||||
minDist=10,
|
||||
circles=np.array([]),
|
||||
param1=300,
|
||||
param2=0.9,
|
||||
minRadius=13,
|
||||
maxRadius=15)
|
||||
|
||||
self.assertEqual(circles_acc.shape, (1, 3, 4))
|
||||
self.assertEqual(circles_acc[0, 0, 3], 62.)
|
||||
self.assertEqual(circles_acc[0, 1, 3], 59.)
|
||||
self.assertEqual(circles_acc[0, 2, 3], 47.)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
This example illustrates how to use Hough Transform to find lines
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import sys
|
||||
import math
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
def linesDiff(line1, line2):
|
||||
|
||||
norm1 = cv.norm(line1 - line2, cv.NORM_L2)
|
||||
line3 = line1[2:4] + line1[0:2]
|
||||
norm2 = cv.norm(line3 - line2, cv.NORM_L2)
|
||||
|
||||
return min(norm1, norm2)
|
||||
|
||||
class houghlines_test(NewOpenCVTests):
|
||||
|
||||
def test_houghlines(self):
|
||||
|
||||
fn = "/samples/data/pic1.png"
|
||||
|
||||
src = self.get_sample(fn)
|
||||
dst = cv.Canny(src, 50, 200)
|
||||
|
||||
lines = cv.HoughLinesP(dst, 1, math.pi/180.0, 40, np.array([]), 50, 10)[:,:]
|
||||
|
||||
eps = 5
|
||||
testLines = [
|
||||
#rect1
|
||||
[ 232, 25, 43, 25],
|
||||
[ 43, 129, 232, 129],
|
||||
[ 43, 129, 43, 25],
|
||||
[232, 129, 232, 25],
|
||||
#rect2
|
||||
[251, 86, 314, 183],
|
||||
[252, 86, 323, 40],
|
||||
[315, 183, 386, 137],
|
||||
[324, 40, 386, 136],
|
||||
#triangle
|
||||
[245, 205, 377, 205],
|
||||
[244, 206, 305, 278],
|
||||
[306, 279, 377, 205],
|
||||
#rect3
|
||||
[153, 177, 196, 177],
|
||||
[153, 277, 153, 179],
|
||||
[153, 277, 196, 277],
|
||||
[196, 177, 196, 277]]
|
||||
|
||||
matches_counter = 0
|
||||
|
||||
for i in range(len(testLines)):
|
||||
for j in range(len(lines)):
|
||||
if linesDiff(testLines[i], lines[j]) < eps:
|
||||
matches_counter += 1
|
||||
|
||||
self.assertGreater(float(matches_counter) / len(testLines), .7)
|
||||
|
||||
lines_acc = cv.HoughLinesWithAccumulator(dst, rho=1, theta=np.pi / 180, threshold=150, srn=0, stn=0)
|
||||
self.assertEqual(lines_acc[0,2], 192.0)
|
||||
self.assertEqual(lines_acc[1,2], 187.0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for imread
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class imread_test(NewOpenCVTests):
|
||||
def test_imread_to_buffer(self):
|
||||
path = self.extraTestDataPath + '/cv/shared/lena.png'
|
||||
ref = cv.imread(path)
|
||||
|
||||
img = np.zeros_like(ref)
|
||||
cv.imread(path, img)
|
||||
self.assertEqual(cv.norm(ref, img, cv.NORM_INF), 0.0)
|
||||
|
||||
def test_imread_with_meta(self):
|
||||
path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.jpg'
|
||||
img, meta_types, meta_data = cv.imreadWithMetadata(path, flags=cv.IMREAD_ANYCOLOR)
|
||||
self.assertTrue(img is not None)
|
||||
self.assertTrue(meta_types is not None)
|
||||
self.assertTrue(meta_data is not None)
|
||||
|
||||
path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.png'
|
||||
img, meta_types, meta_data = cv.imreadWithMetadata(path, flags=cv.IMREAD_ANYCOLOR)
|
||||
self.assertTrue(img is not None)
|
||||
self.assertTrue(meta_types is not None)
|
||||
self.assertTrue(meta_data is not None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
K-means clusterization test
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
from numpy import random
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
if PY3:
|
||||
xrange = range
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
def make_gaussians(cluster_n, img_size):
|
||||
points = []
|
||||
ref_distrs = []
|
||||
sizes = []
|
||||
for _ in xrange(cluster_n):
|
||||
mean = (0.1 + 0.8*random.rand(2)) * img_size
|
||||
a = (random.rand(2, 2)-0.5)*img_size*0.1
|
||||
cov = np.dot(a.T, a) + img_size*0.05*np.eye(2)
|
||||
n = 100 + random.randint(900)
|
||||
pts = random.multivariate_normal(mean, cov, n)
|
||||
points.append( pts )
|
||||
ref_distrs.append( (mean, cov) )
|
||||
sizes.append(n)
|
||||
points = np.float32( np.vstack(points) )
|
||||
return points, ref_distrs, sizes
|
||||
|
||||
def getMainLabelConfidence(labels, nLabels):
|
||||
|
||||
n = len(labels)
|
||||
labelsDict = dict.fromkeys(range(nLabels), 0)
|
||||
labelsConfDict = dict.fromkeys(range(nLabels))
|
||||
|
||||
for i in range(n):
|
||||
labelsDict[labels[i][0]] += 1
|
||||
|
||||
for i in range(nLabels):
|
||||
labelsConfDict[i] = float(labelsDict[i]) / n
|
||||
|
||||
return max(labelsConfDict.values())
|
||||
|
||||
class kmeans_test(NewOpenCVTests):
|
||||
|
||||
def test_kmeans(self):
|
||||
|
||||
np.random.seed(10)
|
||||
|
||||
cluster_n = 5
|
||||
img_size = 512
|
||||
|
||||
points, _, clusterSizes = make_gaussians(cluster_n, img_size)
|
||||
|
||||
term_crit = (cv.TERM_CRITERIA_EPS, 30, 0.1)
|
||||
_ret, labels, centers = cv.kmeans(points, cluster_n, None, term_crit, 10, 0)
|
||||
|
||||
self.assertEqual(len(centers), cluster_n)
|
||||
|
||||
offset = 0
|
||||
for i in range(cluster_n):
|
||||
confidence = getMainLabelConfidence(labels[offset : (offset + clusterSizes[i])], cluster_n)
|
||||
offset += clusterSizes[i]
|
||||
self.assertGreater(confidence, 0.9)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class Hackathon244Tests(NewOpenCVTests):
|
||||
|
||||
def test_int_array(self):
|
||||
a = np.array([-1, 2, -3, 4, -5])
|
||||
absa0 = np.abs(a)
|
||||
self.assertTrue(cv.norm(a, cv.NORM_L1) == 15)
|
||||
absa1 = cv.absdiff(a, 0)
|
||||
self.assertEqual(cv.norm(absa1, absa0, cv.NORM_INF), 0)
|
||||
|
||||
def test_imencode(self):
|
||||
a = np.zeros((480, 640), dtype=np.uint8)
|
||||
flag, ajpg = cv.imencode("img_q90.jpg", a, [cv.IMWRITE_JPEG_QUALITY, 90])
|
||||
self.assertEqual(flag, True)
|
||||
self.assertEqual(ajpg.dtype, np.uint8)
|
||||
self.assertTrue(isinstance(ajpg, np.ndarray), "imencode returned buffer of wrong type: {}".format(type(ajpg)))
|
||||
self.assertEqual(len(ajpg.shape), 1, "imencode returned buffer with wrong shape: {}".format(ajpg.shape))
|
||||
self.assertGreaterEqual(len(ajpg), 1, "imencode length of the returned buffer should be at least 1")
|
||||
self.assertLessEqual(
|
||||
len(ajpg), a.size,
|
||||
"imencode length of the returned buffer shouldn't exceed number of elements in original image"
|
||||
)
|
||||
|
||||
def test_projectPoints(self):
|
||||
objpt = np.float64([[1,2,3]])
|
||||
imgpt0, jac0 = cv.projectPoints(objpt, np.zeros(3), np.zeros(3), np.eye(3), np.float64([]))
|
||||
imgpt1, jac1 = cv.projectPoints(objpt, np.zeros(3), np.zeros(3), np.eye(3), None)
|
||||
self.assertEqual(imgpt0.shape, (objpt.shape[0], 1, 2))
|
||||
self.assertEqual(imgpt1.shape, imgpt0.shape)
|
||||
self.assertEqual(jac0.shape, jac1.shape)
|
||||
self.assertEqual(jac0.shape[0], 2*objpt.shape[0])
|
||||
|
||||
def test_estimateAffine3D(self):
|
||||
pattern_size = (11, 8)
|
||||
pattern_points = np.zeros((np.prod(pattern_size), 3), np.float32)
|
||||
pattern_points[:,:2] = np.indices(pattern_size).T.reshape(-1, 2)
|
||||
pattern_points *= 10
|
||||
(retval, out, inliers) = cv.estimateAffine3D(pattern_points, pattern_points)
|
||||
self.assertEqual(retval, 1)
|
||||
if cv.norm(out[2,:]) < 1e-3:
|
||||
out[2,2]=1
|
||||
self.assertLess(cv.norm(out, np.float64([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])), 1e-3)
|
||||
self.assertEqual(cv.countNonZero(inliers), pattern_size[0]*pattern_size[1])
|
||||
|
||||
def test_fast(self):
|
||||
fd = cv.FastFeatureDetector_create(30, True)
|
||||
img = self.get_sample("samples/data/right02.jpg", 0)
|
||||
img = cv.medianBlur(img, 3)
|
||||
keypoints = fd.detect(img)
|
||||
self.assertTrue(600 <= len(keypoints) <= 700)
|
||||
for kpt in keypoints:
|
||||
self.assertNotEqual(kpt.response, 0)
|
||||
|
||||
def check_close_angles(self, a, b, angle_delta):
|
||||
self.assertTrue(abs(a - b) <= angle_delta or
|
||||
abs(360 - abs(a - b)) <= angle_delta)
|
||||
|
||||
def check_close_pairs(self, a, b, delta):
|
||||
self.assertLessEqual(abs(a[0] - b[0]), delta)
|
||||
self.assertLessEqual(abs(a[1] - b[1]), delta)
|
||||
|
||||
def check_close_boxes(self, a, b, delta, angle_delta):
|
||||
self.check_close_pairs(a[0], b[0], delta)
|
||||
self.check_close_pairs(a[1], b[1], delta)
|
||||
self.check_close_angles(a[2], b[2], angle_delta)
|
||||
|
||||
def test_geometry(self):
|
||||
npt = 100
|
||||
np.random.seed(244)
|
||||
a = np.random.randn(npt,2).astype('float32')*50 + 150
|
||||
|
||||
be = cv.fitEllipse(a)
|
||||
br = cv.minAreaRect(a)
|
||||
mc, mr = cv.minEnclosingCircle(a)
|
||||
|
||||
be0 = ((150.2511749267578, 150.77322387695312), (158.024658203125, 197.57696533203125), 37.57804489135742)
|
||||
br0 = ((161.2974090576172, 154.41793823242188), (199.2301483154297, 207.7177734375), -9.164555549621582)
|
||||
mc0, mr0 = (160.41790771484375, 144.55152893066406), 136.713500977
|
||||
|
||||
self.check_close_boxes(be, be0, 5, 15)
|
||||
self.check_close_boxes(br, br0, 5, 15)
|
||||
self.check_close_pairs(mc, mc0, 5)
|
||||
self.assertLessEqual(abs(mr - mr0), 5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
try:
|
||||
if sys.version_info[:2] < (3, 0):
|
||||
raise unittest.SkipTest('Python 2.x is not supported')
|
||||
|
||||
|
||||
class MatTest(NewOpenCVTests):
|
||||
|
||||
def test_mat_construct(self):
|
||||
data = np.random.random([10, 10, 3])
|
||||
|
||||
#print(np.ndarray.__dictoffset__) # 0
|
||||
#print(cv.Mat.__dictoffset__) # 88 (> 0)
|
||||
#print(cv.Mat) # <class cv2.Mat>
|
||||
#print(cv.Mat.__base__) # <class 'numpy.ndarray'>
|
||||
|
||||
mat_data0 = cv.Mat(data)
|
||||
assert isinstance(mat_data0, cv.Mat)
|
||||
assert isinstance(mat_data0, np.ndarray)
|
||||
self.assertEqual(mat_data0.wrap_channels, False)
|
||||
res0 = cv.utils.dumpInputArray(mat_data0)
|
||||
self.assertEqual(res0, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=300 dims(-1)=3 size(-1)=[10 10 3] type(-1)=CV_64FC1")
|
||||
|
||||
mat_data1 = cv.Mat(data, wrap_channels=True)
|
||||
assert isinstance(mat_data1, cv.Mat)
|
||||
assert isinstance(mat_data1, np.ndarray)
|
||||
self.assertEqual(mat_data1.wrap_channels, True)
|
||||
res1 = cv.utils.dumpInputArray(mat_data1)
|
||||
self.assertEqual(res1, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=100 dims(-1)=2 size(-1)=10x10 type(-1)=CV_64FC3")
|
||||
|
||||
mat_data2 = cv.Mat(mat_data1)
|
||||
assert isinstance(mat_data2, cv.Mat)
|
||||
assert isinstance(mat_data2, np.ndarray)
|
||||
self.assertEqual(mat_data2.wrap_channels, True) # fail if __array_finalize__ doesn't work
|
||||
res2 = cv.utils.dumpInputArray(mat_data2)
|
||||
self.assertEqual(res2, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=100 dims(-1)=2 size(-1)=10x10 type(-1)=CV_64FC3")
|
||||
|
||||
|
||||
def test_mat_construct_4d(self):
|
||||
data = np.random.random([5, 10, 10, 3])
|
||||
|
||||
mat_data0 = cv.Mat(data)
|
||||
assert isinstance(mat_data0, cv.Mat)
|
||||
assert isinstance(mat_data0, np.ndarray)
|
||||
self.assertEqual(mat_data0.wrap_channels, False)
|
||||
res0 = cv.utils.dumpInputArray(mat_data0)
|
||||
self.assertEqual(res0, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=1500 dims(-1)=4 size(-1)=[5 10 10 3] type(-1)=CV_64FC1")
|
||||
|
||||
mat_data1 = cv.Mat(data, wrap_channels=True)
|
||||
assert isinstance(mat_data1, cv.Mat)
|
||||
assert isinstance(mat_data1, np.ndarray)
|
||||
self.assertEqual(mat_data1.wrap_channels, True)
|
||||
res1 = cv.utils.dumpInputArray(mat_data1)
|
||||
self.assertEqual(res1, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=500 dims(-1)=3 size(-1)=[5 10 10] type(-1)=CV_64FC3")
|
||||
|
||||
mat_data2 = cv.Mat(mat_data1)
|
||||
assert isinstance(mat_data2, cv.Mat)
|
||||
assert isinstance(mat_data2, np.ndarray)
|
||||
self.assertEqual(mat_data2.wrap_channels, True) # __array_finalize__ doesn't work
|
||||
res2 = cv.utils.dumpInputArray(mat_data2)
|
||||
self.assertEqual(res2, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=500 dims(-1)=3 size(-1)=[5 10 10] type(-1)=CV_64FC3")
|
||||
|
||||
|
||||
def test_mat_wrap_channels_fail(self):
|
||||
data = np.random.random([2, 3, 4, 520])
|
||||
|
||||
mat_data0 = cv.Mat(data)
|
||||
assert isinstance(mat_data0, cv.Mat)
|
||||
assert isinstance(mat_data0, np.ndarray)
|
||||
self.assertEqual(mat_data0.wrap_channels, False)
|
||||
res0 = cv.utils.dumpInputArray(mat_data0)
|
||||
self.assertEqual(res0, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=12480 dims(-1)=4 size(-1)=[2 3 4 520] type(-1)=CV_64FC1")
|
||||
|
||||
with self.assertRaises(cv.error):
|
||||
mat_data1 = cv.Mat(data, wrap_channels=True) # argument unable to wrap channels, too high (520 > CV_CN_MAX=512)
|
||||
res1 = cv.utils.dumpInputArray(mat_data1)
|
||||
print(mat_data1.__dict__)
|
||||
print(res1)
|
||||
|
||||
|
||||
def test_mat_wrap_channels_zero(self):
|
||||
# Passing a 0-channel array must raise cv.error, not segfault.
|
||||
data = np.zeros((100, 100, 0), dtype=np.uint8)
|
||||
|
||||
with self.assertRaises(cv.error):
|
||||
cv.resize(data, (200, 200)) # channels=0 -> invalid, must not segfault
|
||||
|
||||
with self.assertRaises(cv.error):
|
||||
mat_data = cv.Mat(data, wrap_channels=True) # unable to wrap channels, invalid count (0)
|
||||
cv.utils.dumpInputArray(mat_data)
|
||||
|
||||
# Verify that channels=1 (lower bound) still works correctly
|
||||
data_1ch = np.zeros((100, 100, 1), dtype=np.uint8)
|
||||
mat_1ch = cv.Mat(data_1ch, wrap_channels=True)
|
||||
res = cv.utils.dumpInputArray(mat_1ch)
|
||||
self.assertIn("CV_8UC1", res)
|
||||
|
||||
def test_ufuncs(self):
|
||||
data = np.arange(10)
|
||||
mat_data = cv.Mat(data)
|
||||
mat_data2 = 2 * mat_data
|
||||
self.assertEqual(type(mat_data2), cv.Mat)
|
||||
np.testing.assert_equal(2 * data, 2 * mat_data)
|
||||
|
||||
|
||||
def test_comparison(self):
|
||||
# Undefined behavior, do NOT use that.
|
||||
# Behavior may be changed in the future
|
||||
|
||||
data = np.ones((10, 10, 3))
|
||||
mat_wrapped = cv.Mat(data, wrap_channels=True)
|
||||
mat_simple = cv.Mat(data)
|
||||
np.testing.assert_equal(mat_wrapped, mat_simple) # ???: wrap_channels is not checked for now
|
||||
np.testing.assert_equal(data, mat_simple)
|
||||
np.testing.assert_equal(data, mat_wrapped)
|
||||
|
||||
#self.assertEqual(mat_wrapped, mat_simple) # ???
|
||||
#self.assertTrue(mat_wrapped == mat_simple) # ???
|
||||
#self.assertTrue((mat_wrapped == mat_simple).all())
|
||||
|
||||
|
||||
except unittest.SkipTest as e:
|
||||
|
||||
message = str(e)
|
||||
|
||||
class TestSkip(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.skipTest('Skip tests: ' + message)
|
||||
|
||||
def test_skip():
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,988 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import ctypes
|
||||
from functools import partial
|
||||
from collections import namedtuple
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
from collections import Sequence
|
||||
else:
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests, unittest
|
||||
|
||||
|
||||
def is_numeric(dtype):
|
||||
return np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.floating)
|
||||
|
||||
|
||||
def get_limits(dtype):
|
||||
if not is_numeric(dtype):
|
||||
return None, None
|
||||
|
||||
if np.issubdtype(dtype, np.integer):
|
||||
info = np.iinfo(dtype)
|
||||
else:
|
||||
info = np.finfo(dtype)
|
||||
return info.min, info.max
|
||||
|
||||
|
||||
def get_conversion_error_msg(value, expected, actual):
|
||||
return 'Conversion "{}" of type "{}" failed\nExpected: "{}" vs Actual "{}"'.format(
|
||||
value, type(value).__name__, expected, actual
|
||||
)
|
||||
|
||||
|
||||
def get_no_exception_msg(value):
|
||||
return 'Exception is not risen for {} of type {}'.format(value, type(value).__name__)
|
||||
|
||||
|
||||
def rpad(src, dst_size, pad_value=0):
|
||||
"""Extend `src` up to `dst_size` with given value.
|
||||
|
||||
Args:
|
||||
src (np.ndarray | tuple | list): 1d array like object to pad.
|
||||
dst_size (_type_): Desired `src` size after padding.
|
||||
pad_value (int, optional): Padding value. Defaults to 0.
|
||||
|
||||
Returns:
|
||||
np.ndarray: 1d array with len == `dst_size`.
|
||||
"""
|
||||
src = np.asarray(src)
|
||||
if len(src.shape) != 1:
|
||||
raise ValueError("Only 1d arrays are supported")
|
||||
|
||||
# Considering the meaning, it is desirable to use np.pad().
|
||||
# However, the old numpy doesn't include the following fixes and cannot work as expected.
|
||||
# So an alternative fix that combines np.append() and np.fill() is used.
|
||||
# https://docs.scipy.org/doc/numpy-1.13.0/release.html#support-for-returning-arrays-of-arbitrary-dimensions-in-apply-along-axis
|
||||
|
||||
return np.append(src, np.full( dst_size - len(src), pad_value, dtype=src.dtype) )
|
||||
|
||||
def get_ocv_arithm_op_table(apply_saturation=False):
|
||||
def saturate(func):
|
||||
def wrapped_func(x, y):
|
||||
dst_dtype = x.dtype
|
||||
if apply_saturation:
|
||||
if np.issubdtype(x.dtype, np.integer):
|
||||
x = x.astype(np.int64)
|
||||
# Apply padding or truncation for array-like `y` inputs
|
||||
if not isinstance(y, (float, int)):
|
||||
if len(y) > x.shape[-1]:
|
||||
y = y[:x.shape[-1]]
|
||||
else:
|
||||
y = rpad(y, x.shape[-1], pad_value=0)
|
||||
|
||||
dst = func(x, y)
|
||||
if apply_saturation:
|
||||
min_val, max_val = get_limits(dst_dtype)
|
||||
dst = np.clip(dst, min_val, max_val)
|
||||
return dst.astype(dst_dtype)
|
||||
return wrapped_func
|
||||
|
||||
@saturate
|
||||
def subtract(x, y):
|
||||
return x - y
|
||||
|
||||
@saturate
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
@saturate
|
||||
def divide(x, y):
|
||||
if not isinstance(y, (int, float)):
|
||||
dst_dtype = np.result_type(x, y)
|
||||
y = np.array(y).astype(dst_dtype)
|
||||
_, max_value = get_limits(dst_dtype)
|
||||
y[y == 0] = max_value
|
||||
|
||||
# to compatible between python2 and python3, it calicurates with float.
|
||||
# python2: int / int = int
|
||||
# python3: int / int = float
|
||||
dst = 1.0 * x / y
|
||||
|
||||
if np.issubdtype(x.dtype, np.integer):
|
||||
dst = np.rint(dst)
|
||||
return dst
|
||||
|
||||
@saturate
|
||||
def multiply(x, y):
|
||||
return x * y
|
||||
|
||||
@saturate
|
||||
def absdiff(x, y):
|
||||
res = np.abs(x - y)
|
||||
return res
|
||||
|
||||
return {
|
||||
cv.subtract: subtract,
|
||||
cv.add: add,
|
||||
cv.multiply: multiply,
|
||||
cv.divide: divide,
|
||||
cv.absdiff: absdiff
|
||||
}
|
||||
|
||||
|
||||
class Bindings(NewOpenCVTests):
|
||||
|
||||
def test_inheritance(self):
|
||||
bm = cv.StereoBM_create()
|
||||
bm.getPreFilterCap() # from StereoBM
|
||||
bm.getBlockSize() # from SteroMatcher
|
||||
|
||||
def test_raiseGeneralException(self):
|
||||
with self.assertRaises((cv.error,),
|
||||
msg='C++ exception is not propagated to Python in the right way') as cm:
|
||||
cv.utils.testRaiseGeneralException()
|
||||
self.assertEqual(str(cm.exception), 'exception text')
|
||||
|
||||
def test_redirectError(self):
|
||||
try:
|
||||
cv.imshow("", None) # This causes an assert
|
||||
self.assertEqual("Dead code", 0)
|
||||
except cv.error as _e:
|
||||
pass
|
||||
|
||||
handler_called = [False]
|
||||
|
||||
def test_error_handler(status, func_name, err_msg, file_name, line):
|
||||
handler_called[0] = True
|
||||
|
||||
cv.redirectError(test_error_handler)
|
||||
try:
|
||||
cv.imshow("", None) # This causes an assert
|
||||
self.assertEqual("Dead code", 0)
|
||||
except cv.error as _e:
|
||||
self.assertEqual(handler_called[0], True)
|
||||
pass
|
||||
|
||||
cv.redirectError(None)
|
||||
try:
|
||||
cv.imshow("", None) # This causes an assert
|
||||
self.assertEqual("Dead code", 0)
|
||||
except cv.error as _e:
|
||||
pass
|
||||
|
||||
def test_overload_resolution_can_choose_correct_overload(self):
|
||||
val = 123
|
||||
point = (51, 165)
|
||||
self.assertEqual(cv.utils.testOverloadResolution(val, point),
|
||||
'overload (int={}, point=(x={}, y={}))'.format(val, *point),
|
||||
"Can't select first overload if all arguments are provided as positional")
|
||||
|
||||
self.assertEqual(cv.utils.testOverloadResolution(val, point=point),
|
||||
'overload (int={}, point=(x={}, y={}))'.format(val, *point),
|
||||
"Can't select first overload if one of the arguments are provided as keyword")
|
||||
|
||||
self.assertEqual(cv.utils.testOverloadResolution(val),
|
||||
'overload (int={}, point=(x=42, y=24))'.format(val),
|
||||
"Can't select first overload if one of the arguments has default value")
|
||||
|
||||
rect = (1, 5, 10, 23)
|
||||
self.assertEqual(cv.utils.testOverloadResolution(rect),
|
||||
'overload (rect=(x={}, y={}, w={}, h={}))'.format(*rect),
|
||||
"Can't select second overload if all arguments are provided")
|
||||
|
||||
def test_overload_resolution_fails(self):
|
||||
def test_overload_resolution(msg, *args, **kwargs):
|
||||
no_exception_msg = 'Overload resolution failed without any exception for: "{}"'.format(msg)
|
||||
wrong_exception_msg = 'Overload resolution failed with wrong exception type for: "{}"'.format(msg)
|
||||
with self.assertRaises((cv.error, Exception), msg=no_exception_msg) as cm:
|
||||
res = cv.utils.testOverloadResolution(*args, **kwargs)
|
||||
self.fail("Unexpected result for {}: '{}'".format(msg, res))
|
||||
self.assertEqual(type(cm.exception), cv.error, wrong_exception_msg)
|
||||
|
||||
test_overload_resolution('wrong second arg type (keyword arg)', 5, point=(1, 2, 3))
|
||||
test_overload_resolution('wrong second arg type', 5, 2)
|
||||
test_overload_resolution('wrong first arg', 3.4, (12, 21))
|
||||
test_overload_resolution('wrong first arg, no second arg', 4.5)
|
||||
test_overload_resolution('wrong args number for first overload', 3, (12, 21), 123)
|
||||
test_overload_resolution('wrong args number for second overload', (3, 12, 12, 1), (12, 21))
|
||||
# One of the common problems
|
||||
test_overload_resolution('rect with float coordinates', (4.5, 4, 2, 1))
|
||||
test_overload_resolution('rect with wrong number of coordinates', (4, 4, 1))
|
||||
|
||||
def test_properties_with_reserved_keywords_names_are_transformed(self):
|
||||
obj = cv.utils.ClassWithKeywordProperties(except_arg=23)
|
||||
self.assertTrue(hasattr(obj, "lambda_"),
|
||||
msg="Class doesn't have RW property with converted name")
|
||||
try:
|
||||
obj.lambda_ = 32
|
||||
except Exception as e:
|
||||
self.fail("Failed to set value to RW property. Error: {}".format(e))
|
||||
|
||||
self.assertTrue(hasattr(obj, "except_"),
|
||||
msg="Class doesn't have readonly property with converted name")
|
||||
self.assertEqual(obj.except_, 23,
|
||||
msg="Can't access readonly property value")
|
||||
with self.assertRaises(AttributeError):
|
||||
obj.except_ = 32
|
||||
|
||||
def test_maketype(self):
|
||||
data = {
|
||||
cv.CV_8UC3: [cv.CV_8U, 3, cv.CV_8UC],
|
||||
cv.CV_16SC1: [cv.CV_16S, 1, cv.CV_16SC],
|
||||
cv.CV_32FC4: [cv.CV_32F, 4, cv.CV_32FC],
|
||||
cv.CV_64FC2: [cv.CV_64F, 2, cv.CV_64FC],
|
||||
cv.CV_8SC4: [cv.CV_8S, 4, cv.CV_8SC],
|
||||
cv.CV_16UC2: [cv.CV_16U, 2, cv.CV_16UC],
|
||||
cv.CV_32SC1: [cv.CV_32S, 1, cv.CV_32SC],
|
||||
cv.CV_16FC3: [cv.CV_16F, 3, cv.CV_16FC],
|
||||
cv.CV_BoolC1: [cv.CV_Bool, 1, cv.CV_BoolC],
|
||||
}
|
||||
for ref, (depth, channels, func) in data.items():
|
||||
self.assertEqual(ref, cv.CV_MAKETYPE(depth, channels))
|
||||
self.assertEqual(ref, func(channels))
|
||||
|
||||
|
||||
class Arguments(NewOpenCVTests):
|
||||
|
||||
def _try_to_convert(self, conversion, value):
|
||||
try:
|
||||
result = conversion(value).lower()
|
||||
except Exception as e:
|
||||
self.fail(
|
||||
'{} "{}" is risen for conversion {} of type {}'.format(
|
||||
type(e).__name__, e, value, type(value).__name__
|
||||
)
|
||||
)
|
||||
else:
|
||||
return result
|
||||
|
||||
def test_InputArray(self):
|
||||
res1 = cv.utils.dumpInputArray(None)
|
||||
# self.assertEqual(res1, "InputArray: noArray()") # not supported
|
||||
self.assertEqual(res1, "InputArray: empty()=true kind=0x00010000 flags=0x01010000 total(-1)=0 dims(-1)=0 size(-1)=0x0 type(-1)=CV_8UC1")
|
||||
res2_1 = cv.utils.dumpInputArray((1, 2))
|
||||
self.assertEqual(res2_1, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=2 dims(-1)=2 size(-1)=1x2 type(-1)=CV_64FC1")
|
||||
res2_2 = cv.utils.dumpInputArray(1.5) # Scalar(1.5, 1.5, 1.5, 1.5)
|
||||
self.assertEqual(res2_2, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=4 dims(-1)=2 size(-1)=1x4 type(-1)=CV_64FC1")
|
||||
a = np.array([[1, 2], [3, 4], [5, 6]])
|
||||
res3 = cv.utils.dumpInputArray(a) # 32SC1
|
||||
self.assertEqual(res3, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=6 dims(-1)=2 size(-1)=2x3 type(-1)=CV_32SC1")
|
||||
a = np.array([[[1, 2], [3, 4], [5, 6]]], dtype='f')
|
||||
res4 = cv.utils.dumpInputArray(a) # 32FC2
|
||||
self.assertEqual(res4, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=3 dims(-1)=2 size(-1)=3x1 type(-1)=CV_32FC2")
|
||||
a = np.array([[[1, 2]], [[3, 4]], [[5, 6]]], dtype=float)
|
||||
res5 = cv.utils.dumpInputArray(a) # 64FC2
|
||||
self.assertEqual(res5, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=3 dims(-1)=2 size(-1)=1x3 type(-1)=CV_64FC2")
|
||||
a = np.zeros((2,3,4), dtype='f')
|
||||
res6 = cv.utils.dumpInputArray(a)
|
||||
self.assertEqual(res6, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=6 dims(-1)=2 size(-1)=3x2 type(-1)=CV_32FC4")
|
||||
a = np.zeros((2,3,4,5), dtype='f')
|
||||
res7 = cv.utils.dumpInputArray(a)
|
||||
self.assertEqual(res7, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=120 dims(-1)=4 size(-1)=[2 3 4 5] type(-1)=CV_32FC1")
|
||||
a = np.array([0, 1, 0, 1], dtype=bool)
|
||||
res8 = cv.utils.dumpInputArray(a)
|
||||
self.assertEqual(res8, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=4 dims(-1)=1 size(-1)=4x1 type(-1)=CV_BoolC1")
|
||||
a = np.array(3.14, dtype=np.float32)
|
||||
res9 = cv.utils.dumpInputArray(a)
|
||||
self.assertEqual(res9, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=1 dims(-1)=0 size(-1)=1x1 type(-1)=CV_32FC1")
|
||||
|
||||
def test_InputArrayOfArrays(self):
|
||||
res1 = cv.utils.dumpInputArrayOfArrays(None)
|
||||
# self.assertEqual(res1, "InputArray: noArray()") # not supported
|
||||
self.assertEqual(res1, "InputArrayOfArrays: empty()=true kind=0x00050000 flags=0x01050000 total(-1)=0 dims(-1)=1 size(-1)=0x0")
|
||||
res2_1 = cv.utils.dumpInputArrayOfArrays((1, 2)) # { Scalar:all(1), Scalar::all(2) }
|
||||
self.assertEqual(res2_1, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_64FC1 dims(0)=2 size(0)=1x4")
|
||||
res2_2 = cv.utils.dumpInputArrayOfArrays([1.5])
|
||||
self.assertEqual(res2_2, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=1 dims(-1)=1 size(-1)=1x1 type(0)=CV_64FC1 dims(0)=2 size(0)=1x4")
|
||||
a = np.array([[1, 2], [3, 4], [5, 6]])
|
||||
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
res3 = cv.utils.dumpInputArrayOfArrays([a, b])
|
||||
self.assertEqual(res3, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_32SC1 dims(0)=2 size(0)=2x3")
|
||||
c = np.array([[[1, 2], [3, 4], [5, 6]]], dtype='f')
|
||||
res4 = cv.utils.dumpInputArrayOfArrays([c, a, b])
|
||||
self.assertEqual(res4, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=3 dims(-1)=1 size(-1)=3x1 type(0)=CV_32FC2 dims(0)=2 size(0)=3x1")
|
||||
a = np.zeros((2,3,4), dtype='f')
|
||||
res5 = cv.utils.dumpInputArrayOfArrays([a, b])
|
||||
self.assertEqual(res5, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_32FC4 dims(0)=2 size(0)=3x2")
|
||||
# TODO: fix conversion error
|
||||
#a = np.zeros((2,3,4,5), dtype='f')
|
||||
#res6 = cv.utils.dumpInputArray([a, b])
|
||||
#self.assertEqual(res6, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_32FC1 dims(0)=4 size(0)=[2 3 4 5]")
|
||||
|
||||
def test_unsupported_numpy_data_types_string_description(self):
|
||||
for dtype in (object, str, np.complex128):
|
||||
test_array = np.zeros((4, 4, 3), dtype=dtype)
|
||||
msg = ".*type = {} is not supported".format(test_array.dtype)
|
||||
if sys.version_info[0] < 3:
|
||||
self.assertRaisesRegexp(
|
||||
Exception, msg, cv.utils.dumpInputArray, test_array
|
||||
)
|
||||
else:
|
||||
self.assertRaisesRegex(
|
||||
Exception, msg, cv.utils.dumpInputArray, test_array
|
||||
)
|
||||
|
||||
def test_numpy_writeable_flag_is_preserved(self):
|
||||
array = np.zeros((10, 10, 1), dtype=np.uint8)
|
||||
array.setflags(write=False)
|
||||
with self.assertRaises(Exception):
|
||||
cv.rectangle(array, (0, 0), (5, 5), (255), 2)
|
||||
|
||||
def test_20968(self):
|
||||
pixel = np.uint8([[[40, 50, 200]]])
|
||||
_ = cv.cvtColor(pixel, cv.COLOR_RGB2BGR) # should not raise exception
|
||||
|
||||
def test_parse_to_bool_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpBool)
|
||||
for convertible_true in (True, 1, 64, np.int8(123), np.int16(11), np.int32(2),
|
||||
np.int64(1), np.bool_(12)):
|
||||
actual = try_to_convert(convertible_true)
|
||||
self.assertEqual('bool: true', actual,
|
||||
msg=get_conversion_error_msg(convertible_true, 'bool: true', actual))
|
||||
|
||||
for convertible_false in (False, 0, np.uint8(0), np.bool_(0), np.int_(0)):
|
||||
actual = try_to_convert(convertible_false)
|
||||
self.assertEqual('bool: false', actual,
|
||||
msg=get_conversion_error_msg(convertible_false, 'bool: false', actual))
|
||||
|
||||
def test_parse_to_bool_not_convertible(self):
|
||||
for not_convertible in (1.2, np.float32(2.3), 's', 'str', (1, 2), [1, 2], complex(1, 1),
|
||||
complex(imag=2), complex(1.1)):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpBool(not_convertible)
|
||||
|
||||
def test_parse_to_bool_convertible_extra(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpBool)
|
||||
_, max_size_t = get_limits(ctypes.c_size_t)
|
||||
for convertible_true in (-1, max_size_t):
|
||||
actual = try_to_convert(convertible_true)
|
||||
self.assertEqual('bool: true', actual,
|
||||
msg=get_conversion_error_msg(convertible_true, 'bool: true', actual))
|
||||
|
||||
def test_parse_to_bool_not_convertible_extra(self):
|
||||
for not_convertible in (np.array([False]), np.array([True])):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpBool(not_convertible)
|
||||
|
||||
def test_parse_to_int_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpInt)
|
||||
min_int, max_int = get_limits(ctypes.c_int)
|
||||
for convertible in (-10, -1, 2, int(43.2), np.uint8(15), np.int8(33), np.int16(-13),
|
||||
np.int32(4), np.int64(345), (23), min_int, max_int, np.int_(33)):
|
||||
expected = 'int: {0:d}'.format(convertible)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_int_not_convertible(self):
|
||||
min_int, max_int = get_limits(ctypes.c_int)
|
||||
for not_convertible in (1.2, float(3), np.float32(4), np.double(45), 's', 'str',
|
||||
np.array([1, 2]), (1,), [1, 2], min_int - 1, max_int + 1,
|
||||
complex(1, 1), complex(imag=2), complex(1.1)):
|
||||
with self.assertRaises((TypeError, OverflowError, ValueError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpInt(not_convertible)
|
||||
|
||||
def test_parse_to_int_not_convertible_extra(self):
|
||||
for not_convertible in (np.bool_(True), True, False, np.float32(2.3),
|
||||
np.array([3, ], dtype=int), np.array([-2, ], dtype=np.int32),
|
||||
np.array([11, ], dtype=np.uint8)):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpInt(not_convertible)
|
||||
|
||||
def test_parse_to_int64_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpInt64)
|
||||
min_int64, max_int64 = get_limits(ctypes.c_longlong)
|
||||
for convertible in (-10, -1, 2, int(43.2), np.uint8(15), np.int8(33), np.int16(-13),
|
||||
np.int32(4), np.int64(345), (23), min_int64, max_int64, np.int_(33)):
|
||||
expected = 'int64: {0:d}'.format(convertible)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_int64_not_convertible(self):
|
||||
min_int64, max_int64 = get_limits(ctypes.c_longlong)
|
||||
for not_convertible in (1.2, np.float32(4), float(3), np.double(45), 's', 'str',
|
||||
np.array([1, 2]), (1,), [1, 2], min_int64 - 1, max_int64 + 1,
|
||||
complex(1, 1), complex(imag=2), complex(1.1), np.bool_(True),
|
||||
True, False, np.float32(2.3), np.array([3, ], dtype=int),
|
||||
np.array([-2, ], dtype=np.int32), np.array([11, ], dtype=np.uint8)):
|
||||
with self.assertRaises((TypeError, OverflowError, ValueError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpInt64(not_convertible)
|
||||
|
||||
def test_parse_to_size_t_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpSizeT)
|
||||
_, max_uint = get_limits(ctypes.c_uint)
|
||||
for convertible in (2, max_uint, (12), np.uint8(34), np.int8(12), np.int16(23),
|
||||
np.int32(123), np.int64(344), np.uint64(3), np.uint16(2), np.uint32(5),
|
||||
np.uint(44)):
|
||||
expected = 'size_t: {0:d}'.format(convertible).lower()
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_size_t_not_convertible(self):
|
||||
min_long, _ = get_limits(ctypes.c_long)
|
||||
for not_convertible in (1.2, True, False, np.bool_(True), np.float32(4), float(3),
|
||||
np.double(45), 's', 'str', np.array([1, 2]), (1,), [1, 2],
|
||||
np.float64(6), complex(1, 1), complex(imag=2), complex(1.1),
|
||||
-1, min_long, np.int8(-35)):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpSizeT(not_convertible)
|
||||
|
||||
def test_parse_to_size_t_convertible_extra(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpSizeT)
|
||||
_, max_size_t = get_limits(ctypes.c_size_t)
|
||||
for convertible in (max_size_t,):
|
||||
expected = 'size_t: {0:d}'.format(convertible).lower()
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_size_t_not_convertible_extra(self):
|
||||
for not_convertible in (np.bool_(True), True, False, np.array([123, ], dtype=np.uint8),):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpSizeT(not_convertible)
|
||||
|
||||
def test_parse_to_float_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpFloat)
|
||||
min_float, max_float = get_limits(ctypes.c_float)
|
||||
for convertible in (2, -13, 1.24, np.float32(32.45), float(32), np.double(12.23),
|
||||
np.float32(-12.3), np.float64(3.22), min_float,
|
||||
max_float, np.inf, -np.inf, float('Inf'), -float('Inf'),
|
||||
np.double(np.inf), np.double(-np.inf), np.double(float('Inf')),
|
||||
np.double(-float('Inf'))):
|
||||
expected = 'Float: {0:.2f}'.format(convertible).lower()
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
# Workaround for Windows NaN tests due to Visual C runtime
|
||||
# special floating point values (indefinite NaN)
|
||||
for nan in (float('NaN'), np.nan, np.float32(np.nan), np.double(np.nan),
|
||||
np.double(float('NaN'))):
|
||||
actual = try_to_convert(nan)
|
||||
self.assertIn('nan', actual, msg="Can't convert nan of type {} to float. "
|
||||
"Actual: {}".format(type(nan).__name__, actual))
|
||||
|
||||
min_double, max_double = get_limits(ctypes.c_double)
|
||||
for inf in (min_float * 10, max_float * 10, min_double, max_double):
|
||||
expected = 'float: {}inf'.format('-' if inf < 0 else '')
|
||||
actual = try_to_convert(inf)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(inf, expected, actual))
|
||||
|
||||
def test_parse_to_float_not_convertible(self):
|
||||
for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=float),
|
||||
np.array([1, 2], dtype=np.double), complex(1, 1), complex(imag=2),
|
||||
complex(1.1)):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpFloat(not_convertible)
|
||||
|
||||
def test_parse_to_float_not_convertible_extra(self):
|
||||
for not_convertible in (np.bool_(False), True, False, np.array([123, ], dtype=int),
|
||||
np.array([1., ]), np.array([False]),
|
||||
np.array([True])):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpFloat(not_convertible)
|
||||
|
||||
def test_parse_to_double_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpDouble)
|
||||
min_float, max_float = get_limits(ctypes.c_float)
|
||||
min_double, max_double = get_limits(ctypes.c_double)
|
||||
for convertible in (2, -13, 1.24, np.float32(32.45), float(2), np.double(12.23),
|
||||
np.float32(-12.3), np.float64(3.22), min_float,
|
||||
max_float, min_double, max_double, np.inf, -np.inf, float('Inf'),
|
||||
-float('Inf'), np.double(np.inf), np.double(-np.inf),
|
||||
np.double(float('Inf')), np.double(-float('Inf'))):
|
||||
expected = 'Double: {0:.2f}'.format(convertible).lower()
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
# Workaround for Windows NaN tests due to Visual C runtime
|
||||
# special floating point values (indefinite NaN)
|
||||
for nan in (float('NaN'), np.nan, np.double(np.nan),
|
||||
np.double(float('NaN'))):
|
||||
actual = try_to_convert(nan)
|
||||
self.assertIn('nan', actual, msg="Can't convert nan of type {} to double. "
|
||||
"Actual: {}".format(type(nan).__name__, actual))
|
||||
|
||||
def test_parse_to_double_not_convertible(self):
|
||||
for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=np.float32),
|
||||
np.array([1, 2], dtype=np.double), complex(1, 1), complex(imag=2),
|
||||
complex(1.1)):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpDouble(not_convertible)
|
||||
|
||||
def test_parse_to_double_not_convertible_extra(self):
|
||||
for not_convertible in (np.bool_(False), True, False, np.array([123, ], dtype=int),
|
||||
np.array([1., ]), np.array([False]),
|
||||
np.array([12.4], dtype=np.double), np.array([True])):
|
||||
with self.assertRaises((TypeError, OverflowError),
|
||||
msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpDouble(not_convertible)
|
||||
|
||||
def test_parse_to_cstring_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpCString)
|
||||
for convertible in ('', 's', 'str', str(123), ('char'), np.str_('test2')):
|
||||
expected = 'string: ' + convertible
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_cstring_not_convertible(self):
|
||||
for not_convertible in ((12,), ('t', 'e', 's', 't'), np.array(['123', ]),
|
||||
np.array(['t', 'e', 's', 't']), 1, -1.4, True, False, None):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpCString(not_convertible)
|
||||
|
||||
def test_parse_to_string_convertible(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpString)
|
||||
for convertible in (None, '', 's', 'str', str(123), np.str_('test2')):
|
||||
expected = 'string: ' + (convertible if convertible else '')
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_string_not_convertible(self):
|
||||
for not_convertible in ((12,), ('t', 'e', 's', 't'), np.array(['123', ]),
|
||||
np.array(['t', 'e', 's', 't']), 1, True, False):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpString(not_convertible)
|
||||
|
||||
def test_parse_to_rect_convertible(self):
|
||||
Rect = namedtuple('Rect', ('x', 'y', 'w', 'h'))
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpRect)
|
||||
for convertible in ((1, 2, 4, 5), [5, 3, 10, 20], np.array([10, 20, 23, 10]),
|
||||
Rect(10, 30, 40, 55), tuple(np.array([40, 20, 24, 20])),
|
||||
list(np.array([20, 40, 30, 35]))):
|
||||
expected = 'rect: (x={}, y={}, w={}, h={})'.format(*convertible)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_rect_not_convertible(self):
|
||||
for not_convertible in (np.empty(shape=(4, 1)), (), [], np.array([]), (12, ),
|
||||
[3, 4, 5, 10, 123], {1: 2, 3:4, 5:10, 6:30},
|
||||
'1234', np.array([1, 2, 3, 4], dtype=np.float32),
|
||||
np.array([[1, 2], [3, 4], [5, 6], [6, 8]]), (1, 2, 5, 1.5)):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpRect(not_convertible)
|
||||
|
||||
def test_parse_to_rotated_rect_convertible(self):
|
||||
RotatedRect = namedtuple('RotatedRect', ('center', 'size', 'angle'))
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpRotatedRect)
|
||||
for convertible in (((2.5, 2.5), (10., 20.), 12.5), [[1.5, 10.5], (12.5, 51.5), 10],
|
||||
RotatedRect((10, 40), np.array([10.5, 20.5]), 5),
|
||||
np.array([[10, 6], [50, 50], 5.5], dtype=object)):
|
||||
center, size, angle = convertible
|
||||
expected = 'rotated_rect: (c_x={:.6f}, c_y={:.6f}, w={:.6f},' \
|
||||
' h={:.6f}, a={:.6f})'.format(center[0], center[1],
|
||||
size[0], size[1], angle)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
|
||||
def test_wrap_rotated_rect(self):
|
||||
center = (34.5, 52.)
|
||||
size = (565.0, 140.0)
|
||||
angle = -177.5
|
||||
rect1 = cv.RotatedRect(center, size, angle)
|
||||
self.assertEqual(rect1.center, center)
|
||||
self.assertEqual(rect1.size, size)
|
||||
self.assertEqual(rect1.angle, angle)
|
||||
|
||||
pts = [[ 319.7845, -5.6109037],
|
||||
[ 313.6778, 134.25586],
|
||||
[-250.78448, 109.6109],
|
||||
[-244.6778, -30.25586]]
|
||||
self.assertLess(np.max(np.abs(rect1.points() - pts)), 1e-4)
|
||||
|
||||
rect2 = cv.RotatedRect(pts[0], pts[1], pts[2])
|
||||
_, inter_pts = cv.rotatedRectangleIntersection(rect1, rect2)
|
||||
self.assertLess(np.max(np.abs(inter_pts.reshape(-1, 2) - pts)), 1e-4)
|
||||
|
||||
def test_result_rotated_rect_boundingRect2f(self):
|
||||
center = (0, 0)
|
||||
size = (10, 10)
|
||||
angle = 0
|
||||
gold_box = (-5.0, -5.0, 10.0, 10.0)
|
||||
rect1 = cv.RotatedRect(center, size, angle)
|
||||
bbox = rect1.boundingRect2f()
|
||||
self.assertEqual(gold_box, bbox)
|
||||
|
||||
def test_parse_to_rotated_rect_not_convertible(self):
|
||||
for not_convertible in ([], (), np.array([]), (123, (45, 34), 1), {1: 2, 3: 4}, 123,
|
||||
np.array([[123, 123, 14], [1, 3], 56], dtype=object), '123'):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpRotatedRect(not_convertible)
|
||||
|
||||
def test_parse_to_term_criteria_convertible(self):
|
||||
TermCriteria = namedtuple('TermCriteria', ('type', 'max_count', 'epsilon'))
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpTermCriteria)
|
||||
for convertible in ((1, 10, 1e-3), [2, 30, 1e-1], np.array([10, 20, 0.5], dtype=object),
|
||||
TermCriteria(0, 5, 0.1)):
|
||||
expected = 'term_criteria: (type={}, max_count={}, epsilon={:.6f}'.format(*convertible)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_term_criteria_not_convertible(self):
|
||||
for not_convertible in ([], (), np.array([]), [1, 4], (10,), (1.5, 34, 0.1),
|
||||
{1: 5, 3: 5, 10: 10}, '145'):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpTermCriteria(not_convertible)
|
||||
|
||||
def test_parse_to_range_convertible_to_all(self):
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpRange)
|
||||
for convertible in ((), [], np.array([])):
|
||||
expected = 'range: all'
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_range_convertible(self):
|
||||
Range = namedtuple('Range', ('start', 'end'))
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpRange)
|
||||
for convertible in ((10, 20), [-1, 3], np.array([10, 24]), Range(-4, 6)):
|
||||
expected = 'range: (s={}, e={})'.format(*convertible)
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_to_range_not_convertible(self):
|
||||
for not_convertible in ((1, ), [40, ], np.array([1, 4, 6]), {'a': 1, 'b': 40},
|
||||
(1.5, 13.5), [3, 6.7], np.array([6.3, 2.1]), '14, 4'):
|
||||
with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpRange(not_convertible)
|
||||
|
||||
def test_reserved_keywords_are_transformed(self):
|
||||
default_lambda_value = 2
|
||||
default_from_value = 3
|
||||
format_str = "arg={}, lambda={}, from={}"
|
||||
self.assertEqual(
|
||||
cv.utils.testReservedKeywordConversion(20), format_str.format(20, default_lambda_value, default_from_value)
|
||||
)
|
||||
self.assertEqual(
|
||||
cv.utils.testReservedKeywordConversion(10, lambda_=10), format_str.format(10, 10, default_from_value)
|
||||
)
|
||||
self.assertEqual(
|
||||
cv.utils.testReservedKeywordConversion(10, from_=10), format_str.format(10, default_lambda_value, 10)
|
||||
)
|
||||
self.assertEqual(
|
||||
cv.utils.testReservedKeywordConversion(20, lambda_=-4, from_=12), format_str.format(20, -4, 12)
|
||||
)
|
||||
|
||||
def test_parse_vector_int_convertible(self):
|
||||
np.random.seed(123098765)
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfInt)
|
||||
arr = np.random.randint(-20, 20, 40).astype(np.int32).reshape(10, 2, 2)
|
||||
int_min, int_max = get_limits(ctypes.c_int)
|
||||
for convertible in ((int_min, 1, 2, 3, int_max), [40, 50], tuple(),
|
||||
np.array([int_min, -10, 24, int_max], dtype=np.int32),
|
||||
np.array([10, 230, 12], dtype=np.uint8), arr[:, 0, 1],):
|
||||
expected = "[" + ", ".join(map(str, convertible)) + "]"
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_vector_int_not_convertible(self):
|
||||
np.random.seed(123098765)
|
||||
arr = np.random.randint(-20, 20, 40).astype(np.float32).reshape(10, 2, 2)
|
||||
int_min, int_max = get_limits(ctypes.c_int)
|
||||
test_dict = {1: 2, 3: 10, 10: 20}
|
||||
for not_convertible in ((int_min, 1, 2.5, 3, int_max), [True, 50], 'test', test_dict,
|
||||
reversed([1, 2, 3]),
|
||||
np.array([int_min, -10, 24, [1, 2]], dtype=object),
|
||||
np.array([[1, 2], [3, 4]]), arr[:, 0, 1],):
|
||||
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpVectorOfInt(not_convertible)
|
||||
|
||||
def test_parse_vector_double_convertible(self):
|
||||
np.random.seed(1230965)
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfDouble)
|
||||
arr = np.random.randint(-20, 20, 40).astype(np.int32).reshape(10, 2, 2)
|
||||
for convertible in ((1, 2.12, 3.5), [40, 50], tuple(),
|
||||
np.array([-10, 24], dtype=np.int32),
|
||||
np.array([-12.5, 1.4], dtype=np.double),
|
||||
np.array([10, 230, 12], dtype=np.float32), arr[:, 0, 1], ):
|
||||
expected = "[" + ", ".join(map(lambda v: "{:.2f}".format(v), convertible)) + "]"
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_vector_double_not_convertible(self):
|
||||
test_dict = {1: 2, 3: 10, 10: 20}
|
||||
for not_convertible in (('t', 'e', 's', 't'), [True, 50.55], 'test', test_dict,
|
||||
np.array([-10.1, 24.5, [1, 2]], dtype=object),
|
||||
np.array([[1, 2], [3, 4]]),):
|
||||
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpVectorOfDouble(not_convertible)
|
||||
|
||||
def test_parse_vector_rect_convertible(self):
|
||||
np.random.seed(1238765)
|
||||
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfRect)
|
||||
arr_of_rect_int32 = np.random.randint(5, 20, 4 * 3).astype(np.int32).reshape(3, 4)
|
||||
arr_of_rect_cast = np.random.randint(10, 40, 4 * 5).astype(np.uint8).reshape(5, 4)
|
||||
for convertible in (((1, 2, 3, 4), (10, -20, 30, 10)), arr_of_rect_int32, arr_of_rect_cast,
|
||||
arr_of_rect_int32.astype(np.int8), [[5, 3, 1, 4]],
|
||||
((np.int8(4), np.uint8(10), int(32), np.int16(55)),)):
|
||||
expected = "[" + ", ".join(map(lambda v: "[x={}, y={}, w={}, h={}]".format(*v), convertible)) + "]"
|
||||
actual = try_to_convert(convertible)
|
||||
self.assertEqual(expected, actual,
|
||||
msg=get_conversion_error_msg(convertible, expected, actual))
|
||||
|
||||
def test_parse_vector_rect_not_convertible(self):
|
||||
np.random.seed(1238765)
|
||||
arr = np.random.randint(5, 20, 4 * 3).astype(np.float32).reshape(3, 4)
|
||||
for not_convertible in (((1, 2, 3, 4), (10.5, -20, 30.1, 10)), arr,
|
||||
[[5, 3, 1, 4], []],
|
||||
((float(4), np.uint8(10), int(32), np.int16(55)),)):
|
||||
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
|
||||
_ = cv.utils.dumpVectorOfRect(not_convertible)
|
||||
|
||||
def test_vector_general_return(self):
|
||||
expected_number_of_mats = 5
|
||||
expected_shape = (10, 10, 3)
|
||||
expected_type = np.uint8
|
||||
mats = cv.utils.generateVectorOfMat(5, 10, 10, cv.CV_8UC3)
|
||||
self.assertTrue(isinstance(mats, tuple),
|
||||
"Vector of Mats objects should be returned as tuple. Got: {}".format(type(mats)))
|
||||
self.assertEqual(len(mats), expected_number_of_mats, "Returned array has wrong length")
|
||||
for mat in mats:
|
||||
self.assertEqual(mat.shape, expected_shape, "Returned Mat has wrong shape")
|
||||
self.assertEqual(mat.dtype, expected_type, "Returned Mat has wrong elements type")
|
||||
empty_mats = cv.utils.generateVectorOfMat(0, 10, 10, cv.CV_32FC1)
|
||||
self.assertTrue(isinstance(empty_mats, tuple),
|
||||
"Empty vector should be returned as empty tuple. Got: {}".format(type(mats)))
|
||||
self.assertEqual(len(empty_mats), 0, "Vector of size 0 should be returned as tuple of length 0")
|
||||
|
||||
def test_vector_fast_return(self):
|
||||
expected_shape = (5, 4)
|
||||
rects = cv.utils.generateVectorOfRect(expected_shape[0])
|
||||
self.assertTrue(isinstance(rects, np.ndarray),
|
||||
"Vector of rectangles should be returned as numpy array. Got: {}".format(type(rects)))
|
||||
self.assertEqual(rects.dtype, np.int32, "Vector of rectangles has wrong elements type")
|
||||
self.assertEqual(rects.shape, expected_shape, "Vector of rectangles has wrong shape")
|
||||
empty_rects = cv.utils.generateVectorOfRect(0)
|
||||
self.assertTrue(isinstance(empty_rects, tuple),
|
||||
"Empty vector should be returned as empty tuple. Got: {}".format(type(empty_rects)))
|
||||
self.assertEqual(len(empty_rects), 0, "Vector of size 0 should be returned as tuple of length 0")
|
||||
|
||||
expected_shape = (10,)
|
||||
ints = cv.utils.generateVectorOfInt(expected_shape[0])
|
||||
self.assertTrue(isinstance(ints, np.ndarray),
|
||||
"Vector of integers should be returned as numpy array. Got: {}".format(type(ints)))
|
||||
self.assertEqual(ints.dtype, np.int32, "Vector of integers has wrong elements type")
|
||||
self.assertEqual(ints.shape, expected_shape, "Vector of integers has wrong shape.")
|
||||
|
||||
def test_result_rotated_rect_issue_20930(self):
|
||||
rr = cv.utils.testRotatedRect(10, 20, 100, 200, 45)
|
||||
self.assertTrue(isinstance(rr, tuple), msg=type(rr))
|
||||
self.assertEqual(len(rr), 3)
|
||||
|
||||
rrv = cv.utils.testRotatedRectVector(10, 20, 100, 200, 45)
|
||||
self.assertTrue(isinstance(rrv, tuple), msg=type(rrv))
|
||||
self.assertEqual(len(rrv), 10)
|
||||
|
||||
rr = rrv[0]
|
||||
self.assertTrue(isinstance(rr, tuple), msg=type(rrv))
|
||||
self.assertEqual(len(rr), 3)
|
||||
|
||||
def test_nested_function_availability(self):
|
||||
self.assertTrue(hasattr(cv.utils, "nested"),
|
||||
msg="Module is not generated for nested namespace")
|
||||
self.assertTrue(hasattr(cv.utils.nested, "testEchoBooleanFunction"),
|
||||
msg="Function in nested module is not available")
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
# Nested submodule is managed only by the global submodules dictionary
|
||||
# and parent native module
|
||||
expected_ref_count = 2
|
||||
else:
|
||||
# Nested submodule is managed by the global submodules dictionary,
|
||||
# parent native module and Python part of the submodule
|
||||
expected_ref_count = 3
|
||||
|
||||
# `getrefcount` temporary increases reference counter by 1
|
||||
actual_ref_count = sys.getrefcount(cv.utils.nested) - 1
|
||||
|
||||
self.assertEqual(actual_ref_count, expected_ref_count,
|
||||
msg="Nested submodule reference counter has wrong value\n"
|
||||
"Expected: {}. Actual: {}".format(expected_ref_count, actual_ref_count))
|
||||
for flag in (True, False):
|
||||
self.assertEqual(flag, cv.utils.nested.testEchoBooleanFunction(flag),
|
||||
msg="Function in nested module returns wrong result")
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
)
|
||||
)
|
||||
|
||||
def test_named_arguments_without_parameters(self):
|
||||
src = np.ones((5, 5, 3), dtype=np.uint8)
|
||||
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(src)
|
||||
np.testing.assert_equal(src, src_copy)
|
||||
self.assertEqual(arguments_dump, 'lambda=-1, sigma=0.0')
|
||||
|
||||
def test_named_arguments_without_output_argument(self):
|
||||
src = np.zeros((2, 2, 3), dtype=np.uint8)
|
||||
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(
|
||||
src, lambda_=15, sigma=3.5
|
||||
)
|
||||
np.testing.assert_equal(src, src_copy)
|
||||
self.assertEqual(arguments_dump, 'lambda=15, sigma=3.5')
|
||||
|
||||
def test_named_arguments_with_output_argument(self):
|
||||
src = np.zeros((3, 3, 3), dtype=np.uint8)
|
||||
dst = np.ones_like(src)
|
||||
arguments_dump, src_copy = cv.utils.copyMatAndDumpNamedArguments(
|
||||
src, dst, lambda_=25, sigma=5.5
|
||||
)
|
||||
np.testing.assert_equal(src, src_copy)
|
||||
np.testing.assert_equal(dst, src_copy)
|
||||
self.assertEqual(arguments_dump, 'lambda=25, sigma=5.5')
|
||||
|
||||
def test_arithm_op_without_saturation(self):
|
||||
np.random.seed(4231568)
|
||||
src = np.random.randint(20, 40, 8 * 4 * 3).astype(np.uint8).reshape(8, 4, 3)
|
||||
operations = get_ocv_arithm_op_table(apply_saturation=False)
|
||||
for ocv_op, numpy_op in operations.items():
|
||||
for val in (2, 4, (5, ), (6, 4), (2., 4., 1.),
|
||||
np.uint8([1, 2, 2]), np.float64([5, 2, 6, 3]),):
|
||||
dst = ocv_op(src, val)
|
||||
expected = numpy_op(src, val)
|
||||
# Temporarily allows a difference of 1 for arm64 workaround.
|
||||
self.assertLess(np.max(np.abs(dst - expected)), 2,
|
||||
msg="Operation '{}' is failed for {}".format(ocv_op.__name__, val ) )
|
||||
|
||||
def test_arithm_op_with_saturation(self):
|
||||
np.random.seed(4231568)
|
||||
src = np.random.randint(20, 40, 4 * 8 * 4).astype(np.uint8).reshape(4, 8, 4)
|
||||
operations = get_ocv_arithm_op_table(apply_saturation=True)
|
||||
|
||||
for ocv_op, numpy_op in operations.items():
|
||||
for val in (10, 4, (40, ), (15, 12), (25., 41., 15.),
|
||||
np.uint8([1, 2, 20]), np.float64([50, 21, 64, 30]),):
|
||||
dst = ocv_op(src, val)
|
||||
expected = numpy_op(src, val)
|
||||
# Temporarily allows a difference of 1 for arm64 workaround.
|
||||
self.assertLess(np.max(np.abs(dst - expected)), 2,
|
||||
msg="Saturated Operation '{}' is failed for {}".format(ocv_op.__name__, val ) )
|
||||
|
||||
class CanUsePurePythonModuleFunction(NewOpenCVTests):
|
||||
def test_can_get_ocv_version(self):
|
||||
import sys
|
||||
if sys.version_info[0] < 3:
|
||||
raise unittest.SkipTest('Python 2.x is not supported')
|
||||
|
||||
self.assertEqual(cv.misc.get_ocv_version(), cv.__version__,
|
||||
"Can't get package version using Python misc module")
|
||||
|
||||
def test_native_method_can_be_patched(self):
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise unittest.SkipTest('Python 2.x is not supported')
|
||||
|
||||
res = cv.utils.testOverwriteNativeMethod(10)
|
||||
self.assertTrue(isinstance(res, Sequence),
|
||||
msg="Overwritten method should return sequence. "
|
||||
"Got: {} of type {}".format(res, type(res)))
|
||||
self.assertSequenceEqual(res, (11, 10),
|
||||
msg="Failed to overwrite native method")
|
||||
res = cv.utils._native.testOverwriteNativeMethod(123)
|
||||
self.assertEqual(res, 123, msg="Failed to call native method implementation")
|
||||
|
||||
def test_default_matx_argument(self):
|
||||
res = cv.utils.dumpVec2i()
|
||||
self.assertEqual(res, "Vec2i(42, 24)",
|
||||
msg="Default argument is not properly handled")
|
||||
res = cv.utils.dumpVec2i((12, 21))
|
||||
self.assertEqual(res, "Vec2i(12, 21)")
|
||||
|
||||
|
||||
class SamplesFindFile(NewOpenCVTests):
|
||||
|
||||
def test_ExistedFile(self):
|
||||
res = cv.samples.findFile('HappyFish.jpg', False)
|
||||
self.assertNotEqual(res, '')
|
||||
|
||||
def test_MissingFile(self):
|
||||
res = cv.samples.findFile('non_existed.file', False)
|
||||
self.assertEqual(res, '')
|
||||
|
||||
def test_MissingFileException(self):
|
||||
try:
|
||||
_res = cv.samples.findFile('non_existed.file', True)
|
||||
self.assertEqual("Dead code", 0)
|
||||
except cv.error as _e:
|
||||
pass
|
||||
|
||||
class AlgorithmImplHit(NewOpenCVTests):
|
||||
def test_callable(self):
|
||||
res = cv.getDefaultAlgorithmHint()
|
||||
self.assertTrue(res is not None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Morphology operations.
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class morphology_test(NewOpenCVTests):
|
||||
|
||||
def test_morphology(self):
|
||||
|
||||
fn = 'samples/data/rubberwhale1.png'
|
||||
img = self.get_sample(fn)
|
||||
|
||||
modes = ['erode/dilate', 'open/close', 'blackhat/tophat', 'gradient']
|
||||
str_modes = ['ellipse', 'rect', 'cross']
|
||||
|
||||
referenceHashes = { modes[0]: '071a526425b79e45b4d0d71ef51b0562', modes[1] : '071a526425b79e45b4d0d71ef51b0562',
|
||||
modes[2] : '427e89f581b7df1b60a831b1ed4c8618', modes[3] : '0dd8ad251088a63d0dd022bcdc57361c'}
|
||||
|
||||
def update(cur_mode):
|
||||
cur_str_mode = str_modes[0]
|
||||
sz = 10
|
||||
iters = 1
|
||||
opers = cur_mode.split('/')
|
||||
if len(opers) > 1:
|
||||
sz = sz - 10
|
||||
op = opers[sz > 0]
|
||||
sz = abs(sz)
|
||||
else:
|
||||
op = opers[0]
|
||||
sz = sz*2+1
|
||||
|
||||
str_name = 'MORPH_' + cur_str_mode.upper()
|
||||
oper_name = 'MORPH_' + op.upper()
|
||||
|
||||
st = cv.getStructuringElement(getattr(cv, str_name), (sz, sz))
|
||||
return cv.morphologyEx(img, getattr(cv, oper_name), st, iterations=iters)
|
||||
|
||||
for mode in modes:
|
||||
res = update(mode)
|
||||
self.assertEqual(self.hashimg(res), referenceHashes[mode])
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
MSER detector test
|
||||
'''
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import random
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class mser_test(NewOpenCVTests):
|
||||
def test_mser(self):
|
||||
|
||||
img = self.get_sample('cv/mser/puzzle.png', 0)
|
||||
smallImg = [
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
|
||||
]
|
||||
kDelta = 5
|
||||
mserExtractor = cv.MSER_create()
|
||||
mserExtractor.setDelta(kDelta)
|
||||
mserExtractor.setMinDiversity(0)
|
||||
random.seed(10)
|
||||
|
||||
for _i in range(100):
|
||||
|
||||
use_big_image = random.choice([True, False])
|
||||
invert = random.choice([True, False])
|
||||
binarize = random.choice([True, False]) if use_big_image else False
|
||||
blur = random.choice([True, False])
|
||||
thresh = random.choice([0, 70, 120, 180, 255])
|
||||
src0 = img if use_big_image else np.array(smallImg).astype('uint8')
|
||||
src = src0.copy()
|
||||
|
||||
kMinArea = 256 if use_big_image else 10
|
||||
kMaxArea = int(src.shape[0]*src.shape[1]/4)
|
||||
|
||||
mserExtractor.setMinArea(kMinArea)
|
||||
mserExtractor.setMaxArea(kMaxArea)
|
||||
if invert:
|
||||
cv.bitwise_not(src, src)
|
||||
if binarize:
|
||||
_, src = cv.threshold(src, thresh, 255, cv.THRESH_BINARY)
|
||||
if blur:
|
||||
src = cv.GaussianBlur(src, (5, 5), 1.5, 1.5)
|
||||
minRegs = 7 if use_big_image else 2
|
||||
maxRegs = 1000 if use_big_image else 20
|
||||
if binarize and (thresh == 0 or thresh == 255):
|
||||
minRegs = maxRegs = 0
|
||||
msers, boxes = mserExtractor.detectRegions(src)
|
||||
nmsers = len(msers)
|
||||
self.assertEqual(nmsers, len(boxes))
|
||||
self.assertLessEqual(minRegs, nmsers)
|
||||
self.assertGreaterEqual(maxRegs, nmsers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from itertools import product
|
||||
from functools import reduce
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
def norm_inf(x, y=None):
|
||||
def norm(vec):
|
||||
return np.linalg.norm(vec.flatten(), np.inf)
|
||||
|
||||
x = x.astype(np.float64)
|
||||
return norm(x) if y is None else norm(x - y.astype(np.float64))
|
||||
|
||||
|
||||
def norm_l1(x, y=None):
|
||||
def norm(vec):
|
||||
return np.linalg.norm(vec.flatten(), 1)
|
||||
|
||||
x = x.astype(np.float64)
|
||||
return norm(x) if y is None else norm(x - y.astype(np.float64))
|
||||
|
||||
|
||||
def norm_l2(x, y=None):
|
||||
def norm(vec):
|
||||
return np.linalg.norm(vec.flatten())
|
||||
|
||||
x = x.astype(np.float64)
|
||||
return norm(x) if y is None else norm(x - y.astype(np.float64))
|
||||
|
||||
|
||||
def norm_l2sqr(x, y=None):
|
||||
def norm(vec):
|
||||
return np.square(vec).sum()
|
||||
|
||||
x = x.astype(np.float64)
|
||||
return norm(x) if y is None else norm(x - y.astype(np.float64))
|
||||
|
||||
|
||||
def norm_hamming(x, y=None):
|
||||
def norm(vec):
|
||||
return sum(bin(i).count('1') for i in vec.flatten())
|
||||
|
||||
return norm(x) if y is None else norm(np.bitwise_xor(x, y))
|
||||
|
||||
|
||||
def norm_hamming2(x, y=None):
|
||||
def norm(vec):
|
||||
def element_norm(element):
|
||||
binary_str = bin(element).split('b')[-1]
|
||||
if len(binary_str) % 2 == 1:
|
||||
binary_str = '0' + binary_str
|
||||
gen = filter(lambda p: p != '00',
|
||||
(binary_str[i:i+2]
|
||||
for i in range(0, len(binary_str), 2)))
|
||||
return sum(1 for _ in gen)
|
||||
|
||||
return sum(element_norm(element) for element in vec.flatten())
|
||||
|
||||
return norm(x) if y is None else norm(np.bitwise_xor(x, y))
|
||||
|
||||
|
||||
norm_type_under_test = {
|
||||
cv.NORM_INF: norm_inf,
|
||||
cv.NORM_L1: norm_l1,
|
||||
cv.NORM_L2: norm_l2,
|
||||
cv.NORM_L2SQR: norm_l2sqr,
|
||||
cv.NORM_HAMMING: norm_hamming,
|
||||
cv.NORM_HAMMING2: norm_hamming2
|
||||
}
|
||||
|
||||
norm_name = {
|
||||
cv.NORM_INF: 'inf',
|
||||
cv.NORM_L1: 'L1',
|
||||
cv.NORM_L2: 'L2',
|
||||
cv.NORM_L2SQR: 'L2SQR',
|
||||
cv.NORM_HAMMING: 'Hamming',
|
||||
cv.NORM_HAMMING2: 'Hamming2'
|
||||
}
|
||||
|
||||
|
||||
def get_element_types(norm_type):
|
||||
if norm_type in (cv.NORM_HAMMING, cv.NORM_HAMMING2):
|
||||
return (np.uint8,)
|
||||
else:
|
||||
return (np.uint8, np.int8, np.uint16, np.int16, np.int32, np.float32,
|
||||
np.float64, np.float16)
|
||||
|
||||
|
||||
def generate_vector(shape, dtype):
|
||||
if np.issubdtype(dtype, np.integer):
|
||||
return np.random.randint(0, 100, shape).astype(dtype)
|
||||
else:
|
||||
return np.random.normal(10., 12.5, shape).astype(dtype)
|
||||
|
||||
|
||||
shapes = (1, 2, 3, 5, 7, 16, (1, 1), (2, 2), (3, 5), (1, 7))
|
||||
|
||||
|
||||
class norm_test(NewOpenCVTests):
|
||||
|
||||
def test_norm_for_one_array(self):
|
||||
np.random.seed(123)
|
||||
for norm_type, norm in norm_type_under_test.items():
|
||||
element_types = get_element_types(norm_type)
|
||||
for shape, element_type in product(shapes, element_types):
|
||||
array = generate_vector(shape, element_type)
|
||||
expected = norm(array)
|
||||
actual = cv.norm(array, norm_type)
|
||||
self.assertAlmostEqual(
|
||||
expected, actual, places=2,
|
||||
msg='Array {0} of {1} and norm {2}'.format(
|
||||
array, element_type.__name__, norm_name[norm_type]
|
||||
)
|
||||
)
|
||||
|
||||
def test_norm_for_two_arrays(self):
|
||||
np.random.seed(456)
|
||||
for norm_type, norm in norm_type_under_test.items():
|
||||
element_types = get_element_types(norm_type)
|
||||
for shape, element_type in product(shapes, element_types):
|
||||
first = generate_vector(shape, element_type)
|
||||
second = generate_vector(shape, element_type)
|
||||
expected = norm(first, second)
|
||||
actual = cv.norm(first, second, norm_type)
|
||||
self.assertAlmostEqual(
|
||||
expected, actual, places=2,
|
||||
msg='Arrays {0} {1} of type {2} and norm {3}'.format(
|
||||
first, second, element_type.__name__,
|
||||
norm_name[norm_type]
|
||||
)
|
||||
)
|
||||
|
||||
def test_norm_fails_for_wrong_type(self):
|
||||
for norm_type in (cv.NORM_HAMMING, cv.NORM_HAMMING2):
|
||||
with self.assertRaises(Exception,
|
||||
msg='Type is not checked {0}'.format(
|
||||
norm_name[norm_type]
|
||||
)):
|
||||
cv.norm(np.array([1, 2], dtype=np.int32), norm_type)
|
||||
|
||||
def test_norm_fails_for_array_and_scalar(self):
|
||||
for norm_type in norm_type_under_test:
|
||||
with self.assertRaises(Exception,
|
||||
msg='Exception is not thrown for {0}'.format(
|
||||
norm_name[norm_type]
|
||||
)):
|
||||
cv.norm(np.array([1, 2], dtype=np.uint8), 123, norm_type)
|
||||
|
||||
def test_norm_fails_for_scalar_and_array(self):
|
||||
for norm_type in norm_type_under_test:
|
||||
with self.assertRaises(Exception,
|
||||
msg='Exception is not thrown for {0}'.format(
|
||||
norm_name[norm_type]
|
||||
)):
|
||||
cv.norm(4, np.array([1, 2], dtype=np.uint8), norm_type)
|
||||
|
||||
def test_norm_fails_for_array_and_norm_type_as_scalar(self):
|
||||
for norm_type in norm_type_under_test:
|
||||
with self.assertRaises(Exception,
|
||||
msg='Exception is not thrown for {0}'.format(
|
||||
norm_name[norm_type]
|
||||
)):
|
||||
cv.norm(np.array([3, 4, 5], dtype=np.uint8),
|
||||
norm_type, normType=norm_type)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,38 @@
|
||||
from tests_common import NewOpenCVTests, unittest
|
||||
import cv2 as cv
|
||||
import os
|
||||
|
||||
|
||||
def import_path():
|
||||
import sys
|
||||
if sys.version_info[0] < 3 or sys.version_info[1] < 6:
|
||||
raise unittest.SkipTest('Python 3.6+ required')
|
||||
|
||||
from pathlib import Path
|
||||
return Path
|
||||
|
||||
|
||||
class CanPassPathLike(NewOpenCVTests):
|
||||
def test_pathlib_path(self):
|
||||
Path = import_path()
|
||||
|
||||
img_path = self.find_file('cv/imgproc/stuff.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
|
||||
|
||||
image_from_str = cv.imread(img_path)
|
||||
self.assertIsNotNone(image_from_str)
|
||||
|
||||
image_from_path = cv.imread(Path(img_path))
|
||||
self.assertIsNotNone(image_from_path)
|
||||
|
||||
|
||||
def test_type_mismatch(self):
|
||||
import_path() # checks python version
|
||||
|
||||
with self.assertRaises(cv.error) as context:
|
||||
cv.imread(123)
|
||||
|
||||
self.assertTrue('str or path-like' in str(context.exception))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
""""Core serialization tests."""
|
||||
import tempfile
|
||||
import os
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
from tests_common import NewOpenCVTests, unittest
|
||||
|
||||
|
||||
class persistence_test(NewOpenCVTests):
|
||||
def test_yml_rw(self):
|
||||
fd, fname = tempfile.mkstemp(prefix="opencv_python_persistence_", suffix=".yml")
|
||||
os.close(fd)
|
||||
|
||||
# Writing ...
|
||||
expected = np.array([[[0, 1, 2, 3, 4]]])
|
||||
expected_str = ("Hello", "World", "!")
|
||||
fs = cv.FileStorage(fname, cv.FILE_STORAGE_WRITE)
|
||||
fs.write("test", expected)
|
||||
fs.write("strings", expected_str)
|
||||
fs.release()
|
||||
|
||||
# Reading ...
|
||||
fs = cv.FileStorage(fname, cv.FILE_STORAGE_READ)
|
||||
root = fs.getFirstTopLevelNode()
|
||||
self.assertEqual(root.name(), "test")
|
||||
|
||||
test = fs.getNode("test")
|
||||
self.assertEqual(test.empty(), False)
|
||||
self.assertEqual(test.name(), "test")
|
||||
self.assertEqual(test.type(), cv.FILE_NODE_MAP)
|
||||
self.assertEqual(test.isMap(), True)
|
||||
actual = test.mat()
|
||||
self.assertEqual(actual.shape, expected.shape)
|
||||
self.assertEqual(np.array_equal(expected, actual), True)
|
||||
|
||||
strings = fs.getNode("strings")
|
||||
self.assertEqual(strings.isSeq(), True)
|
||||
self.assertEqual(strings.size(), len(expected_str))
|
||||
self.assertEqual(all(strings.at(i).isString() for i in range(strings.size())), True)
|
||||
self.assertSequenceEqual([strings.at(i).string() for i in range(strings.size())], expected_str)
|
||||
fs.release()
|
||||
|
||||
os.remove(fname)
|
||||
|
||||
def test_yml_python_interop(self):
|
||||
try:
|
||||
import yaml
|
||||
except:
|
||||
raise unittest.SkipTest('Pyyml is not available for interop test')
|
||||
|
||||
ref_data = {
|
||||
'int_value': 42,
|
||||
"bool_value": True,
|
||||
"float_value": 3.1415926,
|
||||
"int64_value": 2147483647 + 1024, # C++ INT_MAX + 1024
|
||||
"string_value": "opencv"
|
||||
}
|
||||
|
||||
fd, test_file_name = tempfile.mkstemp(prefix="opencv_python_persistence_", suffix=".yml")
|
||||
os.close(fd)
|
||||
|
||||
with open(test_file_name, 'w') as ff:
|
||||
yaml.dump(ref_data, ff)
|
||||
|
||||
# Notice: no cv.FileStorage_FORMAT_YAML flag needed now thanks to the C++ fix!
|
||||
fs = cv.FileStorage(test_file_name, cv.FILE_STORAGE_READ)
|
||||
self.assertTrue(fs.isOpened())
|
||||
|
||||
node = fs.getNode('int_value')
|
||||
self.assertTrue(node.isInt())
|
||||
self.assertEqual(42, int(node.real()))
|
||||
|
||||
node = fs.getNode('int64_value')
|
||||
self.assertTrue(node.isInt())
|
||||
self.assertEqual(2147483647 + 1024, int(node.real()))
|
||||
|
||||
node = fs.getNode('float_value')
|
||||
self.assertTrue(node.isReal())
|
||||
self.assertEqual(3.1415926, node.real())
|
||||
|
||||
node = fs.getNode('string_value')
|
||||
self.assertTrue(node.isString())
|
||||
self.assertEqual("opencv", node.string())
|
||||
|
||||
fs.release()
|
||||
os.remove(test_file_name)
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Simple "Square Detector" program.
|
||||
|
||||
Loads several images sequentially and tries to find squares in each image.
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
xrange = range
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
|
||||
def angle_cos(p0, p1, p2):
|
||||
d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
|
||||
return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) )
|
||||
|
||||
def find_squares(img):
|
||||
img = cv.GaussianBlur(img, (5, 5), 0)
|
||||
squares = []
|
||||
for gray in cv.split(img):
|
||||
for thrs in xrange(0, 255, 26):
|
||||
if thrs == 0:
|
||||
bin = cv.Canny(gray, 0, 50, apertureSize=5)
|
||||
bin = cv.dilate(bin, None)
|
||||
else:
|
||||
_retval, bin = cv.threshold(gray, thrs, 255, cv.THRESH_BINARY)
|
||||
contours, _hierarchy = cv.findContours(bin, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
|
||||
for cnt in contours:
|
||||
cnt_len = cv.arcLength(cnt, True)
|
||||
cnt = cv.approxPolyDP(cnt, 0.02*cnt_len, True)
|
||||
if len(cnt) == 4 and cv.contourArea(cnt) > 1000 and cv.isContourConvex(cnt):
|
||||
cnt = cnt.reshape(-1, 2)
|
||||
max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in xrange(4)])
|
||||
if max_cos < 0.1 and filterSquares(squares, cnt):
|
||||
squares.append(cnt)
|
||||
|
||||
return squares
|
||||
|
||||
def intersectionRate(s1, s2):
|
||||
area, _intersection = cv.intersectConvexConvex(np.array(s1), np.array(s2))
|
||||
return 2 * area / (cv.contourArea(np.array(s1)) + cv.contourArea(np.array(s2)))
|
||||
|
||||
def filterSquares(squares, square):
|
||||
|
||||
for i in range(len(squares)):
|
||||
if intersectionRate(squares[i], square) > 0.95:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class squares_test(NewOpenCVTests):
|
||||
|
||||
def test_squares(self):
|
||||
|
||||
img = self.get_sample('samples/data/pic1.png')
|
||||
squares = find_squares(img)
|
||||
|
||||
testSquares = [
|
||||
[[43, 25],
|
||||
[43, 129],
|
||||
[232, 129],
|
||||
[232, 25]],
|
||||
|
||||
[[252, 87],
|
||||
[324, 40],
|
||||
[387, 137],
|
||||
[315, 184]],
|
||||
|
||||
[[154, 178],
|
||||
[196, 180],
|
||||
[198, 278],
|
||||
[154, 278]],
|
||||
|
||||
[[0, 0],
|
||||
[400, 0],
|
||||
[400, 300],
|
||||
[0, 300]]
|
||||
]
|
||||
|
||||
matches_counter = 0
|
||||
for i in range(len(squares)):
|
||||
for j in range(len(testSquares)):
|
||||
if intersectionRate(squares[i], testSquares[j]) > 0.9:
|
||||
matches_counter += 1
|
||||
|
||||
self.assertGreater(matches_counter / len(testSquares), 0.9)
|
||||
self.assertLess( (len(squares) - matches_counter) / len(squares), 0.2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Texture flow direction estimation.
|
||||
|
||||
Sample shows how cv.cornerEigenValsAndVecs function can be used
|
||||
to estimate image texture flow direction.
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import sys
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
class texture_flow_test(NewOpenCVTests):
|
||||
|
||||
def test_texture_flow(self):
|
||||
|
||||
img = self.get_sample('samples/data/chessboard.png')
|
||||
|
||||
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
h, w = img.shape[:2]
|
||||
|
||||
eigen = cv.cornerEigenValsAndVecs(gray, 5, 3)
|
||||
eigen = eigen.reshape(h, w, 3, 2) # [[e1, e2], v1, v2]
|
||||
flow = eigen[:,:,2]
|
||||
|
||||
d = 300
|
||||
eps = d / 30
|
||||
|
||||
points = np.dstack( np.mgrid[d/2:w:d, d/2:h:d] ).reshape(-1, 2)
|
||||
|
||||
textureVectors = []
|
||||
for x, y in np.int32(points):
|
||||
textureVectors.append(np.int32(flow[y, x]*d))
|
||||
|
||||
for i in range(len(textureVectors)):
|
||||
self.assertTrue(cv.norm(textureVectors[i], cv.NORM_L2) < eps
|
||||
or abs(cv.norm(textureVectors[i], cv.NORM_L2) - d) < eps)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for Tokenizer Python bindings
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import cv2 as cv
|
||||
import os
|
||||
import json
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
def _tf(filename=""):
|
||||
base = os.environ.get("OPENCV_TEST_DATA_PATH") or os.getcwd()
|
||||
path = os.path.join(base, "dnn", "llm", filename)
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(
|
||||
f"Missing test data: {path}. "
|
||||
"Set OPENCV_TEST_DATA_PATH to the testdata root contains dnn/llm."
|
||||
)
|
||||
return path
|
||||
|
||||
class TokenizerBindingTest(NewOpenCVTests):
|
||||
def test_tokenizer_binding(self):
|
||||
try:
|
||||
tokenizer = cv.dnn.Tokenizer
|
||||
print("Tokenizer binding is available.", tokenizer)
|
||||
gpt2_model = _tf("gpt2/config.json")
|
||||
tokenizer = cv.dnn.Tokenizer.load(gpt2_model)
|
||||
print("Tokenizer loaded from:", gpt2_model)
|
||||
except AttributeError:
|
||||
self.fail("Tokenizer binding is NOT available.")
|
||||
|
||||
def test_tokenizer_gpt2(self):
|
||||
tok = cv.dnn.Tokenizer.load((_tf("gpt2/config.json")))
|
||||
ids = tok.encode("hello world")
|
||||
print(ids)
|
||||
txt = tok.decode(ids)
|
||||
self.assertEqual(txt, "hello world")
|
||||
|
||||
def test_tokenizer_gpt4(self):
|
||||
tok = cv.dnn.Tokenizer.load(_tf("gpt4/config.json"))
|
||||
tokens = tok.encode("hello world")
|
||||
# expects {15339, 1917}
|
||||
self.assertEqual(list(tokens), [15339, 1917])
|
||||
sent = tok.decode([15339, 1917])
|
||||
self.assertEqual(sent, "hello world")
|
||||
|
||||
def test_with_hf_tiktoken(self):
|
||||
tok = cv.dnn.Tokenizer.load(_tf("gpt2/config.json"))
|
||||
with open(_tf("gpt2/gpt2_hf_tik_testdata.json"), "r", encoding="utf-8") as f:
|
||||
golden = json.load(f)
|
||||
|
||||
for s in golden["samples"]:
|
||||
text = s["text"]
|
||||
expected = s["ids"]
|
||||
got = tok.encode(text).tolist()
|
||||
self.assertEqual(
|
||||
got, expected,
|
||||
msg=f"Mismatch for sample '{s['name']}'"
|
||||
)
|
||||
self.assertEqual(tok.decode(expected), text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
import os
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
def load_exposure_seq(path):
|
||||
images = []
|
||||
times = []
|
||||
with open(os.path.join(path, 'list.txt'), 'r') as list_file:
|
||||
for line in list_file.readlines():
|
||||
name, time = line.split()
|
||||
images.append(cv.imread(os.path.join(path, name)))
|
||||
times.append(1. / float(time))
|
||||
return images, times
|
||||
|
||||
|
||||
class UMat(NewOpenCVTests):
|
||||
|
||||
def test_umat_construct(self):
|
||||
data = np.random.random([512, 512])
|
||||
# UMat constructors
|
||||
data_um = cv.UMat(data) # from ndarray
|
||||
data_sub_um = cv.UMat(data_um, (128, 256), (128, 256)) # from UMat
|
||||
data_dst_um = cv.UMat(128, 128, cv.CV_64F) # from size/type
|
||||
# test continuous and submatrix flags
|
||||
assert data_um.isContinuous() and not data_um.isSubmatrix()
|
||||
assert not data_sub_um.isContinuous() and data_sub_um.isSubmatrix()
|
||||
# test operation on submatrix
|
||||
cv.multiply(data_sub_um, 2., dst=data_dst_um)
|
||||
assert np.allclose(2. * data[128:256, 128:256], data_dst_um.get())
|
||||
|
||||
def test_umat_handle(self):
|
||||
a_um = cv.UMat(256, 256, cv.CV_32F)
|
||||
_ctx_handle = cv.UMat.context() # obtain context handle
|
||||
_queue_handle = cv.UMat.queue() # obtain queue handle
|
||||
_a_handle = a_um.handle(cv.ACCESS_READ) # obtain buffer handle
|
||||
_offset = a_um.offset # obtain buffer offset
|
||||
|
||||
def test_umat_matching(self):
|
||||
img1 = self.get_sample("samples/data/right01.jpg")
|
||||
img2 = self.get_sample("samples/data/right02.jpg")
|
||||
|
||||
orb = cv.ORB_create()
|
||||
|
||||
img1, img2 = cv.UMat(img1), cv.UMat(img2)
|
||||
ps1, descs_umat1 = orb.detectAndCompute(img1, None)
|
||||
ps2, descs_umat2 = orb.detectAndCompute(img2, None)
|
||||
|
||||
self.assertIsInstance(descs_umat1, cv.UMat)
|
||||
self.assertIsInstance(descs_umat2, cv.UMat)
|
||||
self.assertGreater(len(ps1), 0)
|
||||
self.assertGreater(len(ps2), 0)
|
||||
|
||||
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
|
||||
|
||||
res_umats = bf.match(descs_umat1, descs_umat2)
|
||||
res = bf.match(descs_umat1.get(), descs_umat2.get())
|
||||
|
||||
self.assertGreater(len(res), 0)
|
||||
self.assertEqual(len(res_umats), len(res))
|
||||
|
||||
def test_umat_optical_flow(self):
|
||||
img1 = self.get_sample("samples/data/right01.jpg", cv.IMREAD_GRAYSCALE)
|
||||
img2 = self.get_sample("samples/data/right02.jpg", cv.IMREAD_GRAYSCALE)
|
||||
# Note, that if you want to see performance boost by OCL implementation - you need enough data
|
||||
# For example you can increase maxCorners param to 10000 and increase img1 and img2 in such way:
|
||||
# img = np.hstack([np.vstack([img] * 6)] * 6)
|
||||
|
||||
feature_params = dict(maxCorners=239,
|
||||
qualityLevel=0.3,
|
||||
minDistance=7,
|
||||
blockSize=7)
|
||||
|
||||
p0 = cv.goodFeaturesToTrack(img1, mask=None, **feature_params)
|
||||
p0_umat = cv.goodFeaturesToTrack(cv.UMat(img1), mask=None, **feature_params)
|
||||
self.assertEqual(p0_umat.get().shape, p0.shape)
|
||||
|
||||
p0 = np.array(sorted(p0, key=lambda p: tuple(p[0])))
|
||||
p0_umat = cv.UMat(np.array(sorted(p0_umat.get(), key=lambda p: tuple(p[0]))))
|
||||
self.assertTrue(np.allclose(p0_umat.get(), p0))
|
||||
|
||||
_p1_mask_err = cv.calcOpticalFlowPyrLK(img1, img2, p0, None)
|
||||
|
||||
_p1_mask_err_umat0 = list(map(lambda umat: umat.get(), cv.calcOpticalFlowPyrLK(img1, img2, p0_umat, None)))
|
||||
_p1_mask_err_umat1 = list(map(lambda umat: umat.get(), cv.calcOpticalFlowPyrLK(cv.UMat(img1), img2, p0_umat, None)))
|
||||
_p1_mask_err_umat2 = list(map(lambda umat: umat.get(), cv.calcOpticalFlowPyrLK(img1, cv.UMat(img2), p0_umat, None)))
|
||||
|
||||
for _p1_mask_err_umat in [_p1_mask_err_umat0, _p1_mask_err_umat1, _p1_mask_err_umat2]:
|
||||
for data, data_umat in zip(_p1_mask_err, _p1_mask_err_umat):
|
||||
self.assertEqual(data.shape, data_umat.shape)
|
||||
self.assertEqual(data.dtype, data_umat.dtype)
|
||||
for _p1_mask_err_umat in [_p1_mask_err_umat1, _p1_mask_err_umat2]:
|
||||
for data_umat0, data_umat in zip(_p1_mask_err_umat0[:2], _p1_mask_err_umat[:2]):
|
||||
self.assertTrue(np.allclose(data_umat0, data_umat))
|
||||
|
||||
def test_umat_merge_mertens(self):
|
||||
if self.extraTestDataPath is None:
|
||||
self.fail('Test data is not available')
|
||||
|
||||
test_data_path = os.path.join(self.extraTestDataPath, 'cv', 'hdr')
|
||||
|
||||
images, _ = load_exposure_seq(os.path.join(test_data_path, 'exposures'))
|
||||
|
||||
# As we want to test mat vs. umat here, we temporarily set only one worker-thread to achieve
|
||||
# deterministic summations inside mertens' parallelized process.
|
||||
num_threads = cv.getNumThreads()
|
||||
cv.setNumThreads(1)
|
||||
|
||||
merge = cv.createMergeMertens()
|
||||
mat_result = merge.process(images)
|
||||
|
||||
umat_images = [cv.UMat(img) for img in images]
|
||||
umat_result = merge.process(umat_images)
|
||||
|
||||
cv.setNumThreads(num_threads)
|
||||
|
||||
self.assertTrue(np.allclose(umat_result.get(), mat_result))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Watershed segmentation test
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class watershed_test(NewOpenCVTests):
|
||||
def test_watershed(self):
|
||||
|
||||
img = self.get_sample('cv/inpaint/orig.png')
|
||||
markers = self.get_sample('cv/watershed/wshed_exp.png', 0)
|
||||
refSegments = self.get_sample('cv/watershed/wshed_segments.png')
|
||||
|
||||
if img is None or markers is None:
|
||||
self.assertEqual(0, 1, 'Missing test data')
|
||||
|
||||
colors = np.int32( list(np.ndindex(3, 3, 3)) ) * 122
|
||||
cv.watershed(img, np.int32(markers))
|
||||
segments = colors[np.maximum(markers, 0)]
|
||||
|
||||
if refSegments is None:
|
||||
refSegments = segments.copy()
|
||||
cv.imwrite(self.extraTestDataPath + '/cv/watershed/wshed_segments.png', refSegments)
|
||||
|
||||
self.assertLess(cv.norm(segments - refSegments, cv.NORM_L1) / 255.0, 50)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import hashlib
|
||||
import random
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
#sys.OpenCV_LOADER_DEBUG = True
|
||||
import cv2 as cv
|
||||
|
||||
# Python 3 moved urlopen to urllib.requests
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib import urlopen
|
||||
|
||||
class NewOpenCVTests(unittest.TestCase):
|
||||
|
||||
# path to local repository folder containing 'samples' folder
|
||||
repoPath = None
|
||||
extraTestDataPath = None
|
||||
extraDnnTestDataPath = None
|
||||
# github repository url
|
||||
repoUrl = 'https://raw.github.com/opencv/opencv/5.x'
|
||||
|
||||
def find_file(self, filename, searchPaths=[], required=True):
|
||||
searchPaths = searchPaths if searchPaths else [self.repoPath, self.extraTestDataPath, self.extraDnnTestDataPath]
|
||||
for path in searchPaths:
|
||||
if path is not None:
|
||||
candidate = path + '/' + filename
|
||||
if os.path.isfile(candidate):
|
||||
return candidate
|
||||
if required:
|
||||
self.fail('File ' + filename + ' not found')
|
||||
else:
|
||||
self.skipTest('File ' + filename + ' not found')
|
||||
return None
|
||||
|
||||
|
||||
def get_sample(self, filename, iscolor = None):
|
||||
if iscolor is None:
|
||||
iscolor = cv.IMREAD_COLOR
|
||||
if not filename in self.image_cache:
|
||||
filepath = self.find_file(filename)
|
||||
with open(filepath, 'rb') as f:
|
||||
filedata = f.read()
|
||||
self.image_cache[filename] = cv.imdecode(np.frombuffer(filedata, dtype=np.uint8), iscolor)
|
||||
return self.image_cache[filename]
|
||||
|
||||
def setUp(self):
|
||||
cv.setRNGSeed(10)
|
||||
self.image_cache = {}
|
||||
|
||||
def hashimg(self, im):
|
||||
""" Compute a hash for an image, useful for image comparisons """
|
||||
return hashlib.md5(im.tobytes()).hexdigest()
|
||||
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
def assertLess(self, a, b, msg=None):
|
||||
if not a < b:
|
||||
self.fail('%s not less than %s' % (repr(a), repr(b)))
|
||||
|
||||
def assertLessEqual(self, a, b, msg=None):
|
||||
if not a <= b:
|
||||
self.fail('%s not less than or equal to %s' % (repr(a), repr(b)))
|
||||
|
||||
def assertGreater(self, a, b, msg=None):
|
||||
if not a > b:
|
||||
self.fail('%s not greater than %s' % (repr(a), repr(b)))
|
||||
|
||||
@staticmethod
|
||||
def bootstrap():
|
||||
parser = argparse.ArgumentParser(description='run OpenCV python tests')
|
||||
parser.add_argument('--repo', help='use sample image files from local git repository (path to folder), '
|
||||
'if not set, samples will be downloaded from github.com')
|
||||
parser.add_argument('--data', help='<not used> use data files from local folder (path to folder), '
|
||||
'if not set, data files will be downloaded from docs.opencv.org')
|
||||
args, other = parser.parse_known_args()
|
||||
print("Testing OpenCV", cv.__version__)
|
||||
print("Local repo path:", args.repo)
|
||||
NewOpenCVTests.repoPath = args.repo
|
||||
|
||||
try:
|
||||
NewOpenCVTests.extraTestDataPath = os.environ['OPENCV_TEST_DATA_PATH']
|
||||
except KeyError:
|
||||
print('Missing opencv extra repository. Some of tests may fail.')
|
||||
|
||||
try:
|
||||
NewOpenCVTests.extraDnnTestDataPath = os.environ['OPENCV_DNN_TEST_DATA_PATH']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
random.seed(0)
|
||||
unit_argv = [sys.argv[0]] + other
|
||||
unittest.main(argv=unit_argv)
|
||||
|
||||
|
||||
def intersectionRate(s1, s2):
|
||||
|
||||
x1, y1, x2, y2 = s1
|
||||
s1 = np.array([[x1, y1], [x2,y1], [x2, y2], [x1, y2]])
|
||||
|
||||
x1, y1, x2, y2 = s2
|
||||
s2 = np.array([[x1, y1], [x2,y1], [x2, y2], [x1, y2]])
|
||||
|
||||
area, _intersection = cv.intersectConvexConvex(s1, s2)
|
||||
return 2 * area / (cv.contourArea(s1) + cv.contourArea(s2))
|
||||
|
||||
def isPointInRect(p, rect):
|
||||
if rect[0] <= p[0] and rect[1] <=p[1] and p[0] <= rect[2] and p[1] <= rect[3]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
from numpy import pi, sin, cos
|
||||
|
||||
import cv2 as cv
|
||||
|
||||
defaultSize = 512
|
||||
|
||||
class TestSceneRender():
|
||||
|
||||
def __init__(self, bgImg = None, fgImg = None, deformation = False, noise = 0.0, speed = 0.25, **params):
|
||||
self.time = 0.0
|
||||
self.timeStep = 1.0 / 30.0
|
||||
self.foreground = fgImg
|
||||
self.deformation = deformation
|
||||
self.noise = noise
|
||||
self.speed = speed
|
||||
|
||||
if bgImg is not None:
|
||||
self.sceneBg = bgImg.copy()
|
||||
else:
|
||||
self.sceneBg = np.zeros(defaultSize, defaultSize, np.uint8)
|
||||
|
||||
self.w = self.sceneBg.shape[0]
|
||||
self.h = self.sceneBg.shape[1]
|
||||
|
||||
if fgImg is not None:
|
||||
self.foreground = fgImg.copy()
|
||||
self.center = self.currentCenter = (int(self.w/2 - fgImg.shape[0]/2), int(self.h/2 - fgImg.shape[1]/2))
|
||||
|
||||
self.xAmpl = self.sceneBg.shape[0] - (self.center[0] + fgImg.shape[0])
|
||||
self.yAmpl = self.sceneBg.shape[1] - (self.center[1] + fgImg.shape[1])
|
||||
|
||||
self.initialRect = np.array([ (self.h/2, self.w/2), (self.h/2, self.w/2 + self.w/10),
|
||||
(self.h/2 + self.h/10, self.w/2 + self.w/10), (self.h/2 + self.h/10, self.w/2)]).astype(int)
|
||||
self.currentRect = self.initialRect
|
||||
np.random.seed(10)
|
||||
|
||||
def getXOffset(self, time):
|
||||
return int(self.xAmpl*cos(time*self.speed))
|
||||
|
||||
|
||||
def getYOffset(self, time):
|
||||
return int(self.yAmpl*sin(time*self.speed))
|
||||
|
||||
def setInitialRect(self, rect):
|
||||
self.initialRect = rect
|
||||
|
||||
def getRectInTime(self, time):
|
||||
|
||||
if self.foreground is not None:
|
||||
tmp = np.array(self.center) + np.array((self.getXOffset(time), self.getYOffset(time)))
|
||||
x0, y0 = tmp
|
||||
x1, y1 = tmp + self.foreground.shape[0:2]
|
||||
return np.array([y0, x0, y1, x1])
|
||||
else:
|
||||
x0, y0 = self.initialRect[0] + np.array((self.getXOffset(time), self.getYOffset(time)))
|
||||
x1, y1 = self.initialRect[2] + np.array((self.getXOffset(time), self.getYOffset(time)))
|
||||
return np.array([y0, x0, y1, x1])
|
||||
|
||||
def getCurrentRect(self):
|
||||
|
||||
if self.foreground is not None:
|
||||
|
||||
x0 = self.currentCenter[0]
|
||||
y0 = self.currentCenter[1]
|
||||
x1 = self.currentCenter[0] + self.foreground.shape[0]
|
||||
y1 = self.currentCenter[1] + self.foreground.shape[1]
|
||||
return np.array([y0, x0, y1, x1])
|
||||
else:
|
||||
x0, y0 = self.currentRect[0]
|
||||
x1, y1 = self.currentRect[2]
|
||||
return np.array([x0, y0, x1, y1])
|
||||
|
||||
def getNextFrame(self):
|
||||
img = self.sceneBg.copy()
|
||||
|
||||
if self.foreground is not None:
|
||||
self.currentCenter = (self.center[0] + self.getXOffset(self.time), self.center[1] + self.getYOffset(self.time))
|
||||
img[self.currentCenter[0]:self.currentCenter[0]+self.foreground.shape[0],
|
||||
self.currentCenter[1]:self.currentCenter[1]+self.foreground.shape[1]] = self.foreground
|
||||
else:
|
||||
self.currentRect = self.initialRect + int( 30*cos(self.time) + 50*sin(self.time/3))
|
||||
if self.deformation:
|
||||
self.currentRect[1:3] += int(self.h/20*cos(self.time))
|
||||
cv.fillConvexPoly(img, self.currentRect, (0, 0, 255))
|
||||
|
||||
self.time += self.timeStep
|
||||
|
||||
if self.noise:
|
||||
noise = np.zeros(self.sceneBg.shape, np.int8)
|
||||
cv.randn(noise, np.zeros(3), np.ones(3)*255*self.noise)
|
||||
img = cv.add(img, noise, dtype=cv.CV_8UC3)
|
||||
return img
|
||||
|
||||
def resetTime(self):
|
||||
self.time = 0.0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
backGr = cv.imread('../../../samples/data/lena.jpg')
|
||||
|
||||
render = TestSceneRender(backGr, noise = 0.5)
|
||||
|
||||
while True:
|
||||
|
||||
img = render.getNextFrame()
|
||||
cv.imshow('img', img)
|
||||
|
||||
ch = cv.waitKey(3)
|
||||
if ch == 27:
|
||||
break
|
||||
cv.destroyAllWindows()
|
||||
Reference in New Issue
Block a user