diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 630c64c949..e31900e601 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -82,3 +82,47 @@ ocv_add_accuracy_tests() ocv_add_perf_tests() ocv_install_3rdparty_licenses(SoftFloat "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SoftFloat/COPYING.txt") + + +# generate data (samples data) config file +set(OPENCV_DATA_CONFIG_FILE "${CMAKE_BINARY_DIR}/opencv_data_config.hpp") +set(OPENCV_DATA_CONFIG_STR "") + +if(CMAKE_INSTALL_PREFIX) + set(OPENCV_DATA_CONFIG_STR "${OPENCV_DATA_CONFIG_STR} +#define OPENCV_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\" +") +endif() +if(OPENCV_OTHER_INSTALL_PATH) + set(OPENCV_DATA_CONFIG_STR "${OPENCV_DATA_CONFIG_STR} +#define OPENCV_DATA_INSTALL_PATH \"${OPENCV_OTHER_INSTALL_PATH}\" +") +endif() + +set(OPENCV_DATA_CONFIG_STR "${OPENCV_DATA_CONFIG_STR} +#define OPENCV_BUILD_DIR \"${CMAKE_BINARY_DIR}\" +") + +file(RELATIVE_PATH SOURCE_DIR_RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) +set(OPENCV_DATA_CONFIG_STR "${OPENCV_DATA_CONFIG_STR} +#define OPENCV_DATA_BUILD_DIR_SEARCH_PATHS \\ + \"${SOURCE_DIR_RELATIVE}/\" +") + +if(WIN32) + file(RELATIVE_PATH INSTALL_DATA_DIR_RELATIVE "${CMAKE_INSTALL_PREFIX}/${OPENCV_BIN_INSTALL_PATH}" "${CMAKE_INSTALL_PREFIX}/${OPENCV_OTHER_INSTALL_PATH}") +else() + file(RELATIVE_PATH INSTALL_DATA_DIR_RELATIVE "${CMAKE_INSTALL_PREFIX}/${OPENCV_LIB_INSTALL_PATH}" "${CMAKE_INSTALL_PREFIX}/${OPENCV_OTHER_INSTALL_PATH}") +endif() +list(APPEND OPENCV_INSTALL_DATA_DIR_RELATIVE "${INSTALL_DATA_DIR_RELATIVE}") +string(REPLACE ";" "\",\\\n \"" OPENCV_INSTALL_DATA_DIR_RELATIVE_STR "\"${OPENCV_INSTALL_DATA_DIR_RELATIVE}\"") +set(OPENCV_DATA_CONFIG_STR "${OPENCV_DATA_CONFIG_STR} +#define OPENCV_INSTALL_DATA_DIR_RELATIVE ${OPENCV_INSTALL_DATA_DIR_RELATIVE_STR} +") + +if(EXISTS "${OPENCV_DATA_CONFIG_FILE}") + file(READ "${OPENCV_DATA_CONFIG_FILE}" __content) +endif() +if(NOT OPENCV_DATA_CONFIG_STR STREQUAL "${__content}") + file(WRITE "${OPENCV_DATA_CONFIG_FILE}" "${OPENCV_DATA_CONFIG_STR}") +endif() diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 8e61322164..089c0db334 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -75,6 +75,7 @@ @defgroup core_utils_sse SSE utilities @defgroup core_utils_neon NEON utilities @defgroup core_utils_softfloat Softfloat support + @defgroup core_utils_samples Utility functions for OpenCV samples @} @defgroup core_opengl OpenGL interoperability @defgroup core_ipp Intel IPP Asynchronous C/C++ Converters diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index 26611f04ae..1ea8c28643 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -801,6 +801,82 @@ CV_EXPORTS InstrNode* getCurrentNode(); #define CV_INSTRUMENT_REGION(); CV_INSTRUMENT_REGION_(); #endif +namespace cv { + +namespace utils { + +//! @addtogroup core_utils +//! @{ + +/** @brief Try to find requested data file + +Search directories: + +1. Directories passed via `addDataSearchPath()` +2. Check path specified by configuration parameter with "_HINT" suffix (name of environment variable). +3. Check path specified by configuration parameter (name of environment variable). + If parameter value is not empty and nothing is found then stop searching. +4. Detects build/install path based on: + a. current working directory (CWD) + b. and/or binary module location (opencv_core/opencv_world, doesn't work with static linkage) +5. Scan `/{,data}` directories if build directory is detected or the current directory is in source tree. +6. Scan `/share/OpenCV` directory if install directory is detected. + +@param relative_path Relative path to data file +@param required Specify "file not found" handling. + If true, function prints information message and raises cv::Exception. + If false, function returns empty result +@param configuration_parameter specify configuration parameter name. Default NULL value means "OPENCV_DATA_PATH". +@return Returns path (absolute or relative to the current directory) or empty string if file is not found + +@note Implementation is not thread-safe. +*/ +CV_EXPORTS +cv::String findDataFile(const cv::String& relative_path, bool required = true, + const char* configuration_parameter = NULL); + +/** @overload +@param relative_path Relative path to data file +@param configuration_parameter specify configuration parameter name. Default NULL value means "OPENCV_DATA_PATH". +@param search_paths override addDataSearchPath() settings. +@param subdir_paths override addDataSearchSubDirectory() settings. +@return Returns path (absolute or relative to the current directory) or empty string if file is not found + +@note Implementation is not thread-safe. +*/ +CV_EXPORTS +cv::String findDataFile(const cv::String& relative_path, + const char* configuration_parameter, + const std::vector* search_paths, + const std::vector* subdir_paths); + +/** @brief Override default search data path by adding new search location + +Use this only to override default behavior +Passed paths are used in LIFO order. + +@param path Path to used samples data + +@note Implementation is not thread-safe. +*/ +CV_EXPORTS void addDataSearchPath(const cv::String& path); + +/** @brief Append default search data sub directory + +General usage is to add OpenCV modules name (`/modules//data` -> `modules//data` + `/data`). +Passed subdirectories are used in LIFO order. + +@param subdir samples data sub directory + +@note Implementation is not thread-safe. +*/ +CV_EXPORTS void addDataSearchSubDirectory(const cv::String& subdir); + +//! @} + +} // namespace utils +} // namespace cv + //! @endcond #endif // OPENCV_CORE_PRIVATE_HPP diff --git a/modules/core/include/opencv2/core/utility.hpp b/modules/core/include/opencv2/core/utility.hpp index 8534b6e729..7a7158f5f9 100644 --- a/modules/core/include/opencv2/core/utility.hpp +++ b/modules/core/include/opencv2/core/utility.hpp @@ -1274,8 +1274,75 @@ enum FLAGS CV_EXPORTS void setFlags(FLAGS modeFlags); static inline void setFlags(int modeFlags) { setFlags((FLAGS)modeFlags); } CV_EXPORTS FLAGS getFlags(); + +} // namespace instr + + +namespace samples { + +//! @addtogroup core_utils_samples +// This section describes utility functions for OpenCV samples. +// +// @note Implementation of these utilities is not thread-safe. +// +//! @{ + +/** @brief Try to find requested data file + +Search directories: + +1. Directories passed via `addSamplesDataSearchPath()` +2. OPENCV_SAMPLES_DATA_PATH_HINT environment variable +3. OPENCV_SAMPLES_DATA_PATH environment variable + If parameter value is not empty and nothing is found then stop searching. +4. Detects build/install path based on: + a. current working directory (CWD) + b. and/or binary module location (opencv_core/opencv_world, doesn't work with static linkage) +5. Scan `/{,data,samples/data}` directories if build directory is detected or the current directory is in source tree. +6. Scan `/share/OpenCV` directory if install directory is detected. + +@see cv::utils::findDataFile + +@param relative_path Relative path to data file +@param required Specify "file not found" handling. + If true, function prints information message and raises cv::Exception. + If false, function returns empty result +@param silentMode Disables messages +@return Returns path (absolute or relative to the current directory) or empty string if file is not found +*/ +CV_EXPORTS_W cv::String findFile(const cv::String& relative_path, bool required = true, bool silentMode = false); + +CV_EXPORTS_W cv::String findFileOrKeep(const cv::String& relative_path, bool silentMode = false); + +inline cv::String findFileOrKeep(const cv::String& relative_path, bool silentMode) +{ + cv::String res = findFile(relative_path, false, silentMode); + if (res.empty()) + return relative_path; + return res; } +/** @brief Override search data path by adding new search location + +Use this only to override default behavior +Passed paths are used in LIFO order. + +@param path Path to used samples data +*/ +CV_EXPORTS_W void addSamplesDataSearchPath(const cv::String& path); + +/** @brief Append samples search data sub directory + +General usage is to add OpenCV modules name (`/modules//samples/data` -> `/samples/data` + `modules//samples/data`). +Passed subdirectories are used in LIFO order. + +@param subdir samples data sub directory +*/ +CV_EXPORTS_W void addSamplesDataSearchSubDirectory(const cv::String& subdir); + +//! @} +} // namespace samples + namespace utils { CV_EXPORTS int getThreadID(); diff --git a/modules/core/include/opencv2/core/utils/filesystem.hpp b/modules/core/include/opencv2/core/utils/filesystem.hpp index 12b10a76ae..00b0dd1c12 100644 --- a/modules/core/include/opencv2/core/utils/filesystem.hpp +++ b/modules/core/include/opencv2/core/utils/filesystem.hpp @@ -16,6 +16,13 @@ CV_EXPORTS void remove_all(const cv::String& path); CV_EXPORTS cv::String getcwd(); +/** @brief Converts path p to a canonical absolute path + * Symlinks are processed if there is support for them on running platform. + * + * @param path input path. Target file/directory should exist. + */ +CV_EXPORTS cv::String canonical(const cv::String& path); + /** Join path components */ CV_EXPORTS cv::String join(const cv::String& base, const cv::String& path); diff --git a/modules/core/src/utils/datafile.cpp b/modules/core/src/utils/datafile.cpp new file mode 100644 index 0000000000..4973a55422 --- /dev/null +++ b/modules/core/src/utils/datafile.cpp @@ -0,0 +1,398 @@ +// 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. + +#include "../precomp.hpp" + +#include "opencv_data_config.hpp" + +#include +#include + +#include +#undef CV_LOG_STRIP_LEVEL +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1 +#include "opencv2/core/utils/logger.hpp" +#include "opencv2/core/utils/filesystem.hpp" + +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef small +#undef min +#undef max +#undef abs +#elif defined(__APPLE__) +#include +#if TARGET_OS_MAC +#include +#endif +#endif + +namespace cv { namespace utils { + +static cv::Ptr< std::vector > g_data_search_path; +static cv::Ptr< std::vector > g_data_search_subdir; + +static std::vector& _getDataSearchPath() +{ + if (g_data_search_path.empty()) + g_data_search_path.reset(new std::vector()); + return *(g_data_search_path.get()); +} + +static std::vector& _getDataSearchSubDirectory() +{ + if (g_data_search_subdir.empty()) + { + g_data_search_subdir.reset(new std::vector()); + g_data_search_subdir->push_back("data"); + g_data_search_subdir->push_back(""); + } + return *(g_data_search_subdir.get()); +} + + +CV_EXPORTS void addDataSearchPath(const cv::String& path) +{ + if (utils::fs::isDirectory(path)) + _getDataSearchPath().push_back(path); +} +CV_EXPORTS void addDataSearchSubDirectory(const cv::String& subdir) +{ + _getDataSearchSubDirectory().push_back(subdir); +} + +static bool isPathSep(char c) +{ + return c == '/' || c == '\\'; +} +static bool isSubDirectory_(const cv::String& base_path, const cv::String& path) +{ + size_t N = base_path.size(); + if (N == 0) + return false; + if (isPathSep(base_path[N - 1])) + N--; + if (path.size() < N) + return false; + for (size_t i = 0; i < N; i++) + { + if (path[i] == base_path[i]) + continue; + if (isPathSep(path[i]) && isPathSep(base_path[i])) + continue; + return false; + } + size_t M = path.size(); + if (M > N) + { + if (!isPathSep(path[N])) + return false; + } + return true; +} +static bool isSubDirectory(const cv::String& base_path, const cv::String& path) +{ + bool res = isSubDirectory_(base_path, path); + CV_LOG_VERBOSE(NULL, 0, "isSubDirectory(): base: " << base_path << " path: " << path << " => result: " << (res ? "TRUE" : "FALSE")); + return res; +} + +static cv::String getModuleLocation(const void* addr) +{ + CV_UNUSED(addr); +#ifdef _WIN32 + HMODULE m = 0; +#if _WIN32_WINNT >= 0x0501 + ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(addr), + &m); +#endif + if (m) + { + char path[MAX_PATH]; + const size_t path_size = sizeof(path)/sizeof(*path); + size_t sz = GetModuleFileNameA(m, path, path_size); // no unicode support + if (sz > 0 && sz < path_size) + { + path[sz] = '\0'; + return cv::String(path); + } + } +#elif defined(__linux__) + std::ifstream fs("/proc/self/maps"); + std::string line; + while (std::getline(fs, line, '\n')) + { + long long int addr_begin = 0, addr_end = 0; + if (2 == sscanf(line.c_str(), "%llx-%llx", &addr_begin, &addr_end)) + { + if ((intptr_t)addr >= (intptr_t)addr_begin && (intptr_t)addr < (intptr_t)addr_end) + { + size_t pos = line.rfind(" "); // 2 spaces + if (pos == cv::String::npos) + pos = line.rfind(' '); // 1 spaces + else + pos++; + if (pos == cv::String::npos) + { + CV_LOG_DEBUG(NULL, "Can't parse module path: '" << line << '\''); + } + return line.substr(pos + 1); + } + } + } +#elif defined(__APPLE__) +# if TARGET_OS_MAC + Dl_info info; + if (0 != dladdr(addr, &info)) + { + return cv::String(info.dli_fname); + } +# endif +#else + // not supported, skip +#endif + return cv::String(); +} + +cv::String findDataFile(const cv::String& relative_path, + const char* configuration_parameter, + const std::vector* search_paths, + const std::vector* subdir_paths) +{ + configuration_parameter = configuration_parameter ? configuration_parameter : "OPENCV_DATA_PATH"; + CV_LOG_DEBUG(NULL, cv::format("utils::findDataFile('%s', %s)", relative_path.c_str(), configuration_parameter)); + +#define TRY_FILE_WITH_PREFIX(prefix) \ +{ \ + cv::String path = utils::fs::join(prefix, relative_path); \ + CV_LOG_DEBUG(NULL, cv::format("... Line %d: trying open '%s'", __LINE__, path.c_str())); \ + FILE* f = fopen(path.c_str(), "rb"); \ + if(f) { \ + fclose(f); \ + return path; \ + } \ +} + + + // Step 0: check current directory or absolute path at first + TRY_FILE_WITH_PREFIX(""); + + + // Step 1 + const std::vector& search_path = search_paths ? *search_paths : _getDataSearchPath(); + for(size_t i = search_path.size(); i > 0; i--) + { + const cv::String& prefix = search_path[i - 1]; + TRY_FILE_WITH_PREFIX(prefix); + } + + const std::vector& search_subdir = subdir_paths ? *subdir_paths : _getDataSearchSubDirectory(); + + + // Step 2 + const cv::String configuration_parameter_s(configuration_parameter ? configuration_parameter : ""); + const cv::utils::Paths& search_hint = configuration_parameter_s.empty() ? cv::utils::Paths() + : getConfigurationParameterPaths((configuration_parameter_s + "_HINT").c_str()); + for (size_t k = 0; k < search_hint.size(); k++) + { + cv::String datapath = search_hint[k]; + if (datapath.empty()) + continue; + if (utils::fs::isDirectory(datapath)) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying " << configuration_parameter << "_HINT=" << datapath); + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + else + { + CV_LOG_WARNING(NULL, configuration_parameter << "_HINT is specified but it is not a directory: " << datapath); + } + } + + + // Step 3 + const cv::utils::Paths& override_paths = configuration_parameter_s.empty() ? cv::utils::Paths() + : getConfigurationParameterPaths(configuration_parameter); + for (size_t k = 0; k < override_paths.size(); k++) + { + cv::String datapath = override_paths[k]; + if (datapath.empty()) + continue; + if (utils::fs::isDirectory(datapath)) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying " << configuration_parameter << "=" << datapath); + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + else + { + CV_LOG_WARNING(NULL, configuration_parameter << " is specified but it is not a directory: " << datapath); + } + } + if (!override_paths.empty()) + { + CV_LOG_INFO(NULL, "utils::findDataFile(): can't find data file via " << configuration_parameter << " configuration override: " << relative_path); + return cv::String(); + } + + + // Steps: 4, 5, 6 + cv::String cwd = utils::fs::getcwd(); + cv::String build_dir(OPENCV_BUILD_DIR); + bool has_tested_build_directory = false; + if (isSubDirectory(build_dir, cwd) || isSubDirectory(utils::fs::canonical(build_dir), utils::fs::canonical(cwd))) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): the current directory is build sub-directory: " << cwd); + const char* build_subdirs[] = { OPENCV_DATA_BUILD_DIR_SEARCH_PATHS }; + for (size_t k = 0; k < sizeof(build_subdirs)/sizeof(build_subdirs[0]); k++) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): /" << build_subdirs[k]); + cv::String datapath = utils::fs::join(build_dir, build_subdirs[k]); + if (utils::fs::isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + } + has_tested_build_directory = true; + } + + cv::String source_dir; + cv::String try_source_dir = cwd; + for (int levels = 0; levels < 3; ++levels) + { + if (utils::fs::exists(utils::fs::join(try_source_dir, "modules/core/include/opencv2/core/version.hpp"))) + { + source_dir = try_source_dir; + break; + } + try_source_dir = utils::fs::join(try_source_dir, "/.."); + } + if (!source_dir.empty()) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): the current directory is source sub-directory: " << source_dir); + CV_LOG_DEBUG(NULL, "utils::findDataFile(): " << source_dir); + cv::String datapath = source_dir; + if (utils::fs::isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + } + + cv::String module_path = getModuleLocation((void*)getModuleLocation); // use code addr, doesn't work with static linkage! + CV_LOG_DEBUG(NULL, "Detected module path: '" << module_path << '\''); + + if (!has_tested_build_directory && + (isSubDirectory(build_dir, module_path) || isSubDirectory(utils::fs::canonical(build_dir), utils::fs::canonical(module_path))) + ) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): the binary module directory is build sub-directory: " << module_path); + const char* build_subdirs[] = { OPENCV_DATA_BUILD_DIR_SEARCH_PATHS }; + for (size_t k = 0; k < sizeof(build_subdirs)/sizeof(build_subdirs[0]); k++) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): /" << build_subdirs[k]); + cv::String datapath = utils::fs::join(build_dir, build_subdirs[k]); + if (utils::fs::isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + } + } + +#if defined OPENCV_INSTALL_DATA_DIR_RELATIVE + if (!module_path.empty()) // require module path + { + size_t pos = module_path.rfind('/'); + if (pos == cv::String::npos) + pos = module_path.rfind('\\'); + cv::String module_dir = (pos == cv::String::npos) ? module_path : module_path.substr(0, pos); + const char* install_subdirs[] = { OPENCV_INSTALL_DATA_DIR_RELATIVE }; + for (size_t k = 0; k < sizeof(install_subdirs)/sizeof(install_subdirs[0]); k++) + { + cv::String datapath = utils::fs::join(module_dir, install_subdirs[k]); + CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying install path (from binary path): " << datapath); + if (utils::fs::isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + else + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): ... skip, not a valid directory: " << datapath); + } + } + } +#endif + +#if defined OPENCV_INSTALL_PREFIX && defined OPENCV_DATA_INSTALL_PATH + cv::String install_dir(OPENCV_INSTALL_PREFIX); + // use core/world module path and verify that library is running from installation directory + // It is neccessary to avoid touching of unrelated common /usr/local path + if (module_path.empty()) // can't determine + module_path = install_dir; + if (isSubDirectory(install_dir, module_path) || isSubDirectory(utils::fs::canonical(install_dir), utils::fs::canonical(module_path))) + { + cv::String datapath = utils::fs::join(install_dir, OPENCV_DATA_INSTALL_PATH); + if (utils::fs::isDirectory(datapath)) + { + CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying install path: " << datapath); + for(size_t i = search_subdir.size(); i > 0; i--) + { + const cv::String& subdir = search_subdir[i - 1]; + cv::String prefix = utils::fs::join(datapath, subdir); + TRY_FILE_WITH_PREFIX(prefix); + } + } + } +#endif + + return cv::String(); // not found +} + +cv::String findDataFile(const cv::String& relative_path, bool required, const char* configuration_parameter) +{ + CV_LOG_DEBUG(NULL, cv::format("cv::utils::findDataFile('%s', %s, %s)", + relative_path.c_str(), required ? "true" : "false", + configuration_parameter ? configuration_parameter : "NULL")); + cv::String result = cv::utils::findDataFile(relative_path, + configuration_parameter, + NULL, + NULL); + if (result.empty() && required) + CV_Error(cv::Error::StsError, cv::format("OpenCV: Can't find required data file: %s", relative_path.c_str())); + return result; +} + +}} // namespace diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index c58374da61..99d764f1ea 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -85,6 +85,20 @@ cv::String join(const cv::String& base, const cv::String& path) #if OPENCV_HAVE_FILESYSTEM_SUPPORT +cv::String canonical(const cv::String& path) +{ +#ifdef _WIN32 + const char* result = _fullpath(NULL, path.c_str(), 0); +#else + const char* result = realpath(path.c_str(), NULL); +#endif + if (result) + return cv::String(result); + // no error handling, just return input + return path; +} + + bool exists(const cv::String& path) { CV_INSTRUMENT_REGION(); @@ -543,11 +557,12 @@ cv::String getCacheDirectory(const char* sub_directory_name, const char* configu #else #define NOT_IMPLEMENTED CV_Error(Error::StsNotImplemented, ""); -CV_EXPORTS bool exists(const cv::String& /*path*/) { NOT_IMPLEMENTED } -CV_EXPORTS void remove_all(const cv::String& /*path*/) { NOT_IMPLEMENTED } -CV_EXPORTS bool createDirectory(const cv::String& /*path*/) { NOT_IMPLEMENTED } -CV_EXPORTS bool createDirectories(const cv::String& /*path*/) { NOT_IMPLEMENTED } -CV_EXPORTS cv::String getCacheDirectory(const char* /*sub_directory_name*/, const char* /*configuration_name = NULL*/) { NOT_IMPLEMENTED } +cv::String canonical(const cv::String& /*path*/) { NOT_IMPLEMENTED } +bool exists(const cv::String& /*path*/) { NOT_IMPLEMENTED } +void remove_all(const cv::String& /*path*/) { NOT_IMPLEMENTED } +bool createDirectory(const cv::String& /*path*/) { NOT_IMPLEMENTED } +bool createDirectories(const cv::String& /*path*/) { NOT_IMPLEMENTED } +cv::String getCacheDirectory(const char* /*sub_directory_name*/, const char* /*configuration_name = NULL*/) { NOT_IMPLEMENTED } #undef NOT_IMPLEMENTED #endif // OPENCV_HAVE_FILESYSTEM_SUPPORT diff --git a/modules/core/src/utils/samples.cpp b/modules/core/src/utils/samples.cpp new file mode 100644 index 0000000000..c1162f85fe --- /dev/null +++ b/modules/core/src/utils/samples.cpp @@ -0,0 +1,67 @@ +// 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. + +#include "../precomp.hpp" + +#include + +#include +#undef CV_LOG_STRIP_LEVEL +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1 +#include "opencv2/core/utils/logger.hpp" +#include "opencv2/core/utils/filesystem.hpp" + +namespace cv { namespace samples { + +static cv::Ptr< std::vector > g_data_search_path; +static cv::Ptr< std::vector > g_data_search_subdir; + +static std::vector& _getDataSearchPath() +{ + if (g_data_search_path.empty()) + g_data_search_path.reset(new std::vector()); + return *(g_data_search_path.get()); +} + +static std::vector& _getDataSearchSubDirectory() +{ + if (g_data_search_subdir.empty()) + { + g_data_search_subdir.reset(new std::vector()); + g_data_search_subdir->push_back("samples/data"); + g_data_search_subdir->push_back("data"); + g_data_search_subdir->push_back(""); + } + return *(g_data_search_subdir.get()); +} + + +CV_EXPORTS void addSamplesDataSearchPath(const cv::String& path) +{ + if (utils::fs::isDirectory(path)) + _getDataSearchPath().push_back(path); +} +CV_EXPORTS void addSamplesDataSearchSubDirectory(const cv::String& subdir) +{ + _getDataSearchSubDirectory().push_back(subdir); +} + +cv::String findFile(const cv::String& relative_path, bool required, bool silentMode) +{ + CV_LOG_DEBUG(NULL, cv::format("cv::samples::findFile('%s', %s)", relative_path.c_str(), required ? "true" : "false")); + cv::String result = cv::utils::findDataFile(relative_path, + "OPENCV_SAMPLES_DATA_PATH", + &_getDataSearchPath(), + &_getDataSearchSubDirectory()); + if (result != relative_path && !silentMode) + { + CV_LOG_WARNING(NULL, "cv::samples::findFile('" << relative_path << "') => '" << result << "'"); + } + if (result.empty() && required) + CV_Error(cv::Error::StsError, cv::format("OpenCV samples: Can't find required data file: %s", relative_path.c_str())); + return result; +} + + +}} // namespace diff --git a/modules/core/test/test_utils.cpp b/modules/core/test/test_utils.cpp index 2a1503a33c..c566762925 100644 --- a/modules/core/test/test_utils.cpp +++ b/modules/core/test/test_utils.cpp @@ -2,6 +2,7 @@ // 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. #include "test_precomp.hpp" +#include "opencv2/core/utils/logger.hpp" namespace opencv_test { namespace { @@ -283,4 +284,21 @@ TEST(CommandLineParser, testScalar) EXPECT_EQ(parser.get("s5"), Scalar(5, -4, 3, 2)); } +TEST(Samples, findFile) +{ + cv::utils::logging::LogLevel prev = cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_VERBOSE); + cv::String path; + ASSERT_NO_THROW(path = samples::findFile("lena.jpg", false)); + EXPECT_NE(std::string(), path.c_str()); + cv::utils::logging::setLogLevel(prev); +} + +TEST(Samples, findFile_missing) +{ + cv::utils::logging::LogLevel prev = cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_VERBOSE); + cv::String path; + ASSERT_ANY_THROW(path = samples::findFile("non-existed.file", true)); + cv::utils::logging::setLogLevel(prev); +} + }} // namespace diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index e84de50247..892215b9a1 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -84,6 +84,23 @@ class Arguments(NewOpenCVTests): 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 type(0)=CV_32FC2") +class SamplesFindFile(NewOpenCVTests): + + def test_ExistedFile(self): + res = cv.samples.findFile('lena.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 + if __name__ == '__main__': NewOpenCVTests.bootstrap()