Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions SuperBuild/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ set(CONDA_CMAKE_ARGS
"-DPython_ROOT_DIR=$ENV{CONDA_PREFIX}")
message(STATUS "Using conda prefix: $ENV{CONDA_PREFIX}")

# CUDA only via ODM_ENABLE_CUDA (pixi gpu feature, or set by hand)
set(SB_ENABLE_CUDA OFF)
if("$ENV{ODM_ENABLE_CUDA}" STREQUAL "ON")
set(SB_ENABLE_CUDA ON)
endif()
message(STATUS "CUDA support: ${SB_ENABLE_CUDA}")

# On Linux the conda-forge toolchain bakes an RPATH to the environment's lib
# into every binary. macOS clang does not, so libraries linked with an
# @rpath install name (e.g. conda boost) fail to load at runtime. Bake the
Expand Down
1 change: 1 addition & 0 deletions SuperBuild/cmake/External-OpenMVS.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ ExternalProject_Add(${_proj_name}
-DOpenMVS_ENABLE_TESTS=OFF
-DOpenMVS_MAX_CUDA_COMPATIBILITY=ON
-DINSTALL_USE_SUBDIR=OFF
-DOpenMVS_USE_CUDA=${SB_ENABLE_CUDA}
${GPU_CMAKE_ARGS}
${CONDA_CMAKE_ARGS}
${OPENMVS_WIN_CONDA_ARGS}
Expand Down
9 changes: 6 additions & 3 deletions SuperBuild/cmake/External-OpenSfM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ if(DEFINED ENV{CONDA_PREFIX})
# propagated via the glog::glog cmake target, but OpenSfM links Ceres with
# the old-style ${CERES_LIBRARIES} variable and misses the transitive define.
set(OPENSFM_EXTRA_CXX_FLAGS "$ENV{CXXFLAGS} -DGLOG_USE_GLOG_EXPORT -DGLOG_USE_GFLAGS")
if(WIN32)
set(OPENSFM_EXTRA_CXX_FLAGS "${OPENSFM_EXTRA_CXX_FLAGS} /EHsc /GR")
endif()
endif()

ExternalProject_Add(${_proj_name}
Expand All @@ -40,10 +43,9 @@ ExternalProject_Add(${_proj_name}
#--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/
GIT_TAG c5328439465e6ace011f39077d1077d7b1cdd65d
GIT_TAG 85f83a705cf6801965e6bd6fd2e080a7d7b9fd0b
#--Update/Patch step----------
UPDATE_COMMAND git submodule update --init --recursive
PATCH_COMMAND ${CMAKE_COMMAND} -P ${SB_ROOT_DIR}/cmake/apply-patch.cmake ${SB_ROOT_DIR}/cmake/opensfm-aarch64-abs.patch
#--Configure step-------------
SOURCE_DIR ${SB_INSTALL_DIR}/bin/${_proj_name}
CONFIGURE_COMMAND ${CMAKE_COMMAND} <SOURCE_DIR>/${_proj_name}/src
Expand All @@ -63,7 +65,8 @@ ExternalProject_Add(${_proj_name}
#--Build step-----------------
BINARY_DIR ${_SB_BINARY_DIR}
#--Install step---------------
INSTALL_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} --install <BINARY_DIR> --config ${CMAKE_BUILD_TYPE}
--prefix ${SB_INSTALL_DIR}/bin/${_proj_name}/opensfm
#--Output logging-------------
LOG_DOWNLOAD OFF
LOG_CONFIGURE OFF
Expand Down
6 changes: 2 additions & 4 deletions SuperBuild/cmake/External-PyPopsift.cmake
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/pypopsift")

# Pypopsift
find_package(CUDA 7.0)

if(CUDA_FOUND)
if(SB_ENABLE_CUDA)
ExternalProject_Add(pypopsift
DEPENDS
PREFIX ${_SB_BINARY_DIR}
Expand Down Expand Up @@ -32,5 +30,5 @@ if(CUDA_FOUND)
LOG_BUILD OFF
)
else()
message(WARNING "Could not find CUDA >= 7.0")
message(STATUS "Skipping pypopsift: ODM_ENABLE_CUDA not set (use the pixi gpu env, or set ODM_ENABLE_CUDA=ON)")
endif()
13 changes: 0 additions & 13 deletions SuperBuild/cmake/opensfm-aarch64-abs.patch

This file was deleted.

31 changes: 17 additions & 14 deletions opendm/osfm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from opendm.multispectral import get_photos_by_band
from opendm.gpu import has_popsift_and_can_handle_texsize, has_gpu
from opensfm import multiview, exif
from opensfm.actions.export_geocoords import _transform

class OSFMContext:
def __init__(self, opensfm_project_path):
Expand All @@ -49,10 +48,16 @@ def create_tracks(self, rerun=False):
else:
log.ODM_WARNING('Found a valid OpenSfM tracks file in: %s' % tracks_file)

def reconstruct(self, rolling_shutter_correct=False, merge_partial=False, rerun=False):
def reconstruct(self, algorithm='incremental', rolling_shutter_correct=False, merge_partial=False, rerun=False):
# Upstream OpenSfM supports 'incremental' and 'triangulation', so map anything
# else (e.g. the deprecated 'planar') to incremental
if algorithm not in ('incremental', 'triangulation'):
log.ODM_WARNING("Unsupported SfM algorithm '%s', using incremental instead" % algorithm)
algorithm = 'incremental'

reconstruction_file = os.path.join(self.opensfm_project_path, 'reconstruction.json')
if not io.file_exists(reconstruction_file) or rerun:
self.run('reconstruct')
self.run('reconstruct --algorithm %s' % algorithm)
if merge_partial:
self.check_merge_partial_reconstructions()
else:
Expand All @@ -70,13 +75,13 @@ def reconstruct(self, rolling_shutter_correct=False, merge_partial=False, rerun=
rs_file = self.path('rs_done.txt')

if not io.file_exists(rs_file) or rerun:
self.run('rs_correct')
self.run('correct_rolling_shutter')

log.ODM_INFO("Re-running the reconstruction pipeline")

self.match_features(True)
self.create_tracks(True)
self.reconstruct(rolling_shutter_correct=False, merge_partial=merge_partial, rerun=True)
self.reconstruct(algorithm=algorithm, rolling_shutter_correct=False, merge_partial=merge_partial, rerun=True)

self.touch(rs_file)
else:
Expand Down Expand Up @@ -256,7 +261,6 @@ def setup(self, args, images_path, reconstruction, append_config = [], rerun=Fal
"matching_gps_distance: 0",
"matching_graph_rounds: %s" % matcher_graph_rounds,
"optimize_camera_parameters: %s" % ('no' if args.use_fixed_camera_params else 'yes'),
"reconstruction_algorithm: %s" % (args.sfm_algorithm),
"undistorted_image_format: tif",
"bundle_outlier_filtering_type: AUTO",
"sift_peak_threshold: 0.066",
Expand Down Expand Up @@ -372,7 +376,7 @@ def extract_metadata(self, rerun=False):
if not io.dir_exists(metadata_dir) or rerun:
self.run('extract_metadata')

def photos_to_metadata(self, photos, rolling_shutter, rolling_shutter_readout, rerun=False):
def photos_to_metadata(self, photos, rolling_shutter, rolling_shutter_readout, gps_accuracy, rerun=False):
metadata_dir = self.path("exif")

if io.dir_exists(metadata_dir) and not rerun:
Expand All @@ -388,7 +392,7 @@ def photos_to_metadata(self, photos, rolling_shutter, rolling_shutter_readout, r
data = DataSet(self.opensfm_project_path)

for p in photos:
d = p.to_opensfm_exif(rolling_shutter, rolling_shutter_readout)
d = p.to_opensfm_exif(rolling_shutter, rolling_shutter_readout, gps_accuracy)
with open(os.path.join(metadata_dir, "%s.exif" % p.filename), 'w') as f:
f.write(json.dumps(d, indent=4))

Expand Down Expand Up @@ -623,7 +627,11 @@ def ground_control_points(self, proj4):

result = []
for gcp in gcps_stats:
geocoords = _transform(gcp['coordinates'], reference, projection)
# Local coords (XYZ /ENU) to WGS84 (lat/lon/alt)
coords = gcp['coordinates']
lat, lon, altitude = reference.to_lla(coords[0], coords[1], coords[2])
easting, northing = projection(lon, lat)
geocoords = [easting, northing, altitude]
result.append({
'id': gcp['id'],
'observations': gcp['observations'],
Expand Down Expand Up @@ -665,11 +673,6 @@ def get_submodel_argv(args, submodels_path = None, submodel_name = None):
# Startup script (/path/to/run.py)
startup_script = argv[0]

# On Windows, make sure we always invoke the "run.bat" file
if sys.platform == 'win32':
startup_script_dir = os.path.dirname(startup_script)
startup_script = os.path.join(startup_script_dir, "run")

result = [startup_script]

args_dict = vars(args).copy()
Expand Down
18 changes: 15 additions & 3 deletions opendm/photo.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ def camera_id(self):
]
).lower()

def to_opensfm_exif(self, rolling_shutter = False, rolling_shutter_readout = 0):
def to_opensfm_exif(self, rolling_shutter = False, rolling_shutter_readout = 0, gps_accuracy = None):
capture_time = 0.0
if self.utc_time is not None:
capture_time = self.utc_time / 1000.0
Expand All @@ -838,10 +838,22 @@ def to_opensfm_exif(self, rolling_shutter = False, rolling_shutter_readout = 0):

dop = self.get_gps_dop()
if dop is None:
dop = 10.0 # Default

dop = gps_accuracy if gps_accuracy is not None else 10.0 # Default

# dop is a single GPS accuracy value. Older OpenSfM code still reads
# it, so keep writing it.
gps['dop'] = dop

# Newer OpenSfM wants horizontal and vertical GPS accuracy separately.
# ODM stores them per axis when it has them (e.g. RTK), so use those.
# If a value is missing, use dop for horizontal and assume vertical is
# 3x less accurate (typical for GPS).
horizontal = self.gps_xy_stddev if self.gps_xy_stddev is not None else dop
vertical = self.gps_z_stddev if self.gps_z_stddev is not None else horizontal * 3.0
gps['latitude_std'] = horizontal
gps['longitude_std'] = horizontal
gps['altitude_std'] = vertical

d = {
"make": self.camera_make,
"model": self.camera_model,
Expand Down
10 changes: 6 additions & 4 deletions opendm/remote.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
import datetime
import os
import sys
import threading
import zipfile
import glob
Expand All @@ -26,12 +27,13 @@ class LocalRemoteExecutor:
to use the processing power of the current machine as well as offloading tasks to a
network node.
"""
def __init__(self, nodeUrl, rolling_shutter = False, rerun = False):
def __init__(self, nodeUrl, rolling_shutter = False, sfm_algorithm = 'incremental', rerun = False):
self.node = Node.from_url(nodeUrl)
self.params = {
'tasks': [],
'threads': [],
'rolling_shutter': rolling_shutter,
'sfm_algorithm': sfm_algorithm,
'rerun': rerun
}
self.node_online = True
Expand Down Expand Up @@ -446,7 +448,7 @@ def process_local(self):
log.ODM_INFO("==================================")
octx.feature_matching(self.params['rerun'])
octx.create_tracks(self.params['rerun'])
octx.reconstruct(self.params['rolling_shutter'], True, self.params['rerun'])
octx.reconstruct(self.params['sfm_algorithm'], self.params['rolling_shutter'], True, self.params['rerun'])

def process_remote(self, done):
octx = OSFMContext(self.path("opensfm"))
Expand Down Expand Up @@ -476,8 +478,8 @@ def process_local(self):
submodels_path = os.path.abspath(self.path(".."))
argv = get_submodel_argv(config.config(), submodels_path, submodel_name)

# Always invoke run.py through venv python
cmd = ["python3"] + argv
# Always invoke run.py through the same (pixi env) interpreter
cmd = [sys.executable] + argv

# Re-run the ODM toolchain on the submodel
system.run(" ".join(map(double_quote, cmd)), env_vars=os.environ.copy())
Expand Down
Loading
Loading