Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c5bb371
Projected export geocoords support
pierotofy Jul 14, 2025
c416371
update pybind to 3.0.0
NathanMOlson Jul 31, 2025
dc33ae2
export compile commands
NathanMOlson Jul 31, 2025
32581b2
back to pybind11 v2.13.6
NathanMOlson Jul 31, 2025
b442028
c++17 for 3-argument hypot used by Ceres solver
NathanMOlson Jul 31, 2025
a128cca
update for compatibility with Ceres 2.2.0
NathanMOlson Jul 31, 2025
648fd76
pybind compatibility
NathanMOlson Jul 31, 2025
953e639
don't link pybind11
NathanMOlson Jul 31, 2025
e3b59ee
remove more links to pybind11
NathanMOlson Jul 31, 2025
4266d22
find pybind include files
NathanMOlson Aug 1, 2025
02534f5
fix errors caused by upgrades to numpy and matplotlib
NathanMOlson Aug 1, 2025
c49ef42
make bundle adjuster work with Ceres v1 or Ceres v2
NathanMOlson Aug 13, 2025
f1bfff6
remove test that was broken by change to binary tracks file (564ff2ca)
NathanMOlson Aug 13, 2025
3d54bc3
Create a new track file version, which stores the instance ID and seg…
NathanMOlson Aug 13, 2025
ce5f175
Revert "Create a new track file version, which stores the instance ID…
NathanMOlson Aug 13, 2025
37ff7a8
Make HasIOFileConsistency test pass, by not ensuring that segmentatio…
NathanMOlson Aug 13, 2025
e19573a
fix copy-paste error
NathanMOlson Aug 13, 2025
d0d5889
use mapillary version of setup.py
NathanMOlson Aug 13, 2025
31eb1ee
remove unused miniglog
NathanMOlson Aug 13, 2025
dc8f617
Revert "remove unused miniglog"
NathanMOlson Aug 14, 2025
eeb9678
Revert "use mapillary version of setup.py"
NathanMOlson Aug 14, 2025
a0599e8
Fix Ceres version check
NathanMOlson Aug 15, 2025
f5c35e6
remove debugging option
NathanMOlson Aug 15, 2025
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
51 changes: 47 additions & 4 deletions opensfm/actions/export_geocoords.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from opensfm import io
from opensfm.dataset import DataSet, UndistortedDataSet
from opensfm.geo import TopocentricConverter
from opensfm.reconstruction import bundle_shot_poses
from typing import List, Sequence

logger: logging.Logger = logging.getLogger(__name__)
Expand All @@ -20,7 +21,8 @@ def run_dataset(
reconstruction: bool,
dense : bool,
output: str,
offset = (0, 0)
offset = (0, 0),
mode = "affine"
) -> None:
"""Export reconstructions in geographic coordinates

Expand All @@ -32,7 +34,7 @@ def run_dataset(
dense : export dense point cloud (depthmaps/merged.ply)
output : path of the output file relative to the dataset
offset : offset to substract from the translation (optional)

mode : Method of georeferencing (optional)
"""

