Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/app/mcp_operator_tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,9 @@ namespace lfs::app {
if (result.is_running_modal()) {
return json{{"success", true}, {"status", "running_modal"}};
}
if (const auto error = props.get<std::string>("error"); error && !error->empty()) {
return json{{"error", *error}};
}
return json{{"error", operator_cancel_message(*descriptor)}};
}

Expand Down
29 changes: 23 additions & 6 deletions src/io/formats/colmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2046,7 +2046,7 @@ namespace lfs::io {
};
}

void update_image_poses_from_cameras(
size_t update_image_poses_from_cameras(
std::vector<ImageData>& images,
const std::vector<ColmapCameraWriteData>& cameras) {
std::unordered_map<std::string, const ColmapCameraWriteData*> camera_by_name;
Expand All @@ -2058,25 +2058,33 @@ namespace lfs::io {
camera_by_name.emplace(item.camera->image_name(), &item);
}

const size_t source_image_count = images.size();
size_t matched = 0;
for (auto& image : images) {
std::erase_if(images, [&](ImageData& image) {
// Keep live-camera images by updating their poses; drop source images whose cameras were deleted.
const auto it = camera_by_name.find(image.name);
if (it == camera_by_name.end()) {
throw std::runtime_error(std::format("No current scene camera found for COLMAP image '{}'", image.name));
return true;
}
auto [qvec, tvec] = transformed_camera_pose(*it->second->camera, it->second->data_world_transform);
image.qvec = std::move(qvec);
image.tvec = std::move(tvec);
++matched;
}
return false;
});

if (matched == 0) {
throw std::runtime_error("No COLMAP image poses were matched for export");
}
if (images.size() != source_image_count) {
LOG_WARN("COLMAP export skipped {} source images with no current scene camera",
source_image_count - images.size());
}
if (matched != camera_by_name.size()) {
LOG_WARN("COLMAP export matched {} source images but received {} scene cameras",
matched, camera_by_name.size());
}
return source_image_count - images.size();
}

uint8_t float_color_to_u8(const float value) {
Expand Down Expand Up @@ -2179,6 +2187,13 @@ namespace lfs::io {
}
}

void clear_point3D_tracks(std::vector<Point3DData>& points) {
for (auto& point : points) {
point.track.clear();
point.track_count = 0;
}
}

double mean_observations_per_image(const std::vector<ImageData>& images) {
if (images.empty()) {
return 0.0;
Expand Down Expand Up @@ -2823,11 +2838,13 @@ namespace lfs::io {

try {
ColmapSparseModelData model = read_colmap_sparse_model_for_write(source_base);
update_image_poses_from_cameras(model.images, cameras);
const size_t dropped_images = update_image_poses_from_cameras(model.images, cameras);
const bool clear_observation_links = point_cloud_requires_untracked_export(model.points3D, point_cloud);
model.points3D = build_points3D_for_write(model.points3D, point_cloud, point_cloud_transform);
if (clear_observation_links) {
if (clear_observation_links || dropped_images > 0) {
// Omitted images can leave point tracks referencing image ids that are no longer exported.
clear_image_point3D_references(model.images);
clear_point3D_tracks(model.points3D);
}

std::error_code ec;
Expand Down
7 changes: 5 additions & 2 deletions src/python/lfs/py_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "visualizer/training/training_state.hpp"
#include <algorithm>
#include <nanobind/ndarray.h>
#include <stdexcept>

namespace lfs::python {

Expand Down Expand Up @@ -600,7 +601,9 @@ namespace lfs::python {

void PyScene::remove_node(const std::string& name, bool keep_children) {
if (auto* const scene_manager = get_scene_manager()) {
scene_manager->removePLY(name, keep_children);
if (auto result = scene_manager->removePLYWithResult(name, keep_children); !result) {
throw std::runtime_error(result.error());
}
return;
}
scene_->removeNode(name, keep_children);
Expand Down Expand Up @@ -1282,7 +1285,7 @@ namespace lfs::python {
)doc")
.def("remove_node", &PyScene::remove_node,
nb::arg("name"), nb::arg("keep_children") = false,
"Remove a node by name, optionally keeping its children")
"Remove a node by name, optionally keeping its children. Raises RuntimeError if the GUI scene manager rejects removal.")
.def("rename_node", &PyScene::rename_node,
nb::arg("old_name"), nb::arg("new_name"),
"Rename a node, returns true on success")
Expand Down
7 changes: 6 additions & 1 deletion src/python/stubs/lichtfeld/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,12 @@ def set_camera_training_enabled(name: str, enabled: bool) -> None:
"""Enable or disable a camera for training by name"""

def remove_node(name: str, keep_children: bool = False) -> None:
"""Remove a scene node by name"""
"""Remove a scene node by name.

Raises:
RuntimeError: If the GUI scene manager rejects removal, for example
when training protects the target node or subtree.
"""

def select_node(name: str) -> None:
"""Select a scene node by name"""
Expand Down
7 changes: 6 additions & 1 deletion src/python/stubs/lichtfeld/scene.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,12 @@ class Scene:
"""

def remove_node(self, name: str, keep_children: bool = False) -> None:
"""Remove a node by name, optionally keeping its children"""
"""Remove a node by name, optionally keeping its children.

Raises:
RuntimeError: If the GUI scene manager rejects removal, for example
when training protects the target node or subtree.
"""

def rename_node(self, old_name: str, new_name: str) -> bool:
"""Rename a node, returns true on success"""
Expand Down
8 changes: 4 additions & 4 deletions src/training/training_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ namespace lfs::training {
const auto cameras_group_id = scene.addGroup("Cameras", dataset_id);

const auto train_cameras_id = scene.addCameraGroup(
std::format("Training ({})", train_count),
"Training",
cameras_group_id,
train_count);

Expand All @@ -536,7 +536,7 @@ namespace lfs::training {

if (enable_eval && val_count > 0) {
const auto val_cameras_id = scene.addCameraGroup(
std::format("Validation ({})", val_count),
"Validation",
cameras_group_id,
val_count);

Expand Down Expand Up @@ -885,7 +885,7 @@ namespace lfs::training {

const auto cameras_group_id = scene.addGroup("Cameras", dataset_id);
const auto train_cameras_id = scene.addCameraGroup(
std::format("Training ({})", train_count), cameras_group_id, train_count);
"Training", cameras_group_id, train_count);

for (size_t i = 0; i < cameras.size(); ++i) {
if (!enable_eval || (i % test_every) != 0) {
Expand All @@ -895,7 +895,7 @@ namespace lfs::training {

if (enable_eval && val_count > 0) {
const auto val_cameras_id = scene.addCameraGroup(
std::format("Validation ({})", val_count), cameras_group_id, val_count);
"Validation", cameras_group_id, val_count);
for (size_t i = 0; i < cameras.size(); ++i) {
if ((i % test_every) == 0) {
scene.addCamera(cameras[i]->image_name(), val_cameras_id, cameras[i]);
Expand Down
8 changes: 8 additions & 0 deletions src/visualizer/gui/async_task_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,14 @@ namespace lfs::vis::gui {
lfs::vis::app_store().import_overlay_state.set(std::move(state));
}

void AsyncTaskManager::setImportNumImages(const size_t num_images) {
{
const std::lock_guard lock(import_state_.mutex);
import_state_.num_images = num_images;
}
publishImportOverlayState();
}

void AsyncTaskManager::publishVideoExportOverlayState() {
lfs::vis::AppStore::VideoExportOverlayState state;
state.active = video_export_state_.active.load();
Expand Down
1 change: 1 addition & 0 deletions src/visualizer/gui/async_task_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ namespace lfs::vis {
std::lock_guard lock(import_state_.mutex);
return import_state_.num_images;
}
void setImportNumImages(size_t num_images);
[[nodiscard]] size_t getImportNumPoints() const {
std::lock_guard lock(import_state_.mutex);
return import_state_.num_points;
Expand Down
Loading
Loading