Skip to content

Commit

Permalink
Windows Support (mapillary#795)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: mapillary#795

Hello ✋

This PR gets OpenSfM to compile and run on Windows!

The main points that have been addressed:
 - [x] Rewrote the memory functions in `context.py` to work on all three platforms (Windows, Mac, Linux). This adds a new small dependency (`vmem`).
 - [x] Added a `bin/opensfm.bat` script for invoking the program.
 - [x] Some minor compilation issues related to Visual Studio are addressed. The definition of `M_PI` is a bit.. hackish, but the alternative is to include:

```
#define _USE_MATH_DEFINES
#include <cmath>
```

In a lot of places where M_PI is referenced. I'm not sure that's better (but I can change it if you want). I'm also unsure of what side effects the `_USE_MATH_DEFINES` macro might do. 💣

 - [x] Updated the docs with build instructions for Windows.
 - [x] Installs `opencv-python` as a pip dependency on Windows only; this is because vcpkg builds of OpenCV don't provide a version with OpenCV's Python bindings. This only affects Windows environments (it won't install on Mac or Linux).

I hope this can be useful to others.

Pull Request resolved: mapillary#735

Reviewed By: paulinus

Differential Revision: D29959025

Pulled By: YanNoun

fbshipit-source-id: 7016c642091e95323beb04bda7cb48a9f4e5c156
  • Loading branch information
pierotofy authored and facebook-github-bot committed Sep 20, 2021
1 parent 54d3605 commit 13b0b65
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 33 deletions.
8 changes: 8 additions & 0 deletions bin/opensfm.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@echo off

setlocal
set OSFMBASE=%~dp0

python "%OSFMBASE%\opensfm_main.py" %*

endlocal
19 changes: 19 additions & 0 deletions doc/source/building.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ Also, in order for Cmake to recognize the libraries installed by Brew, make sure
When running OpenSfM on top of OpenCV version 3.0 the `OpenCV Contrib`_ modules are required for extracting SIFT or SURF features.


Installing dependencies on Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Install vcpkg from the OpenSfM root directory::

cd OpenSfM
git clone https://github.com/microsoft/vcpkg
cd vcpkg
bootstrap-vcpkg.bat

Then install OpenCV, Ceres, SuiteSparse and LAPACK (this will take a while)::

vcpkg install opencv4 ceres ceres[suitesparse] lapack suitesparse --triplet x64-windows

Finally install the PIP requirements::

pip install -r requirements.txt


Building the library
--------------------

Expand Down
76 changes: 57 additions & 19 deletions opensfm/context.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import logging
import os
import resource
try:
import resource
except ModuleNotFoundError:
pass # Windows
import sys
import ctypes
from typing import Optional

import cv2
Expand Down Expand Up @@ -56,23 +60,61 @@ def parallel_map(func, args, num_proc, max_batch_size=1):


# Memory usage
if sys.platform == "darwin":
rusage_unit = 1

if sys.platform == 'win32':
class MEMORYSTATUSEX(ctypes.Structure):
_fields_ = [
("dwLength", ctypes.c_ulong),
("dwMemoryLoad", ctypes.c_ulong),
("ullTotalPhys", ctypes.c_ulonglong),
("ullAvailPhys", ctypes.c_ulonglong),
("ullTotalPageFile", ctypes.c_ulonglong),
("ullAvailPageFile", ctypes.c_ulonglong),
("ullTotalVirtual", ctypes.c_ulonglong),
("ullAvailVirtual", ctypes.c_ulonglong),
("sullAvailExtendedVirtual", ctypes.c_ulonglong),
]

def __init__(self):
# have to initialize this to the size of MEMORYSTATUSEX
self.dwLength = ctypes.sizeof(self)
super(MEMORYSTATUSEX, self).__init__()

def memory_available() -> Optional[int]:
"""Available memory in MB.
Only works on Windows
"""
stat = MEMORYSTATUSEX()
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
return stat.ullAvailPhys / 1024 / 1024

def current_memory_usage():
stat = MEMORYSTATUSEX()
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
return (stat.ullTotalPhys - stat.ullAvailPhys) / 1024
else:
rusage_unit = 1024
if sys.platform == "darwin":
rusage_unit = 1
else:
rusage_unit = 1024


def memory_available() -> Optional[int]:
"""Available memory in MB.
def memory_available() -> Optional[int]:
"""Available memory in MB.
Only works on linux and returns None otherwise.
"""
with os.popen("free -t -m") as fp:
lines = fp.readlines()
if not lines:
return None
available_mem = int(lines[1].split()[6])
return available_mem

Only works on linux and returns None otherwise.
"""
with os.popen("free -t -m") as fp:
lines = fp.readlines()
if not lines:
return None
available_mem = int(lines[1].split()[6])
return available_mem

def current_memory_usage():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * rusage_unit


def processes_that_fit_in_memory(desired: int, per_process: int) -> int:
Expand All @@ -82,8 +124,4 @@ def processes_that_fit_in_memory(desired: int, per_process: int) -> int:
fittable = max(1, int(available_mem / per_process))
return min(desired, fittable)
else:
return desired


def current_memory_usage():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * rusage_unit
return desired
39 changes: 31 additions & 8 deletions opensfm/large/metadataset.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import os.path
import shutil
import sys
import glob

import numpy as np
from opensfm import config
Expand Down Expand Up @@ -71,15 +73,34 @@ def _create_symlink(self, base_path, file_path):

if not os.path.exists(src):
return

if os.path.islink(dst):
os.unlink(dst)

# Symlinks on Windows require admin privileges,
# so we use hard links instead
if sys.platform == 'win32':
if os.path.isdir(dst):
shutil.rmtree(dst)
elif os.path.isfile(dst):
os.remove(dst)
else:
if os.path.islink(dst):
os.unlink(dst)

subfolders = len(file_path.split(os.path.sep)) - 1

os.symlink(
os.path.join(*[".."] * subfolders, os.path.relpath(src, base_path)), dst
)
if sys.platform == 'win32':
if os.path.isdir(src):
# Create directory in destination, then make hard links
# to files
os.mkdir(dst)

for f in glob.glob(os.path.join(src, "*")):
filename = os.path.basename(f)
os.link(f, os.path.join(dst, filename))
else:
# Just make hard link
os.link(src, dst)
else:
os.symlink(os.path.join(*[".."] * subfolders, os.path.relpath(src, base_path)), dst)

def image_groups_exists(self):
return os.path.isfile(self._image_groups_path())
Expand Down Expand Up @@ -158,9 +179,11 @@ def create_submodels(self, clusters):
for image in cluster:
src = data.image_files[image]
dst = os.path.join(submodel_images_path, image)
src_relpath = os.path.relpath(src, submodel_images_path)
if not os.path.isfile(dst):
os.symlink(src_relpath, dst)
if sys.platform == 'win32':
os.link(src, dst)
else:
os.symlink(os.path.relpath(src, submodel_images_path), dst)
dst_relpath = os.path.relpath(dst, submodel_path)
txtfile.write(dst_relpath + "\n")

Expand Down
17 changes: 17 additions & 0 deletions opensfm/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
set(CMAKE_MODULE_PATH ${opensfm_SOURCE_DIR}/cmake)

if (WIN32)
# Place compilation results in opensfm/ folder, not in Debug/ or Release/
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${opensfm_SOURCE_DIR}/..")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${opensfm_SOURCE_DIR}/..")
endif()

####### Compilation Options #######
# Visibility stuff
cmake_policy(SET CMP0063 NEW)
Expand All @@ -22,9 +28,20 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Enable all warnings
if (NOT WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
endif()

# For compiling VLFeat
add_definitions(-DVL_DISABLE_AVX)

if (WIN32)
# Missing math constant
add_definitions(-DM_PI=3.14159265358979323846)
endif()

####### Find Dependencies #######
find_package(OpenMP)
if (OPENMP_FOUND)
Expand Down
1 change: 1 addition & 0 deletions opensfm/src/dense/depthmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class DepthmapEstimator {
std::mt19937 rng_;
std::uniform_int_distribution<int> uni_;
std::normal_distribution<float> unit_normal_;
std::vector<float> patch_variance_buffer_;
};

class DepthmapCleaner {
Expand Down
10 changes: 7 additions & 3 deletions opensfm/src/dense/src/depthmap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ DepthmapEstimator::DepthmapEstimator()
min_patch_variance_(5 * 5),
rng_{std::random_device{}()},
uni_(0, 0),
unit_normal_(0, 1) {}
unit_normal_(0, 1),
patch_variance_buffer_(patch_size_ * patch_size_) {}

void DepthmapEstimator::AddView(const double *pK, const double *pR,
const double *pt, const unsigned char *pimage,
Expand Down Expand Up @@ -169,7 +170,10 @@ void DepthmapEstimator::SetPatchMatchIterations(int n) {
patchmatch_iterations_ = n;
}

void DepthmapEstimator::SetPatchSize(int size) { patch_size_ = size; }
void DepthmapEstimator::SetPatchSize(int size) {
patch_size_ = size;
patch_variance_buffer_.resize(patch_size_ * patch_size_);
}

void DepthmapEstimator::SetMinPatchSD(float sd) {
min_patch_variance_ = sd * sd;
Expand Down Expand Up @@ -263,7 +267,7 @@ void DepthmapEstimator::ComputeIgnoreMask(DepthmapEstimatorResult *result) {
}

float DepthmapEstimator::PatchVariance(int i, int j) {
float patch[patch_size_ * patch_size_];
float *patch = patch_variance_buffer_.data();
int hpz = (patch_size_ - 1) / 2;
int counter = 0;
for (int u = -hpz; u <= hpz; ++u) {
Expand Down
4 changes: 4 additions & 0 deletions opensfm/src/third_party/vlfeat/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ if( ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" )
add_definitions( -DVL_DISABLE_SSE2 )
endif()

if(WIN32)
add_definitions(-D__SSE2__)
endif()

add_library(vl ${VLFEAT_SRCS})
target_include_directories(vl
PRIVATE
Expand Down
6 changes: 4 additions & 2 deletions opensfm/src/third_party/vlfeat/vl/host.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,9 @@ defined(__DOXYGEN__)
#if defined(VL_COMPILER_MSC) & ! defined(__DOXYGEN__)
# define VL_UNUSED
# define VL_INLINE static __inline
#if _MSC_VER < 1900
# define snprintf _snprintf
#endif
# define isnan _isnan
# ifdef VL_BUILD_DLL
# ifdef __cplusplus
Expand All @@ -322,9 +324,9 @@ defined(__DOXYGEN__)
# endif
# else
# ifdef __cplusplus
# define VL_EXPORT extern "C" __declspec(dllimport)
# define VL_EXPORT extern "C"
# else
# define VL_EXPORT extern __declspec(dllimport)
# define VL_EXPORT extern
# endif
# endif
#endif
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Sphinx==3.4.3
six
xmltodict==0.10.2
wheel
opencv-python==4.5.1.48 ; sys_platform == "win32"
11 changes: 10 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import subprocess
import sys
import multiprocessing

import setuptools
from sphinx.setup_command import BuildDoc
Expand Down Expand Up @@ -34,13 +35,21 @@ def configure_c_extension():
"../opensfm/src",
"-DPYTHON_EXECUTABLE=" + sys.executable,
]
if sys.platform == 'win32':
cmake_command += [
'-DVCPKG_TARGET_TRIPLET=x64-windows',
'-DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake'
]
subprocess.check_call(cmake_command, cwd="cmake_build")


def build_c_extension():
"""Compile C extension."""
print("Compiling extension...")
subprocess.check_call(["make", "-j4"], cwd="cmake_build")
if sys.platform == 'win32':
subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd='cmake_build')
else:
subprocess.check_call(['make', '-j' + str(multiprocessing.cpu_count())], cwd='cmake_build')


configure_c_extension()
Expand Down

0 comments on commit 13b0b65

Please sign in to comment.