1 | // This file is part of OpenCV project. |
2 | // It is subject to the license terms in the LICENSE file found in the top-level directory |
3 | // of this distribution and at http://opencv.org/license.html. |
4 | |
5 | #include "../precomp.hpp" |
6 | |
7 | #include "opencv_data_config.hpp" |
8 | |
9 | #include <vector> |
10 | #include <fstream> |
11 | |
12 | #include <opencv2/core/utils/logger.defines.hpp> |
13 | #undef CV_LOG_STRIP_LEVEL |
14 | #define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1 |
15 | #include "opencv2/core/utils/logger.hpp" |
16 | #include "opencv2/core/utils/filesystem.hpp" |
17 | |
18 | #include <opencv2/core/utils/configuration.private.hpp> |
19 | #include "opencv2/core/utils/filesystem.private.hpp" |
20 | |
21 | #ifdef _WIN32 |
22 | #define WIN32_LEAN_AND_MEAN |
23 | #include <windows.h> |
24 | #undef small |
25 | #undef min |
26 | #undef max |
27 | #undef abs |
28 | #elif defined(__linux__) |
29 | #include <dlfcn.h> // requires -ldl |
30 | #elif defined(__APPLE__) |
31 | #include <TargetConditionals.h> |
32 | #if TARGET_OS_MAC |
33 | #include <dlfcn.h> |
34 | #endif |
35 | #endif |
36 | |
37 | namespace cv { namespace utils { |
38 | |
39 | static cv::Ptr< std::vector<cv::String> > g_data_search_path; |
40 | static cv::Ptr< std::vector<cv::String> > g_data_search_subdir; |
41 | |
42 | static std::vector<cv::String>& _getDataSearchPath() |
43 | { |
44 | if (g_data_search_path.empty()) |
45 | g_data_search_path.reset(ptr: new std::vector<cv::String>()); |
46 | return *(g_data_search_path.get()); |
47 | } |
48 | |
49 | static std::vector<cv::String>& _getDataSearchSubDirectory() |
50 | { |
51 | if (g_data_search_subdir.empty()) |
52 | { |
53 | g_data_search_subdir.reset(ptr: new std::vector<cv::String>()); |
54 | g_data_search_subdir->push_back(x: "data" ); |
55 | g_data_search_subdir->push_back(x: "" ); |
56 | } |
57 | return *(g_data_search_subdir.get()); |
58 | } |
59 | |
60 | |
61 | CV_EXPORTS void addDataSearchPath(const cv::String& path) |
62 | { |
63 | if (utils::fs::isDirectory(path)) |
64 | _getDataSearchPath().push_back(x: path); |
65 | } |
66 | CV_EXPORTS void addDataSearchSubDirectory(const cv::String& subdir) |
67 | { |
68 | _getDataSearchSubDirectory().push_back(x: subdir); |
69 | } |
70 | |
71 | #if OPENCV_HAVE_FILESYSTEM_SUPPORT |
72 | static bool isPathSep(char c) |
73 | { |
74 | return c == '/' || c == '\\'; |
75 | } |
76 | static bool isSubDirectory_(const cv::String& base_path, const cv::String& path) |
77 | { |
78 | size_t N = base_path.size(); |
79 | if (N == 0) |
80 | return false; |
81 | if (isPathSep(c: base_path[N - 1])) |
82 | N--; |
83 | if (path.size() < N) |
84 | return false; |
85 | for (size_t i = 0; i < N; i++) |
86 | { |
87 | if (path[i] == base_path[i]) |
88 | continue; |
89 | if (isPathSep(c: path[i]) && isPathSep(c: base_path[i])) |
90 | continue; |
91 | return false; |
92 | } |
93 | size_t M = path.size(); |
94 | if (M > N) |
95 | { |
96 | if (!isPathSep(c: path[N])) |
97 | return false; |
98 | } |
99 | return true; |
100 | } |
101 | |
102 | static bool isSubDirectory(const cv::String& base_path, const cv::String& path) |
103 | { |
104 | bool res = isSubDirectory_(base_path, path); |
105 | CV_LOG_VERBOSE(NULL, 0, "isSubDirectory(): base: " << base_path << " path: " << path << " => result: " << (res ? "TRUE" : "FALSE" )); |
106 | return res; |
107 | } |
108 | #endif //OPENCV_HAVE_FILESYSTEM_SUPPORT |
109 | |
110 | static cv::String getModuleLocation(const void* addr) |
111 | { |
112 | CV_UNUSED(addr); |
113 | #ifdef _WIN32 |
114 | HMODULE m = 0; |
115 | #if _WIN32_WINNT >= 0x0501 && (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) |
116 | ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
117 | reinterpret_cast<LPCTSTR>(addr), |
118 | &m); |
119 | #endif |
120 | if (m) |
121 | { |
122 | TCHAR path[MAX_PATH]; |
123 | const size_t path_size = sizeof(path) / sizeof(*path); |
124 | size_t sz = GetModuleFileName(m, path, path_size); |
125 | if (sz > 0 && sz < path_size) |
126 | { |
127 | path[sz] = TCHAR('\0'); |
128 | #ifdef _UNICODE |
129 | char char_path[MAX_PATH]; |
130 | size_t copied = wcstombs(char_path, path, MAX_PATH); |
131 | CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1)); |
132 | return cv::String(char_path); |
133 | #else |
134 | return cv::String(path); |
135 | #endif |
136 | } |
137 | } |
138 | #elif defined(__linux__) |
139 | Dl_info info; |
140 | if (0 != dladdr(address: addr, info: &info)) |
141 | { |
142 | return cv::String(info.dli_fname); |
143 | } |
144 | #elif defined(__APPLE__) |
145 | # if TARGET_OS_MAC |
146 | Dl_info info; |
147 | if (0 != dladdr(addr, &info)) |
148 | { |
149 | return cv::String(info.dli_fname); |
150 | } |
151 | # endif |
152 | #else |
153 | // not supported, skip |
154 | #endif |
155 | return cv::String(); |
156 | } |
157 | |
158 | bool getBinLocation(std::string& dst) |
159 | { |
160 | dst = getModuleLocation(addr: (void*)getModuleLocation); // using code address, doesn't work with static linkage! |
161 | return !dst.empty(); |
162 | } |
163 | |
164 | #ifdef _WIN32 |
165 | bool getBinLocation(std::wstring& dst) |
166 | { |
167 | void* addr = (void*)getModuleLocation; // using code address, doesn't work with static linkage! |
168 | HMODULE m = 0; |
169 | #if _WIN32_WINNT >= 0x0501 && (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) |
170 | ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
171 | reinterpret_cast<LPCTSTR>(addr), |
172 | &m); |
173 | #endif |
174 | if (m) |
175 | { |
176 | wchar_t path[4096]; |
177 | const size_t path_size = sizeof(path)/sizeof(*path); |
178 | size_t sz = GetModuleFileNameW(m, path, path_size); |
179 | if (sz > 0 && sz < path_size) |
180 | { |
181 | path[sz] = '\0'; |
182 | dst.assign(path, sz); |
183 | return true; |
184 | } |
185 | } |
186 | return false; |
187 | } |
188 | #endif |
189 | |
190 | cv::String findDataFile(const cv::String& relative_path, |
191 | const char* configuration_parameter, |
192 | const std::vector<String>* search_paths, |
193 | const std::vector<String>* subdir_paths) |
194 | { |
195 | #if OPENCV_HAVE_FILESYSTEM_SUPPORT |
196 | configuration_parameter = configuration_parameter ? configuration_parameter : "OPENCV_DATA_PATH" ; |
197 | CV_LOG_DEBUG(NULL, cv::format("utils::findDataFile('%s', %s)" , relative_path.c_str(), configuration_parameter)); |
198 | |
199 | #define TRY_FILE_WITH_PREFIX(prefix) \ |
200 | { \ |
201 | cv::String path = utils::fs::join(prefix, relative_path); \ |
202 | CV_LOG_DEBUG(NULL, cv::format("... Line %d: trying open '%s'", __LINE__, path.c_str())); \ |
203 | FILE* f = fopen(path.c_str(), "rb"); \ |
204 | if(f) { \ |
205 | fclose(f); \ |
206 | return path; \ |
207 | } \ |
208 | } |
209 | |
210 | |
211 | // Step 0: check current directory or absolute path at first |
212 | TRY_FILE_WITH_PREFIX("" ); |
213 | |
214 | |
215 | // Step 1 |
216 | const std::vector<cv::String>& search_path = search_paths ? *search_paths : _getDataSearchPath(); |
217 | for(size_t i = search_path.size(); i > 0; i--) |
218 | { |
219 | const cv::String& prefix = search_path[i - 1]; |
220 | TRY_FILE_WITH_PREFIX(prefix); |
221 | } |
222 | |
223 | const std::vector<cv::String>& search_subdir = subdir_paths ? *subdir_paths : _getDataSearchSubDirectory(); |
224 | |
225 | |
226 | // Step 2 |
227 | const cv::String configuration_parameter_s(configuration_parameter ? configuration_parameter : "" ); |
228 | const cv::utils::Paths& search_hint = configuration_parameter_s.empty() ? cv::utils::Paths() |
229 | : getConfigurationParameterPaths(name: (configuration_parameter_s + "_HINT" ).c_str()); |
230 | for (size_t k = 0; k < search_hint.size(); k++) |
231 | { |
232 | cv::String datapath = search_hint[k]; |
233 | if (datapath.empty()) |
234 | continue; |
235 | if (utils::fs::isDirectory(path: datapath)) |
236 | { |
237 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying " << configuration_parameter << "_HINT=" << datapath); |
238 | for(size_t i = search_subdir.size(); i > 0; i--) |
239 | { |
240 | const cv::String& subdir = search_subdir[i - 1]; |
241 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
242 | TRY_FILE_WITH_PREFIX(prefix); |
243 | } |
244 | } |
245 | else |
246 | { |
247 | CV_LOG_WARNING(NULL, configuration_parameter << "_HINT is specified but it is not a directory: " << datapath); |
248 | } |
249 | } |
250 | |
251 | |
252 | // Step 3 |
253 | const cv::utils::Paths& override_paths = configuration_parameter_s.empty() ? cv::utils::Paths() |
254 | : getConfigurationParameterPaths(name: configuration_parameter); |
255 | for (size_t k = 0; k < override_paths.size(); k++) |
256 | { |
257 | cv::String datapath = override_paths[k]; |
258 | if (datapath.empty()) |
259 | continue; |
260 | if (utils::fs::isDirectory(path: datapath)) |
261 | { |
262 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying " << configuration_parameter << "=" << datapath); |
263 | for(size_t i = search_subdir.size(); i > 0; i--) |
264 | { |
265 | const cv::String& subdir = search_subdir[i - 1]; |
266 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
267 | TRY_FILE_WITH_PREFIX(prefix); |
268 | } |
269 | } |
270 | else |
271 | { |
272 | CV_LOG_WARNING(NULL, configuration_parameter << " is specified but it is not a directory: " << datapath); |
273 | } |
274 | } |
275 | if (!override_paths.empty()) |
276 | { |
277 | CV_LOG_INFO(NULL, "utils::findDataFile(): can't find data file via " << configuration_parameter << " configuration override: " << relative_path); |
278 | return cv::String(); |
279 | } |
280 | |
281 | |
282 | // Steps: 4, 5, 6 |
283 | cv::String cwd = utils::fs::getcwd(); |
284 | cv::String build_dir(OPENCV_BUILD_DIR); |
285 | bool has_tested_build_directory = false; |
286 | if (isSubDirectory(base_path: build_dir, path: cwd) || isSubDirectory(base_path: utils::fs::canonical(path: build_dir), path: utils::fs::canonical(path: cwd))) |
287 | { |
288 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): the current directory is build sub-directory: " << cwd); |
289 | const char* build_subdirs[] = { OPENCV_DATA_BUILD_DIR_SEARCH_PATHS }; |
290 | for (size_t k = 0; k < sizeof(build_subdirs)/sizeof(build_subdirs[0]); k++) |
291 | { |
292 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): <build>/" << build_subdirs[k]); |
293 | cv::String datapath = utils::fs::join(base: build_dir, path: build_subdirs[k]); |
294 | if (utils::fs::isDirectory(path: datapath)) |
295 | { |
296 | for(size_t i = search_subdir.size(); i > 0; i--) |
297 | { |
298 | const cv::String& subdir = search_subdir[i - 1]; |
299 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
300 | TRY_FILE_WITH_PREFIX(prefix); |
301 | } |
302 | } |
303 | } |
304 | has_tested_build_directory = true; |
305 | } |
306 | |
307 | cv::String source_dir; |
308 | cv::String try_source_dir = cwd; |
309 | for (int levels = 0; levels < 3; ++levels) |
310 | { |
311 | if (utils::fs::exists(path: utils::fs::join(base: try_source_dir, path: "modules/core/include/opencv2/core/version.hpp" ))) |
312 | { |
313 | source_dir = try_source_dir; |
314 | break; |
315 | } |
316 | try_source_dir = utils::fs::join(base: try_source_dir, path: "/.." ); |
317 | } |
318 | if (!source_dir.empty()) |
319 | { |
320 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): the current directory is source sub-directory: " << source_dir); |
321 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): <source>" << source_dir); |
322 | cv::String datapath = source_dir; |
323 | if (utils::fs::isDirectory(path: datapath)) |
324 | { |
325 | for(size_t i = search_subdir.size(); i > 0; i--) |
326 | { |
327 | const cv::String& subdir = search_subdir[i - 1]; |
328 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
329 | TRY_FILE_WITH_PREFIX(prefix); |
330 | } |
331 | } |
332 | } |
333 | |
334 | cv::String module_path; |
335 | if (getBinLocation(dst&: module_path)) |
336 | { |
337 | CV_LOG_DEBUG(NULL, "Detected module path: '" << module_path << '\''); |
338 | } |
339 | else |
340 | { |
341 | CV_LOG_INFO(NULL, "Can't detect module binaries location" ); |
342 | } |
343 | |
344 | if (!has_tested_build_directory && |
345 | (isSubDirectory(base_path: build_dir, path: module_path) || isSubDirectory(base_path: utils::fs::canonical(path: build_dir), path: utils::fs::canonical(path: module_path))) |
346 | ) |
347 | { |
348 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): the binary module directory is build sub-directory: " << module_path); |
349 | const char* build_subdirs[] = { OPENCV_DATA_BUILD_DIR_SEARCH_PATHS }; |
350 | for (size_t k = 0; k < sizeof(build_subdirs)/sizeof(build_subdirs[0]); k++) |
351 | { |
352 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): <build>/" << build_subdirs[k]); |
353 | cv::String datapath = utils::fs::join(base: build_dir, path: build_subdirs[k]); |
354 | if (utils::fs::isDirectory(path: datapath)) |
355 | { |
356 | for(size_t i = search_subdir.size(); i > 0; i--) |
357 | { |
358 | const cv::String& subdir = search_subdir[i - 1]; |
359 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
360 | TRY_FILE_WITH_PREFIX(prefix); |
361 | } |
362 | } |
363 | } |
364 | } |
365 | |
366 | #if defined OPENCV_INSTALL_DATA_DIR_RELATIVE |
367 | if (!module_path.empty()) // require module path |
368 | { |
369 | size_t pos = module_path.rfind(c: '/'); |
370 | if (pos == cv::String::npos) |
371 | pos = module_path.rfind(c: '\\'); |
372 | cv::String module_dir = (pos == cv::String::npos) ? module_path : module_path.substr(pos: 0, n: pos); |
373 | const char* install_subdirs[] = { OPENCV_INSTALL_DATA_DIR_RELATIVE }; |
374 | for (size_t k = 0; k < sizeof(install_subdirs)/sizeof(install_subdirs[0]); k++) |
375 | { |
376 | cv::String datapath = utils::fs::join(base: module_dir, path: install_subdirs[k]); |
377 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying install path (from binary path): " << datapath); |
378 | if (utils::fs::isDirectory(path: datapath)) |
379 | { |
380 | for(size_t i = search_subdir.size(); i > 0; i--) |
381 | { |
382 | const cv::String& subdir = search_subdir[i - 1]; |
383 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
384 | TRY_FILE_WITH_PREFIX(prefix); |
385 | } |
386 | } |
387 | else |
388 | { |
389 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): ... skip, not a valid directory: " << datapath); |
390 | } |
391 | } |
392 | } |
393 | #endif |
394 | |
395 | #if defined OPENCV_INSTALL_PREFIX && defined OPENCV_DATA_INSTALL_PATH |
396 | cv::String install_dir(OPENCV_INSTALL_PREFIX); |
397 | // use core/world module path and verify that library is running from installation directory |
398 | // It is necessary to avoid touching of unrelated common /usr/local path |
399 | if (module_path.empty()) // can't determine |
400 | module_path = install_dir; |
401 | if (isSubDirectory(base_path: install_dir, path: module_path) || isSubDirectory(base_path: utils::fs::canonical(path: install_dir), path: utils::fs::canonical(path: module_path))) |
402 | { |
403 | cv::String datapath = utils::fs::join(base: install_dir, OPENCV_DATA_INSTALL_PATH); |
404 | if (utils::fs::isDirectory(path: datapath)) |
405 | { |
406 | CV_LOG_DEBUG(NULL, "utils::findDataFile(): trying install path: " << datapath); |
407 | for(size_t i = search_subdir.size(); i > 0; i--) |
408 | { |
409 | const cv::String& subdir = search_subdir[i - 1]; |
410 | cv::String prefix = utils::fs::join(base: datapath, path: subdir); |
411 | TRY_FILE_WITH_PREFIX(prefix); |
412 | } |
413 | } |
414 | } |
415 | #endif |
416 | |
417 | return cv::String(); // not found |
418 | #else // OPENCV_HAVE_FILESYSTEM_SUPPORT |
419 | CV_UNUSED(relative_path); |
420 | CV_UNUSED(configuration_parameter); |
421 | CV_UNUSED(search_paths); |
422 | CV_UNUSED(subdir_paths); |
423 | CV_Error(Error::StsNotImplemented, "File system support is disabled in this OpenCV build!" ); |
424 | #endif // OPENCV_HAVE_FILESYSTEM_SUPPORT |
425 | } |
426 | |
427 | cv::String findDataFile(const cv::String& relative_path, bool required, const char* configuration_parameter) |
428 | { |
429 | #if OPENCV_HAVE_FILESYSTEM_SUPPORT |
430 | CV_LOG_DEBUG(NULL, cv::format("cv::utils::findDataFile('%s', %s, %s)" , |
431 | relative_path.c_str(), required ? "true" : "false" , |
432 | configuration_parameter ? configuration_parameter : "NULL" )); |
433 | cv::String result = cv::utils::findDataFile(relative_path, |
434 | configuration_parameter, |
435 | NULL, |
436 | NULL); |
437 | if (result.empty() && required) |
438 | CV_Error(cv::Error::StsError, cv::format("OpenCV: Can't find required data file: %s" , relative_path.c_str())); |
439 | return result; |
440 | #else // OPENCV_HAVE_FILESYSTEM_SUPPORT |
441 | CV_UNUSED(relative_path); |
442 | CV_UNUSED(required); |
443 | CV_UNUSED(configuration_parameter); |
444 | CV_Error(Error::StsNotImplemented, "File system support is disabled in this OpenCV build!" ); |
445 | #endif // OPENCV_HAVE_FILESYSTEM_SUPPORT |
446 | } |
447 | |
448 | }} // namespace |
449 | |