From f3e69e262fdfe874277fad8f09d0bbd82742f79a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:30:12 +0000 Subject: [PATCH 1/3] Add main_lockSfmData.cpp, CMakeLists entry, and LockSfMData Meshroom node Agent-Logs-Url: https://github.com/alicevision/AliceVision/sessions/06e6d05b-581d-420e-8af0-96170e9ae527 Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com> --- meshroom/aliceVision/LockSfMData.py | 76 ++++++++++++ src/software/pipeline/CMakeLists.txt | 12 ++ src/software/pipeline/main_lockSfmData.cpp | 127 +++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 meshroom/aliceVision/LockSfMData.py create mode 100644 src/software/pipeline/main_lockSfmData.cpp diff --git a/meshroom/aliceVision/LockSfMData.py b/meshroom/aliceVision/LockSfMData.py new file mode 100644 index 0000000000..a9a02c575d --- /dev/null +++ b/meshroom/aliceVision/LockSfMData.py @@ -0,0 +1,76 @@ +__version__ = "1.0" + +from meshroom.core import desc +from meshroom.core.utils import DESCRIBER_TYPES, VERBOSE_LEVEL + +import os.path + + +class LockSfMData(desc.AVCommandLineNode): + commandLine = "aliceVision_lockSfmData {allParams}" + size = desc.DynamicNodeSize("input") + + category = "Utils" + documentation = """ +Lock specific elements of an SfMData scene so that they are kept fixed during subsequent +bundle adjustment steps. + +The following elements can be locked independently: + * **Camera Intrinsics**: focal length, principal point, and distortion parameters. + * **Camera Poses**: position and orientation of all reconstructed cameras. + * **Landmarks**: 3D points of the sparse point cloud, optionally filtered by describer type. +""" + + inputs = [ + desc.File( + name="input", + label="SfMData", + description="Input SfMData file.", + value="", + ), + desc.BoolParam( + name="lockIntrinsics", + label="Lock Intrinsics", + description="Lock all camera intrinsics (focal length, principal point, distortion).", + value=False, + ), + desc.BoolParam( + name="lockPoses", + label="Lock Poses", + description="Lock all camera poses (position and orientation).", + value=False, + ), + desc.BoolParam( + name="lockLandmarks", + label="Lock Landmarks", + description="Lock 3D landmarks (sparse point cloud).", + value=False, + ), + desc.ChoiceParam( + name="lockLandmarkTypes", + label="Landmark Types To Lock", + description="Describer types of landmarks to lock.\n" + "If empty, all landmark types will be locked.", + values=DESCRIBER_TYPES, + value=[], + exclusive=False, + joinChar=",", + enabled=lambda node: node.lockLandmarks.value, + ), + desc.ChoiceParam( + name="verboseLevel", + label="Verbose Level", + description="Verbosity level (fatal, error, warning, info, debug, trace).", + values=VERBOSE_LEVEL, + value="info", + ), + ] + + outputs = [ + desc.File( + name="output", + label="SfMData", + description="Output SfMData file with locked elements.", + value=lambda attr: "{nodeCacheFolder}/" + (os.path.splitext(os.path.basename(attr.node.input.value))[0] or "sfmData") + ".abc", + ), + ] diff --git a/src/software/pipeline/CMakeLists.txt b/src/software/pipeline/CMakeLists.txt index d66ebe5afb..f653f98c1c 100644 --- a/src/software/pipeline/CMakeLists.txt +++ b/src/software/pipeline/CMakeLists.txt @@ -357,6 +357,18 @@ if (ALICEVISION_BUILD_SFM) Boost::timer ) + # Lock SfMData elements (intrinsics, poses, landmarks) + alicevision_add_software(aliceVision_lockSfmData + SOURCE main_lockSfmData.cpp + FOLDER ${FOLDER_SOFTWARE_PIPELINE} + LINKS aliceVision_system + aliceVision_cmdline + aliceVision_feature + aliceVision_sfmData + aliceVision_sfmDataIO + Boost::program_options + ) + endif() # if(ALICEVISION_BUILD_SFM) if (ALICEVISION_BUILD_PANORAMA) diff --git a/src/software/pipeline/main_lockSfmData.cpp b/src/software/pipeline/main_lockSfmData.cpp new file mode 100644 index 0000000000..d5377a81b8 --- /dev/null +++ b/src/software/pipeline/main_lockSfmData.cpp @@ -0,0 +1,127 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2024 AliceVision contributors. +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +// These constants define the current software version. +// They must be updated when the command line is changed. +#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 0 + +using namespace aliceVision; + +namespace po = boost::program_options; + +int aliceVision_main(int argc, char** argv) +{ + // command-line parameters + std::string sfmDataFilename; + std::string sfmDataOutputFilename; + bool lockIntrinsics = false; + bool lockPoses = false; + bool lockLandmarks = false; + std::string lockLandmarkTypes; + + // clang-format off + po::options_description requiredParams("Required parameters"); + requiredParams.add_options() + ("input,i", po::value(&sfmDataFilename)->required(), + "SfMData file.") + ("output,o", po::value(&sfmDataOutputFilename)->required(), + "Output SfMData file."); + + po::options_description optionalParams("Optional parameters"); + optionalParams.add_options() + ("lockIntrinsics", po::value(&lockIntrinsics)->default_value(lockIntrinsics), + "Lock all camera intrinsics.") + ("lockPoses", po::value(&lockPoses)->default_value(lockPoses), + "Lock all camera poses.") + ("lockLandmarks", po::value(&lockLandmarks)->default_value(lockLandmarks), + "Lock landmarks.") + ("lockLandmarkTypes", po::value(&lockLandmarkTypes)->default_value(lockLandmarkTypes), + "Comma-separated list of landmark describer types to lock (e.g. 'sift,dspsift'). " + "If empty, all landmark types will be locked."); + // clang-format on + + CmdLine cmdline("AliceVision lockSfmData"); + cmdline.add(requiredParams); + cmdline.add(optionalParams); + if (!cmdline.execute(argc, argv)) + { + return EXIT_FAILURE; + } + + // Load input SfMData scene + sfmData::SfMData sfmData; + if (!sfmDataIO::load(sfmData, sfmDataFilename, sfmDataIO::ESfMData::ALL)) + { + ALICEVISION_LOG_ERROR("The input SfMData file '" + sfmDataFilename + "' cannot be read."); + return EXIT_FAILURE; + } + + // Lock camera intrinsics + if (lockIntrinsics) + { + for (auto& [id, intrinsic] : sfmData.getIntrinsics().valueRange()) + { + intrinsic.lock(); + } + ALICEVISION_LOG_INFO("Locked " << sfmData.getIntrinsics().size() << " camera intrinsic(s)."); + } + + // Lock camera poses + if (lockPoses) + { + for (auto& [id, pose] : sfmData.getPoses().valueRange()) + { + pose.lock(); + } + ALICEVISION_LOG_INFO("Locked " << sfmData.getPoses().size() << " camera pose(s)."); + } + + // Lock landmarks (optionally filtered by describer type) + if (lockLandmarks) + { + std::set typesToLock; + if (!lockLandmarkTypes.empty()) + { + const std::vector typesList = + feature::EImageDescriberType_stringToEnums(lockLandmarkTypes); + typesToLock.insert(typesList.begin(), typesList.end()); + } + + std::size_t lockedCount = 0; + for (auto& [id, landmark] : sfmData.getLandmarks()) + { + if (typesToLock.empty() || typesToLock.count(landmark.getDescType())) + { + landmark.setLocked(true); + ++lockedCount; + } + } + ALICEVISION_LOG_INFO("Locked " << lockedCount << " landmark(s) out of " << sfmData.getLandmarks().size() << "."); + } + + // Save output SfMData + if (!sfmDataIO::save(sfmData, sfmDataOutputFilename, sfmDataIO::ESfMData::ALL)) + { + ALICEVISION_LOG_ERROR("The output SfMData file '" + sfmDataOutputFilename + "' cannot be written."); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} From 49b219a2ef8905f09e344fdcbc31d5de807b6a74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:25:37 +0000 Subject: [PATCH 2/3] Add granular intrinsic locking: lockFocalLength, lockPrincipalPoint, lockDistortion Agent-Logs-Url: https://github.com/alicevision/AliceVision/sessions/82ac0139-f786-4e28-b2e4-a816e14e6ca4 Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com> --- meshroom/aliceVision/LockSfMData.py | 30 +++++++++++-- src/software/pipeline/main_lockSfmData.cpp | 50 +++++++++++++++++++--- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/meshroom/aliceVision/LockSfMData.py b/meshroom/aliceVision/LockSfMData.py index a9a02c575d..279524cbdf 100644 --- a/meshroom/aliceVision/LockSfMData.py +++ b/meshroom/aliceVision/LockSfMData.py @@ -1,4 +1,4 @@ -__version__ = "1.0" +__version__ = "1.1" from meshroom.core import desc from meshroom.core.utils import DESCRIBER_TYPES, VERBOSE_LEVEL @@ -16,7 +16,10 @@ class LockSfMData(desc.AVCommandLineNode): bundle adjustment steps. The following elements can be locked independently: - * **Camera Intrinsics**: focal length, principal point, and distortion parameters. + * **Camera Intrinsics**: all intrinsic parameters, or specific sub-parts: + * **Focal Length**: scale parameters of the camera model. + * **Principal Point**: offset parameters of the camera model. + * **Distortion**: distortion parameters of the camera model. * **Camera Poses**: position and orientation of all reconstructed cameras. * **Landmarks**: 3D points of the sparse point cloud, optionally filtered by describer type. """ @@ -31,9 +34,30 @@ class LockSfMData(desc.AVCommandLineNode): desc.BoolParam( name="lockIntrinsics", label="Lock Intrinsics", - description="Lock all camera intrinsics (focal length, principal point, distortion).", + description="Lock camera intrinsics.", value=False, ), + desc.BoolParam( + name="lockFocalLength", + label="Lock Focal Length", + description="Lock the focal length of camera intrinsics.", + value=True, + enabled=lambda node: node.lockIntrinsics.value, + ), + desc.BoolParam( + name="lockPrincipalPoint", + label="Lock Principal Point", + description="Lock the principal point of camera intrinsics.", + value=True, + enabled=lambda node: node.lockIntrinsics.value, + ), + desc.BoolParam( + name="lockDistortion", + label="Lock Distortion", + description="Lock the distortion parameters of camera intrinsics.", + value=True, + enabled=lambda node: node.lockIntrinsics.value, + ), desc.BoolParam( name="lockPoses", label="Lock Poses", diff --git a/src/software/pipeline/main_lockSfmData.cpp b/src/software/pipeline/main_lockSfmData.cpp index d5377a81b8..d6a1dd5e87 100644 --- a/src/software/pipeline/main_lockSfmData.cpp +++ b/src/software/pipeline/main_lockSfmData.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include @@ -20,7 +22,7 @@ // These constants define the current software version. // They must be updated when the command line is changed. #define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 -#define ALICEVISION_SOFTWARE_VERSION_MINOR 0 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 1 using namespace aliceVision; @@ -32,6 +34,9 @@ int aliceVision_main(int argc, char** argv) std::string sfmDataFilename; std::string sfmDataOutputFilename; bool lockIntrinsics = false; + bool lockFocalLength = true; + bool lockPrincipalPoint = true; + bool lockDistortion = true; bool lockPoses = false; bool lockLandmarks = false; std::string lockLandmarkTypes; @@ -47,7 +52,13 @@ int aliceVision_main(int argc, char** argv) po::options_description optionalParams("Optional parameters"); optionalParams.add_options() ("lockIntrinsics", po::value(&lockIntrinsics)->default_value(lockIntrinsics), - "Lock all camera intrinsics.") + "Lock camera intrinsics.") + ("lockFocalLength", po::value(&lockFocalLength)->default_value(lockFocalLength), + "Lock the focal length of camera intrinsics. Only used when lockIntrinsics is enabled.") + ("lockPrincipalPoint", po::value(&lockPrincipalPoint)->default_value(lockPrincipalPoint), + "Lock the principal point of camera intrinsics. Only used when lockIntrinsics is enabled.") + ("lockDistortion", po::value(&lockDistortion)->default_value(lockDistortion), + "Lock the distortion parameters of camera intrinsics. Only used when lockIntrinsics is enabled.") ("lockPoses", po::value(&lockPoses)->default_value(lockPoses), "Lock all camera poses.") ("lockLandmarks", po::value(&lockLandmarks)->default_value(lockLandmarks), @@ -76,17 +87,42 @@ int aliceVision_main(int argc, char** argv) // Lock camera intrinsics if (lockIntrinsics) { - for (auto& [id, intrinsic] : sfmData.getIntrinsics().valueRange()) + std::size_t lockedCount = 0; + for (auto& [_, intrinsic] : sfmData.getIntrinsics().valueRange()) { - intrinsic.lock(); + if (lockFocalLength && lockPrincipalPoint && lockDistortion) + { + // Lock all intrinsic parts at once using the global lock + intrinsic.lock(); + } + else + { + // Lock only the requested parts + auto* isoPtr = dynamic_cast(&intrinsic); + if (isoPtr) + { + isoPtr->setScaleLocked(lockFocalLength); + isoPtr->setOffsetLocked(lockPrincipalPoint); + } + + if (lockDistortion) + { + auto* isodPtr = dynamic_cast(&intrinsic); + if (isodPtr && isodPtr->getDistortion()) + { + isodPtr->getDistortion()->setLocked(true); + } + } + } + ++lockedCount; } - ALICEVISION_LOG_INFO("Locked " << sfmData.getIntrinsics().size() << " camera intrinsic(s)."); + ALICEVISION_LOG_INFO("Processed " << lockedCount << " camera intrinsic(s)."); } // Lock camera poses if (lockPoses) { - for (auto& [id, pose] : sfmData.getPoses().valueRange()) + for (auto& [_, pose] : sfmData.getPoses().valueRange()) { pose.lock(); } @@ -105,7 +141,7 @@ int aliceVision_main(int argc, char** argv) } std::size_t lockedCount = 0; - for (auto& [id, landmark] : sfmData.getLandmarks()) + for (auto& [_, landmark] : sfmData.getLandmarks()) { if (typesToLock.empty() || typesToLock.count(landmark.getDescType())) { From 0bf9418a7e5b9ee7e68853a7d7b894520a7e251b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:49:33 +0000 Subject: [PATCH 3/3] Add selectedViews optional input and landmarkSelectionMode to lockSfmData Agent-Logs-Url: https://github.com/alicevision/AliceVision/sessions/dbc72809-d1cf-44af-aa32-ceb316f0ff3c Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com> --- meshroom/aliceVision/LockSfMData.py | 26 ++++- src/software/pipeline/main_lockSfmData.cpp | 129 +++++++++++++++++++-- 2 files changed, 144 insertions(+), 11 deletions(-) diff --git a/meshroom/aliceVision/LockSfMData.py b/meshroom/aliceVision/LockSfMData.py index 279524cbdf..a4b6a81395 100644 --- a/meshroom/aliceVision/LockSfMData.py +++ b/meshroom/aliceVision/LockSfMData.py @@ -1,4 +1,4 @@ -__version__ = "1.1" +__version__ = "1.2" from meshroom.core import desc from meshroom.core.utils import DESCRIBER_TYPES, VERBOSE_LEVEL @@ -15,13 +15,17 @@ class LockSfMData(desc.AVCommandLineNode): Lock specific elements of an SfMData scene so that they are kept fixed during subsequent bundle adjustment steps. +An optional **Selected Views** SfMData can be provided to restrict locking to elements +associated with that subset of views. + The following elements can be locked independently: * **Camera Intrinsics**: all intrinsic parameters, or specific sub-parts: * **Focal Length**: scale parameters of the camera model. * **Principal Point**: offset parameters of the camera model. * **Distortion**: distortion parameters of the camera model. * **Camera Poses**: position and orientation of all reconstructed cameras. - * **Landmarks**: 3D points of the sparse point cloud, optionally filtered by describer type. + * **Landmarks**: 3D points of the sparse point cloud, optionally filtered by describer type + and/or by landmark selection mode (fully or partially contained within the selected views). """ inputs = [ @@ -31,6 +35,13 @@ class LockSfMData(desc.AVCommandLineNode): description="Input SfMData file.", value="", ), + desc.File( + name="selectedViews", + label="Selected Views", + description="Optional SfMData file used to define a subset of views.\n" + "When provided, locking is restricted to elements associated with those views.", + value="", + ), desc.BoolParam( name="lockIntrinsics", label="Lock Intrinsics", @@ -81,6 +92,17 @@ class LockSfMData(desc.AVCommandLineNode): joinChar=",", enabled=lambda node: node.lockLandmarks.value, ), + desc.ChoiceParam( + name="landmarkSelectionMode", + label="Landmark Selection Mode", + description="Determines which landmarks to lock when Selected Views is provided:\n" + " - fully_contained: lock only landmarks whose all observations belong to the selected views.\n" + " - partially_contained: lock landmarks with at least one observation in the selected views.", + value="fully_contained", + values=["fully_contained", "partially_contained"], + exclusive=True, + enabled=lambda node: node.lockLandmarks.value and bool(node.selectedViews.value), + ), desc.ChoiceParam( name="verboseLevel", label="Verbose Level", diff --git a/src/software/pipeline/main_lockSfmData.cpp b/src/software/pipeline/main_lockSfmData.cpp index d6a1dd5e87..574e1419dd 100644 --- a/src/software/pipeline/main_lockSfmData.cpp +++ b/src/software/pipeline/main_lockSfmData.cpp @@ -22,7 +22,7 @@ // These constants define the current software version. // They must be updated when the command line is changed. #define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 -#define ALICEVISION_SOFTWARE_VERSION_MINOR 1 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 2 using namespace aliceVision; @@ -33,6 +33,7 @@ int aliceVision_main(int argc, char** argv) // command-line parameters std::string sfmDataFilename; std::string sfmDataOutputFilename; + std::string selectedViewsFilename; bool lockIntrinsics = false; bool lockFocalLength = true; bool lockPrincipalPoint = true; @@ -40,6 +41,7 @@ int aliceVision_main(int argc, char** argv) bool lockPoses = false; bool lockLandmarks = false; std::string lockLandmarkTypes; + std::string landmarkSelectionMode = "fully_contained"; // clang-format off po::options_description requiredParams("Required parameters"); @@ -51,6 +53,9 @@ int aliceVision_main(int argc, char** argv) po::options_description optionalParams("Optional parameters"); optionalParams.add_options() + ("selectedViews,s", po::value(&selectedViewsFilename)->default_value(selectedViewsFilename), + "Optional SfMData file used to define a subset of views. " + "When provided, locking is restricted to elements associated with those views.") ("lockIntrinsics", po::value(&lockIntrinsics)->default_value(lockIntrinsics), "Lock camera intrinsics.") ("lockFocalLength", po::value(&lockFocalLength)->default_value(lockFocalLength), @@ -65,7 +70,11 @@ int aliceVision_main(int argc, char** argv) "Lock landmarks.") ("lockLandmarkTypes", po::value(&lockLandmarkTypes)->default_value(lockLandmarkTypes), "Comma-separated list of landmark describer types to lock (e.g. 'sift,dspsift'). " - "If empty, all landmark types will be locked."); + "If empty, all landmark types will be locked.") + ("landmarkSelectionMode", po::value(&landmarkSelectionMode)->default_value(landmarkSelectionMode), + "Landmark selection mode when selectedViews is provided: " + "'fully_contained' to lock landmarks whose all observations belong to the selected views, " + "'partially_contained' to lock landmarks with at least one observation in the selected views."); // clang-format on CmdLine cmdline("AliceVision lockSfmData"); @@ -84,12 +93,57 @@ int aliceVision_main(int argc, char** argv) return EXIT_FAILURE; } + // Validate landmarkSelectionMode + if (landmarkSelectionMode != "fully_contained" && landmarkSelectionMode != "partially_contained") + { + ALICEVISION_LOG_ERROR("Invalid landmarkSelectionMode '" << landmarkSelectionMode + << "'. Expected 'fully_contained' or 'partially_contained'."); + return EXIT_FAILURE; + } + + // Build the set of selected view IDs (from the optional selectedViews SfMData) + std::set selectedViewIds; + const bool hasSelectedViews = !selectedViewsFilename.empty(); + if (hasSelectedViews) + { + sfmData::SfMData selectedViewsSfmData; + // Only VIEWS data is needed since we only extract view IDs from this SfMData + if (!sfmDataIO::load(selectedViewsSfmData, selectedViewsFilename, sfmDataIO::ESfMData::VIEWS)) + { + ALICEVISION_LOG_ERROR("The selectedViews SfMData file '" + selectedViewsFilename + "' cannot be read."); + return EXIT_FAILURE; + } + for (const auto& [viewId, _] : selectedViewsSfmData.getViews()) + { + selectedViewIds.insert(viewId); + } + ALICEVISION_LOG_INFO("Selected views subset contains " << selectedViewIds.size() << " view(s)."); + } + // Lock camera intrinsics if (lockIntrinsics) { + // If selectedViews is provided, collect the intrinsic IDs referenced by those views + std::set intrinsicIdsToLock; + if (hasSelectedViews) + { + for (const auto& [viewId, view] : sfmData.getViews()) + { + if (selectedViewIds.count(viewId) && view->getIntrinsicId() != UndefinedIndexT) + { + intrinsicIdsToLock.insert(view->getIntrinsicId()); + } + } + } + std::size_t lockedCount = 0; - for (auto& [_, intrinsic] : sfmData.getIntrinsics().valueRange()) + for (auto& [intrinsicId, intrinsic] : sfmData.getIntrinsics().valueRange()) { + if (hasSelectedViews && !intrinsicIdsToLock.count(intrinsicId)) + { + continue; + } + if (lockFocalLength && lockPrincipalPoint && lockDistortion) { // Lock all intrinsic parts at once using the global lock @@ -122,14 +176,33 @@ int aliceVision_main(int argc, char** argv) // Lock camera poses if (lockPoses) { - for (auto& [_, pose] : sfmData.getPoses().valueRange()) + // If selectedViews is provided, collect the pose IDs referenced by those views + std::set poseIdsToLock; + if (hasSelectedViews) + { + for (const auto& [viewId, view] : sfmData.getViews()) + { + if (selectedViewIds.count(viewId) && view->getPoseId() != UndefinedIndexT) + { + poseIdsToLock.insert(view->getPoseId()); + } + } + } + + std::size_t lockedCount = 0; + for (auto& [poseId, pose] : sfmData.getPoses().valueRange()) { + if (hasSelectedViews && !poseIdsToLock.count(poseId)) + { + continue; + } pose.lock(); + ++lockedCount; } - ALICEVISION_LOG_INFO("Locked " << sfmData.getPoses().size() << " camera pose(s)."); + ALICEVISION_LOG_INFO("Locked " << lockedCount << " camera pose(s)."); } - // Lock landmarks (optionally filtered by describer type) + // Lock landmarks (optionally filtered by describer type and/or selected views) if (lockLandmarks) { std::set typesToLock; @@ -143,11 +216,49 @@ int aliceVision_main(int argc, char** argv) std::size_t lockedCount = 0; for (auto& [_, landmark] : sfmData.getLandmarks()) { - if (typesToLock.empty() || typesToLock.count(landmark.getDescType())) + if (!typesToLock.empty() && !typesToLock.count(landmark.getDescType())) + { + continue; + } + + if (hasSelectedViews) { - landmark.setLocked(true); - ++lockedCount; + const Observations& obs = landmark.getObservations(); + bool include = false; + if (landmarkSelectionMode == "partially_contained") + { + // At least one observation in the selected views + for (const auto& [viewId, _] : obs) + { + if (selectedViewIds.count(viewId)) + { + include = true; + break; + } + } + } + else // fully_contained + { + // All observations must belong to selected views + include = !obs.empty(); + for (const auto& [viewId, _] : obs) + { + if (!selectedViewIds.count(viewId)) + { + include = false; + break; + } + } + } + + if (!include) + { + continue; + } } + + landmark.setLocked(true); + ++lockedCount; } ALICEVISION_LOG_INFO("Locked " << lockedCount << " landmark(s) out of " << sfmData.getLandmarks().size() << "."); }