if not (transformation or image_positions or reconstruction or dense):
Expand All @@ -57,8 +59,15 @@ def run_dataset(

if reconstruction:
reconstructions = data.load_reconstruction()
for r in reconstructions:
_transform_reconstruction(r, t)
if mode == "affine":
for r in reconstructions:
_transform_reconstruction(r, t)
elif mode == "projected":
for r in reconstructions:
_transform_reconstruction_projected(r, t, offset[0], offset[1], reference, projection, data)
else:
raise Exception(f"Invalid mode: {mode}")

output = output or "reconstruction.geocoords.json"
data.save_reconstruction(reconstructions, output)

Expand Down Expand Up @@ -133,6 +142,40 @@ def _transform_reconstruction(
for point in reconstruction.points.values():
point.coordinates = list(np.dot(A, point.coordinates) + b)

def _transform_points_projected(pts, offset_x, offset_y, reference, projection):
lat, lon, alt = reference.to_lla(pts[:,0], pts[:,1], pts[:,2])
easting, northing = projection(lon, lat)
return easting - offset_x, northing - offset_y, alt

def _transform_reconstruction_projected(
reconstruction: types.Reconstruction, transformation: np.ndarray, offset_x, offset_y, reference, projection, data
) -> None:
"""Apply a transformation to a reconstruction in-place by projection."""

# Points
pts = np.array([p.coordinates for p in reconstruction.points.values()])
easting, northing, alt = _transform_points_projected(pts, offset_x, offset_y, reference, projection)

for i, point in enumerate(reconstruction.points.values()):
point.coordinates = [easting[i], northing[i], alt[i]]

# Cameras
A, b = transformation[:3, :3], transformation[:3, 3]
A1 = np.linalg.inv(A)

pts = np.array([shot.pose.get_origin() for shot in reconstruction.shots.values()])
easting, northing, alt = _transform_points_projected(pts, offset_x, offset_y, reference, projection)

for i, shot in enumerate(reconstruction.shots.values()):
R = shot.pose.get_rotation_matrix()
shot.pose.set_rotation_matrix(np.dot(R, A1))
shot.pose.set_origin([easting[i], northing[i], alt[i]])

logger.info("Bundle shot poses")
camera_priors = data.load_camera_models()
rig_camera_priors = data.load_rig_cameras()
bundle_shot_poses(reconstruction, set(reconstruction.shots.keys()), camera_priors, rig_camera_priors, data.config)


def _transform_dense_point_cloud(
udata: UndistortedDataSet, transformation: np.ndarray, output_path: str
Expand Down
11 changes: 9 additions & 2 deletions opensfm/commands/export_geocoords.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def run_impl(self, dataset: DataSet, args: argparse.Namespace) -> None:
args.reconstruction,
args.dense,
args.output,
(args.offset_x, args.offset_y)
(args.offset_x, args.offset_y),
args.mode
)

def add_arguments_impl(self, parser: argparse.ArgumentParser) -> None:
Expand Down Expand Up @@ -56,4 +57,10 @@ def add_arguments_impl(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--offset-y", type=float, help="Value to add to the final translation Y axis", default=0.0
)

parser.add_argument(
"--mode",
help="Georeferencing method",
type=str,
choices=["affine", "projected"],
default="affine",
)
2 changes: 2 additions & 0 deletions opensfm/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ class MatchingUnpickler(pickle.Unpickler):
modules_map = {
"numpy.core.multiarray._reconstruct": np.core.multiarray,
"numpy.core.multiarray.scalar": np.core.multiarray,
"numpy._core.multiarray._reconstruct": np.core.multiarray,
"numpy._core.multiarray.scalar": np.core.multiarray,
"numpy.ndarray": np,
"numpy.dtype": np,
}
Expand Down
2 changes: 1 addition & 1 deletion opensfm/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ set(CMAKE_CXX_VISIBILITY_INLINES ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Enable all warnings
Expand Down
3 changes: 1 addition & 2 deletions opensfm/src/bundle/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ pybind11_add_module(pybundle python/pybind.cc)
target_link_libraries(pybundle PRIVATE
bundle
geometry
foundation
pybind11)
foundation)
set_target_properties(pybundle PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${opensfm_SOURCE_DIR}/.."
)
5 changes: 5 additions & 0 deletions opensfm/src/bundle/src/bundle_adjuster.cc
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,13 @@ void BundleAdjuster::Run() {
problem.SetParameterBlockConstant(data.data());
}else if (i.second.GetValue().GetProjectionType() == geometry::ProjectionType::BROWN){
// Keep aspect ratio constant (BROWN only)
#if CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 2
ceres::SubsetManifold *subset_parameterization = new ceres::SubsetManifold(data.size(), { 6 });
problem.SetManifold(data.data(), subset_parameterization);
#else
ceres::SubsetParameterization *subset_parameterization = new ceres::SubsetParameterization(data.size(), { 6 });
problem.SetParameterization(data.data(), subset_parameterization);
#endif
}

// Add a barrier for constraining transition of dual to stay in [0, 1]
Expand Down
2 changes: 1 addition & 1 deletion opensfm/src/dense/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ endif()

pybind11_add_module(pydense python/pybind.cc)
target_include_directories(pydense PRIVATE ${GLOG_INCLUDE_DIR})
target_link_libraries(pydense PRIVATE dense foundation pybind11)
target_link_libraries(pydense PRIVATE dense foundation)
set_target_properties(pydense PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${opensfm_SOURCE_DIR}/.."
)
1 change: 0 additions & 1 deletion opensfm/src/features/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ target_link_libraries(pyfeatures
PRIVATE
features
foundation
pybind11
akaze
)
set_target_properties(pyfeatures PROPERTIES
Expand Down
2 changes: 1 addition & 1 deletion opensfm/src/foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ set(FOUNDATION_FILES
add_library(foundation ${FOUNDATION_FILES})
target_link_libraries(foundation
PUBLIC
pybind11
${OpenCV_LIBS}
${OpenMP_libomp_LIBRARY}
Eigen3::Eigen
)
target_include_directories(foundation
PUBLIC
${PYBIND11_INCLUDE_DIR}
${PYTHON_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}
${OpenMP_CXX_INCLUDE_DIR}
Expand Down
1 change: 0 additions & 1 deletion opensfm/src/geo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ target_link_libraries(pygeo
PRIVATE
geo
foundation
pybind11
)
set_target_properties(pygeo PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${opensfm_SOURCE_DIR}/.."
Expand Down
1 change: 0 additions & 1 deletion opensfm/src/geometry/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ target_link_libraries(pygeometry
PRIVATE
geometry
foundation
pybind11
)
set_target_properties(pygeometry PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${opensfm_SOURCE_DIR}/.."
Expand Down
2 changes: 0 additions & 2 deletions opensfm/src/map/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ set(MAP_FILES
add_library(map ${MAP_FILES})
target_link_libraries(map
PUBLIC
pybind11
Eigen3::Eigen
PRIVATE
geo
Expand All @@ -41,7 +40,6 @@ target_link_libraries(pymap
geometry
foundation
bundle
pybind11
)

if (OPENSFM_BUILD_TESTS)
Expand Down
2 changes: 1 addition & 1 deletion opensfm/src/map/pybind_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename ValueType = decltype(std::declval<Iterator>()),
typename... Extra>
iterator make_ptr_iterator(Iterator first, Sentinel last, Extra &&... extra) {
typedef detail::iterator_state<Iterator, Sentinel, false, Policy> state;
typedef detail::iterator_state<detail::iterator_access<Iterator>, Policy, Iterator, Sentinel, ValueType> state;

if (!detail::get_type_info(typeid(state), false)) {
class_<state>(handle(), "iterator", pybind11::module_local())
Expand Down
21 changes: 10 additions & 11 deletions opensfm/src/map/test/tracks_manager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,18 @@ TEST_F(TracksManagerTest, HasIOFileConsistency) {
::testing::WhenSorted(::testing::ElementsAre("1", "2", "3")));
EXPECT_THAT(manager_new.GetTrackIds(),
::testing::WhenSorted(::testing::ElementsAre("1")));
EXPECT_EQ(track, manager_new.GetTrackObservations("1"));
}

TEST_F(TracksManagerTest, HasIOStringConsistency) {
const auto serialized = manager.AsString();
const map::TracksManager manager_new =
map::TracksManager::InstanciateFromString(serialized);
std::unordered_map<map::ShotId, map::Observation> track_from_file = manager_new.GetTrackObservations("1");

EXPECT_THAT(manager_new.GetShotIds(),
::testing::WhenSorted(::testing::ElementsAre("1", "2", "3")));
EXPECT_THAT(manager_new.GetTrackIds(),
::testing::WhenSorted(::testing::ElementsAre("1")));
EXPECT_EQ(track, manager_new.GetTrackObservations("1"));
// Test that point, scale, color, and feature ID are correctly recovered from the file.
// Segmentation and instance IDs are not saved in the ODM file format. For this reason,
// track_from_file will not exactly match track (it is missing these fields).
for(auto const& it : track) {
EXPECT_EQ(it.second.point, track_from_file[it.first].point);
EXPECT_EQ(it.second.scale, track_from_file[it.first].scale);
EXPECT_EQ(it.second.color, track_from_file[it.first].color);
EXPECT_EQ(it.second.feature_id, track_from_file[it.first].feature_id);
}
}

} // namespace
1 change: 0 additions & 1 deletion opensfm/src/robust/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ target_link_libraries(pyrobust
PRIVATE
robust
foundation
pybind11
)
set_target_properties(pyrobust PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${opensfm_SOURCE_DIR}/.."
Expand Down
1 change: 0 additions & 1 deletion opensfm/src/sfm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ target_include_directories(pysfm PRIVATE ${GLOG_INCLUDE_DIR})
target_link_libraries(pysfm
PRIVATE
foundation
pybind11
sfm
)
set_target_properties(pysfm PROPERTIES
Expand Down
2 changes: 1 addition & 1 deletion opensfm/src/third_party/pybind11
Submodule pybind11 updated 280 files
2 changes: 2 additions & 0 deletions opensfm/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ def save_matchgraph(
orientation="horizontal",
label="Number of matches between images",
pad=0.0,
ax=plt.gca(),
)

with io_handler.open(os.path.join(output_path, "matchgraph.png"), "wb") as fwb:
Expand Down Expand Up @@ -1156,6 +1157,7 @@ def save_residual_grids(
label="Residual Norm",
pad=0.08,
aspect=40,
ax=plt.gca(),
)

plt.xticks(
Expand Down
Loading