From 1c06834c146aa90e286519e7360233c0ebe4c2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20De=20Lillo?= Date: Wed, 1 Oct 2025 18:31:32 +0200 Subject: [PATCH 01/11] [sphereDetection] Write JSON shape file in `sphereDetection()` --- .../sphereDetection/sphereDetection.cpp | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/aliceVision/sphereDetection/sphereDetection.cpp b/src/aliceVision/sphereDetection/sphereDetection.cpp index e1f94a7f6c..0dc131510b 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.cpp +++ b/src/aliceVision/sphereDetection/sphereDetection.cpp @@ -173,9 +173,9 @@ Prediction predict(Ort::Session& session, const fs::path imagePath, const float void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs::path outputPath, const float minScore) { - // Main tree - bpt::ptree fileTree; - + // Spheres tree + bpt::ptree spheresTree; + for (auto& viewID : sfmData.getViews()) { ALICEVISION_LOG_DEBUG("View Id: " << viewID); @@ -191,34 +191,56 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: // If there is no bounding box, then no sphere has been detected if (pred.bboxes.size() > 0) { - bpt::ptree spheresNode; - // We only take the best sphere in the picture const int i = 0; // Compute sphere coords from bbox coords const auto bbox = pred.bboxes.at(i); const float r = std::min(bbox.at(3) - bbox.at(1), bbox.at(2) - bbox.at(0)) / 2; - const float x = bbox.at(0) + r - pred.size.width / 2; - const float y = bbox.at(1) + r - pred.size.height / 2; + const float x = bbox.at(0) + r; + const float y = bbox.at(1) + r; // Create an unnamed node containing the sphere bpt::ptree sphereNode; - sphereNode.put("x", x); - sphereNode.put("y", y); - sphereNode.put("r", r); + sphereNode.put("center.x", x); + sphereNode.put("center.y", y); + sphereNode.put("radius", r); sphereNode.put("score", pred.scores.at(i)); sphereNode.put("type", "matte"); - // Add sphere to array - spheresNode.push_back(std::make_pair("", sphereNode)); - - fileTree.add_child(sphereName, spheresNode); + // Add sphere node to spheres tree + spheresTree.add_child(sphereName, sphereNode); } else { ALICEVISION_LOG_WARNING("No sphere detected for '" << imagePath << "'."); } } + + // Shapes tree + bpt::ptree shapesTree; + { + // Shape tree + bpt::ptree shapeTree; + shapeTree.put("name", "Sphere Detection"); + shapeTree.put("type", "Circle"); + + // Shape properties tree + bpt::ptree shapeProperties; + shapeProperties.put("color", "green"); + shapeTree.add_child("properties", shapeProperties); + + // Shape observations tree + shapeTree.add_child("observations", spheresTree); + + // Add shape tree to shapes tree + shapesTree.push_back(std::make_pair("", shapeTree)); + } + + // Main tree + bpt::ptree fileTree; + fileTree.add_child("shapes", shapesTree); + + // Write JSON bpt::write_json(outputPath.string(), fileTree); } From 513c61e35c7633b6285e4c1e6cc4b24347c4c0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20De=20Lillo?= Date: Wed, 1 Oct 2025 18:32:02 +0200 Subject: [PATCH 02/11] [sphereDetection] Write JSON shape file in `writeManualSphereJSON()` --- .../sphereDetection/sphereDetection.cpp | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/aliceVision/sphereDetection/sphereDetection.cpp b/src/aliceVision/sphereDetection/sphereDetection.cpp index 0dc131510b..db6c79c766 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.cpp +++ b/src/aliceVision/sphereDetection/sphereDetection.cpp @@ -246,8 +246,8 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, fs::path outputPath) { - // Main tree - bpt::ptree fileTree; + // Spheres tree + bpt::ptree spheresTree; for (auto& viewID : sfmData.getViews()) { @@ -255,19 +255,42 @@ void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::arraygetViewId()); - bpt::ptree spheresNode; // Create an unnamed node containing the sphere bpt::ptree sphereNode; - sphereNode.put("x", sphereParam[0]); - sphereNode.put("y", sphereParam[1]); - sphereNode.put("r", sphereParam[2]); + sphereNode.put("center.x", sphereParam[0]); + sphereNode.put("center.y", sphereParam[1]); + sphereNode.put("radius", sphereParam[2]); sphereNode.put("type", "matte"); - // Add sphere to array - spheresNode.push_back(std::make_pair("", sphereNode)); + // Add sphere node to spheres tree + spheresTree.add_child(sphereName, sphereNode); + } + + // Shapes tree + bpt::ptree shapesTree; + { + // Shape tree + bpt::ptree shapeTree; + shapeTree.put("name", "Manual Sphere Detection"); + shapeTree.put("type", "Circle"); + + // Shape properties tree + bpt::ptree shapeProperties; + shapeProperties.put("color", "green"); + shapeTree.add_child("properties", shapeProperties); + + // Shape observations tree + shapeTree.add_child("observations", spheresTree); - fileTree.add_child(sphereName, spheresNode); + // Add shape tree to shapes tree + shapesTree.push_back(std::make_pair("", shapeTree)); } + + // Main tree + bpt::ptree fileTree; + fileTree.add_child("shapes", shapesTree); + + // Write JSON bpt::write_json(outputPath.string(), fileTree); } From ea0f34b76e28a698d239beeb71150b7b4f3c26ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20De=20Lillo?= Date: Wed, 1 Oct 2025 19:08:16 +0200 Subject: [PATCH 03/11] [lightingEstimation] Read JSON shape file in `lightCalibration()` --- .../lightingCalibration.cpp | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/aliceVision/lightingEstimation/lightingCalibration.cpp b/src/aliceVision/lightingEstimation/lightingCalibration.cpp index 102c914aa0..656e0b07c9 100644 --- a/src/aliceVision/lightingEstimation/lightingCalibration.cpp +++ b/src/aliceVision/lightingEstimation/lightingCalibration.cpp @@ -70,9 +70,25 @@ void lightCalibration(const sfmData::SfMData& sfmData, // Main tree bpt::ptree fileTree; + // Read the json file and initialize the tree bpt::read_json(inputFile, fileTree); + // Spheres tree + bpt::ptree spheresTree; + + // Initialize spheres tree + const auto shapesTreeOpt = fileTree.get_child_optional("shapes"); + if (shapesTreeOpt && !shapesTreeOpt->empty()) + { + const auto& firstShapeTree = shapesTreeOpt->begin()->second; + spheresTree = firstShapeTree.get_child("observations"); + } + else + { + ALICEVISION_THROW_ERROR("Cannot find sphere detection data in '" << inputFile << "'."); + } + for (const auto& [currentId, currentView] : viewMap) { ALICEVISION_LOG_INFO("View Id: " << currentView.getViewId()); @@ -81,19 +97,20 @@ void lightCalibration(const sfmData::SfMData& sfmData, if (!boost::algorithm::icontains(imagePath.stem().string(), "ambient")) { std::string sphereName = std::to_string(currentView.getViewId()); - auto sphereExists = (fileTree.get_child_optional(sphereName)).is_initialized(); + auto sphereExists = (spheresTree.get_child_optional(sphereName)).is_initialized(); if (sphereExists) { ALICEVISION_LOG_INFO(" - " << imagePath.string()); imageList.push_back(imagePath.string()); std::array currentSphereParams; - for (auto& currentSphere : fileTree.get_child(sphereName)) { - currentSphereParams[0] = currentSphere.second.get_child("").get("x", 0.0); - currentSphereParams[1] = currentSphere.second.get_child("").get("y", 0.0); - currentSphereParams[2] = currentSphere.second.get_child("").get("r", 0.0); + const auto& currentSphere = spheresTree.get_child(sphereName); + currentSphereParams[0] = currentSphere.get("center.x", 0.0) - (currentView.getImage().getWidth() / 2); + currentSphereParams[1] = currentSphere.get("center.y", 0.0) - (currentView.getImage().getHeight() / 2); + currentSphereParams[2] = currentSphere.get("radius", 0.0); } + allSpheresParams.push_back(currentSphereParams); IndexT intrinsicId = currentView.getIntrinsicId(); From 628c1d2f41578a05d075c831d5c4f0c4e67a9765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20De=20Lillo?= Date: Wed, 1 Oct 2025 19:13:14 +0200 Subject: [PATCH 04/11] [software] sphereDetection: Rename `sphereCenterOffset` to `sphereCenter` --- src/software/pipeline/main_sphereDetection.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/software/pipeline/main_sphereDetection.cpp b/src/software/pipeline/main_sphereDetection.cpp index 313244693d..715b40fcaf 100644 --- a/src/software/pipeline/main_sphereDetection.cpp +++ b/src/software/pipeline/main_sphereDetection.cpp @@ -47,7 +47,7 @@ int aliceVision_main(int argc, char** argv) float inputMinScore; bool autoDetect; - Eigen::Vector2f sphereCenterOffset(0, 0); + Eigen::Vector2f sphereCenter(0, 0); double sphereRadius = 1.0; // clang-format off @@ -66,10 +66,10 @@ int aliceVision_main(int argc, char** argv) optionalParams.add_options() ("minScore,s", po::value(&inputMinScore)->default_value(0.0), "Minimum detection score.") - ("x,x", po::value(&sphereCenterOffset(0))->default_value(0.0), - "Sphere's center offset X (pixels).") - ("y,y", po::value(&sphereCenterOffset(1))->default_value(0.0), - "Sphere's center offset Y (pixels).") + ("x,x", po::value(&sphereCenter(0))->default_value(0.0), + "Sphere's center X (pixels).") + ("y,y", po::value(&sphereCenter(1))->default_value(0.0), + "Sphere's center Y (pixels).") ("sphereRadius,r", po::value(&sphereRadius)->default_value(1.0), "Sphere's radius (pixels)."); // clang-format on @@ -114,8 +114,8 @@ int aliceVision_main(int argc, char** argv) else { std::array sphereParam; - sphereParam[0] = sphereCenterOffset(0); - sphereParam[1] = sphereCenterOffset(1); + sphereParam[0] = sphereCenter(0); + sphereParam[1] = sphereCenter(1); sphereParam[2] = sphereRadius; sphereDetection::writeManualSphereJSON(sfmData, sphereParam, fsOutputPath); From 4f74d6af324747a9cca261e7b3c4271720d86eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20De=20Lillo?= Date: Wed, 1 Oct 2025 19:13:53 +0200 Subject: [PATCH 05/11] [meshroom] SphereDetection: Add shape file semantic --- meshroom/aliceVision/SphereDetection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meshroom/aliceVision/SphereDetection.py b/meshroom/aliceVision/SphereDetection.py index 8d0d394649..5b8be0deae 100644 --- a/meshroom/aliceVision/SphereDetection.py +++ b/meshroom/aliceVision/SphereDetection.py @@ -84,6 +84,7 @@ class SphereDetection(desc.CommandLineNode): name="output", label="Output Path", description="Sphere detection information will be written here.", + semantic="shapeFile", value="{nodeCacheFolder}/detection.json", ) ] From 977d931f355920bf2b458d41271468b7cd065021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 16:02:12 +0200 Subject: [PATCH 06/11] [sphereDetection] Add helper function to fill output detection file --- .../sphereDetection/sphereDetection.cpp | 48 ++++++++++--------- .../sphereDetection/sphereDetection.hpp | 6 +++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/aliceVision/sphereDetection/sphereDetection.cpp b/src/aliceVision/sphereDetection/sphereDetection.cpp index db6c79c766..d5bd517307 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.cpp +++ b/src/aliceVision/sphereDetection/sphereDetection.cpp @@ -32,19 +32,39 @@ #include // Boost JSON -#include #include // SFMData #include #include -// namespaces -namespace bpt = boost::property_tree; - namespace aliceVision { namespace sphereDetection { +void fillShapeTree(bpt::ptree& fileTree, const bpt::ptree& spheresTree) +{ + bpt::ptree shapesTree; + { + // Shape tree + bpt::ptree shapeTree; + shapeTree.put("name", "Manual Sphere Detection"); + shapeTree.put("type", "Circle"); + + // Shape properties tree + bpt::ptree shapeProperties; + shapeProperties.put("color", "green"); + shapeTree.add_child("properties", shapeProperties); + + // Shape observations tree + shapeTree.add_child("observations", spheresTree); + + // Add shape tree to shapes tree + shapesTree.push_back(std::make_pair("", shapeTree)); + } + + fileTree.add_child("shapes", shapesTree); +} + void modelExplore(Ort::Session& session) { // Define allocator @@ -175,7 +195,7 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: { // Spheres tree bpt::ptree spheresTree; - + for (auto& viewID : sfmData.getViews()) { ALICEVISION_LOG_DEBUG("View Id: " << viewID); @@ -216,29 +236,13 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: } } - // Shapes tree - bpt::ptree shapesTree; - { - // Shape tree - bpt::ptree shapeTree; - shapeTree.put("name", "Sphere Detection"); - shapeTree.put("type", "Circle"); - // Shape properties tree - bpt::ptree shapeProperties; - shapeProperties.put("color", "green"); - shapeTree.add_child("properties", shapeProperties); - // Shape observations tree - shapeTree.add_child("observations", spheresTree); - // Add shape tree to shapes tree - shapesTree.push_back(std::make_pair("", shapeTree)); - } // Main tree bpt::ptree fileTree; - fileTree.add_child("shapes", shapesTree); + fillShapeTree(fileTree, spheresTree); // Write JSON bpt::write_json(outputPath.string(), fileTree); diff --git a/src/aliceVision/sphereDetection/sphereDetection.hpp b/src/aliceVision/sphereDetection/sphereDetection.hpp index d244d97420..ac130e46dd 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.hpp +++ b/src/aliceVision/sphereDetection/sphereDetection.hpp @@ -12,6 +12,9 @@ // ONNXRuntime #include +// Boost Property Tree +#include + // SFMData #include #include @@ -23,6 +26,7 @@ namespace sphereDetection { // namespaces namespace fs = std::filesystem; +namespace bpt = boost::property_tree; struct Prediction { @@ -31,6 +35,8 @@ struct Prediction cv::Size size; }; +void fillShapeTree(bpt::ptree& fileTree, const bpt::ptree& spheresTree); + /** * @brief Print inputs and outputs of neural network, and checks the requirements * @param session The ONNXRuntime session From caf9c121b0f6a7bd472ddaa57ce62a7aeca5e4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 16:18:49 +0200 Subject: [PATCH 07/11] [utils] Add method to convert dict-like vectors of strings to maps The function takes a dictionary (Python-like or a flat JSON file content) represented as a flat vector of strings that contain key-value pairs into a map of pairs of strings. --- src/aliceVision/utils/convert.hpp | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/aliceVision/utils/convert.hpp b/src/aliceVision/utils/convert.hpp index 3c84fd4341..aaaba0d34e 100644 --- a/src/aliceVision/utils/convert.hpp +++ b/src/aliceVision/utils/convert.hpp @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -20,5 +21,53 @@ inline std::string toStringZeroPadded(std::size_t i, std::size_t zeroPadding) return ss.str(); } +/** + * @brief Converts a flat vector of strings representing key-value pairs + * into a std::map of string-to-string. + * + * @param dict A vector of strings where each pair of elements (i, i+1) + * represents a key and value, possibly with formatting characters. + * @return std::map A map containing cleaned key-value pairs. + * + * @code + * std::vector dict = {"{key1:", "value1,", "key2:", "value2}"}; + * auto result = dictStringToStringMap(dict); + * // result => { {"key1", "value1"}, {"key2", "value2"} } + * @endcode + */ +inline std::map dictStringToStringMap(std::vector dict) +{ + std::map stringMap; + + for (std::size_t i = 0; i < dict.size(); i += 2) + { + std::string keyString = std::string(dict[i]); + std::string valueString = std::string(dict[i + 1]); + + if (keyString[0] == '{') + { + keyString = keyString.substr(1, keyString.size() - 1); + } + if (keyString[keyString.size() - 1] == ':') + { + keyString = keyString.substr(0, keyString.size() - 1); + } + + if (valueString[valueString.size() - 1] == ',' || valueString[valueString.size() - 1] == '}') + { + valueString = valueString.substr(0, valueString.size() - 1); + } + + // Ensure both strings are not empty after being parsed. + // If one of them is, do not add the pair to the map. + if (keyString.size() > 0 && valueString.size() > 0) + { + stringMap.insert(std::pair(keyString, valueString)); + } + } + + return stringMap; +} + } // namespace utils } // namespace aliceVision From febb825faab04d2b462a0cc7b3806f1a79f6e521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 16:30:31 +0200 Subject: [PATCH 08/11] [meshroom] `SphereDetection`: Add new parameters for manual detection --- meshroom/aliceVision/SphereDetection.py | 62 ++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/meshroom/aliceVision/SphereDetection.py b/meshroom/aliceVision/SphereDetection.py index 5b8be0deae..618344e469 100644 --- a/meshroom/aliceVision/SphereDetection.py +++ b/meshroom/aliceVision/SphereDetection.py @@ -1,4 +1,4 @@ -__version__ = "1.0" +__version__ = "2.0" from meshroom.core import desc from meshroom.core.utils import VERBOSE_LEVEL @@ -31,45 +31,43 @@ class SphereDetection(desc.CommandLineNode): description="Automatic detection of calibration spheres.", value=False, ), + desc.Circle( + name="sphereShape", + label="Sphere Shape", + description="The shape of the calibration sphere for every image.", + enabled=lambda node: not node.autoDetect.value, + group=lambda node: None if node.sphereFile.value else "allParams", + keyable=True, + keyType="viewId", + ), + desc.File( + name="sphereFile", + label="Sphere Shape File", + description="An input JSON file containing the shapes for every image. If provided, " + "the shapes provided with \"Sphere Shape\" will be ignored.", + semantic="shapeFile", + value="", + enabled=lambda node: not node.autoDetect.value, + group=lambda node: None if not node.sphereFile.value else "allParams", + ), + desc.BoolParam( + name="fillMissingSpheres", + label="Fill Missing Spheres", + description="Checked if a sphere position is to be written as detected although it " + "was not provided. In that case, the position of the last known sphere " + "will be used.", + value=False, + enabled=lambda node: not node.autoDetect.value, + ), desc.FloatParam( name="minScore", label="Minimum Score", description="Minimum score for the detection.", value=0.0, range=(0.0, 50.0, 0.01), + enabled=lambda node: node.autoDetect.value, advanced=True, ), - desc.GroupAttribute( - name="sphereCenter", - label="Sphere Center", - description="Center of the circle (XY offset to the center of the image in pixels).", - groupDesc=[ - desc.FloatParam( - name="x", - label="x", - description="X offset in pixels.", - value=0.0, - range=(-1000.0, 10000.0, 1.0), - ), - desc.FloatParam( - name="y", - label="y", - description="Y offset in pixels.", - value=0.0, - range=(-1000.0, 10000.0, 1.0), - ), - ], - enabled=lambda node: not node.autoDetect.value, - group=None, # skip group from command line - ), - desc.FloatParam( - name="sphereRadius", - label="Radius", - description="Sphere radius in pixels.", - value=500.0, - range=(0.0, 10000.0, 0.1), - enabled=lambda node: not node.autoDetect.value, - ), desc.ChoiceParam( name="verboseLevel", label="Verbose Level", From e19c982190394595e8d4f5fd65c5c142da9895aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 16:32:14 +0200 Subject: [PATCH 09/11] [pipeline] `SphereDetection`: Add new command line parameters for manual detection --- src/software/pipeline/main_sphereDetection.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/software/pipeline/main_sphereDetection.cpp b/src/software/pipeline/main_sphereDetection.cpp index 715b40fcaf..5235290e27 100644 --- a/src/software/pipeline/main_sphereDetection.cpp +++ b/src/software/pipeline/main_sphereDetection.cpp @@ -29,7 +29,7 @@ #include -#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 +#define ALICEVISION_SOFTWARE_VERSION_MAJOR 2 #define ALICEVISION_SOFTWARE_VERSION_MINOR 0 namespace fs = std::filesystem; @@ -47,6 +47,9 @@ int aliceVision_main(int argc, char** argv) float inputMinScore; bool autoDetect; + std::vector x, y, radius; + std::string sphereFile; + bool fillMissingSpheres; Eigen::Vector2f sphereCenter(0, 0); double sphereRadius = 1.0; @@ -66,12 +69,16 @@ int aliceVision_main(int argc, char** argv) optionalParams.add_options() ("minScore,s", po::value(&inputMinScore)->default_value(0.0), "Minimum detection score.") - ("x,x", po::value(&sphereCenter(0))->default_value(0.0), + ("x,x", po::value>(&x)->multitoken(), "Sphere's center X (pixels).") - ("y,y", po::value(&sphereCenter(1))->default_value(0.0), + ("y,y", po::value>(&y)->multitoken(), "Sphere's center Y (pixels).") - ("sphereRadius,r", po::value(&sphereRadius)->default_value(1.0), - "Sphere's radius (pixels)."); + ("radius,r", po::value>(&radius)->multitoken(), + "Sphere's radius (pixels).") + ("sphereFile,f", po::value(&sphereFile)->default_value(""), + "File containing the positions for the spheres in all the images.") + ("fillMissingSpheres,m", po::value(&fillMissingSpheres)->default_value(true), + "True if a sphere position is to be written as detected although it was not provided. In that case, the position of the last known sphere will be used."); // clang-format on CmdLine cmdline("AliceVision sphereDetection"); From e7fcb0e3df63df6979c46a5b94ff8365faa2bb7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 16:35:44 +0200 Subject: [PATCH 10/11] [sphereDetection] Rewrite `writeManualSphereJSON` method There are now two variants of this method: - one that takes the coordinates and radius of the hand-detected spheres - one that takes a JSON file containing shape information for the spheres In both cases, `fillMissingSpheres` can be enabled to fill the location of some spheres for which the data is unavailable with the location of the last detected sphere. --- .../sphereDetection/sphereDetection.cpp | 126 +++++++++++++++--- .../sphereDetection/sphereDetection.hpp | 30 ++++- .../pipeline/main_sphereDetection.cpp | 19 +-- 3 files changed, 147 insertions(+), 28 deletions(-) diff --git a/src/aliceVision/sphereDetection/sphereDetection.cpp b/src/aliceVision/sphereDetection/sphereDetection.cpp index d5bd517307..cea676bf88 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.cpp +++ b/src/aliceVision/sphereDetection/sphereDetection.cpp @@ -9,6 +9,9 @@ // Standard libs #include #include +#include + +#include // AliceVision image library #include @@ -236,10 +239,6 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: } } - - - - // Main tree bpt::ptree fileTree; fillShapeTree(fileTree, spheresTree); @@ -248,26 +247,55 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: bpt::write_json(outputPath.string(), fileTree); } -void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, fs::path outputPath) +bool writeManualSphereJSON(const sfmData::SfMData& sfmData, + const std::vector& x, + const std::vector& y, + const std::vector& radius, + fs::path outputPath, + bool fillMissingSpheres) { + auto xValues = aliceVision::utils::dictStringToStringMap(x); + auto yValues = aliceVision::utils::dictStringToStringMap(y); + auto radiusValues = aliceVision::utils::dictStringToStringMap(radius); + // Spheres tree bpt::ptree spheresTree; for (auto& viewID : sfmData.getViews()) { - ALICEVISION_LOG_DEBUG("View Id: " << viewID); - + ALICEVISION_LOG_DEBUG("View ID: " << viewID); const std::string sphereName = std::to_string(viewID.second->getViewId()); + std::vector sphereParams; + auto pos = xValues.find(sphereName); + if (pos == xValues.end()) + { + ALICEVISION_LOG_INFO("Sphere shape for view ID " << sphereName << " not found."); + + if (fillMissingSpheres) + { + ALICEVISION_LOG_INFO("Using sphere position from view ID " << xValues.rbegin()->first << "."); + sphereParams = {std::stof(xValues.rbegin()->second), std::stof(yValues.rbegin()->second), std::stof(radiusValues.rbegin()->second)}; + } + } + else + { + ALICEVISION_LOG_DEBUG("Sphere shape for view ID " << sphereName << " found."); + sphereParams = {std::stof(xValues.at(sphereName)), std::stof(yValues.at(sphereName)), std::stof(radiusValues.at(sphereName))}; + } + // Create an unnamed node containing the sphere - bpt::ptree sphereNode; - sphereNode.put("center.x", sphereParam[0]); - sphereNode.put("center.y", sphereParam[1]); - sphereNode.put("radius", sphereParam[2]); - sphereNode.put("type", "matte"); - - // Add sphere node to spheres tree - spheresTree.add_child(sphereName, sphereNode); + if (!sphereParams.empty()) + { + bpt::ptree sphereNode; + sphereNode.put("center.x", sphereParams[0]); + sphereNode.put("center.y", sphereParams[1]); + sphereNode.put("radius", sphereParams[2]); + sphereNode.put("type", "matte"); + + // Add sphere node to spheres tree + spheresTree.add_child(sphereName, sphereNode); + } } // Shapes tree @@ -296,6 +324,74 @@ void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::arrayempty()) + { + const auto& firstShapeTree = shapesTreeOpt->begin()->second; + spheresTree = firstShapeTree.get_child("observations"); + } + else + { + ALICEVISION_THROW_ERROR("Cannot find sphere detection data in '" << sphereFile << "'."); + } + + std::string lastSphereViewID = spheresTree.rbegin()->first; + std::vector sphereParams = {spheresTree.rbegin()->second.get("center.x", 0.0f), + spheresTree.rbegin()->second.get("center.y", 0.0f), + spheresTree.rbegin()->second.get("radius", 0.0f)}; + + ALICEVISION_LOG_INFO("Got last known sphere position: " << lastSphereViewID); + + for (auto& viewID : sfmData.getViews()) + { + ALICEVISION_LOG_DEBUG("View ID: " << viewID); + const std::string sphereName = std::to_string(viewID.second->getViewId()); + + auto sphereExists = (spheresTree.get_child_optional(sphereName)).is_initialized(); + if (!sphereExists) + { + ALICEVISION_LOG_INFO("Sphere exists"); + bpt::ptree sphereNode; + sphereNode.put("center.x", sphereParams[0]); + sphereNode.put("center.y", sphereParams[1]); + sphereNode.put("radius", sphereParams[2]); + sphereNode.put("type", "matte"); + + // Add sphere node to spheres tree + spheresTree.add_child(sphereName, sphereNode); + } + } + + fileTree.clear(); + fillShapeTree(fileTree, spheresTree); + + // Write JSON + bpt::write_json(outputPath, fileTree); + + return true; } } // namespace sphereDetection diff --git a/src/aliceVision/sphereDetection/sphereDetection.hpp b/src/aliceVision/sphereDetection/sphereDetection.hpp index ac130e46dd..33e9b90b8c 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.hpp +++ b/src/aliceVision/sphereDetection/sphereDetection.hpp @@ -54,13 +54,33 @@ void modelExplore(Ort::Session& session); void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs::path outputPath, const float minScore); /** - * @brief Write JSON for a hand-detected sphere + * @brief Write a JSON file containing the shapes for the hand-detected spheres. * - * @param sfmData Input .sfm file - * @param sphereParam Parameters of the hand-detected sphere - * @return outputPath Path to the JSON file + * @param sfmData Input SfMData. + * @param x Flat vector of strings containing "view ID":"x-coordinate" pairs for the hand-detected spheres. + * @param y Flat vector of strings containing "view ID":"y-coordinate" pairs for the hand-detected spheres. + * @param radius Flat vector of strings containing "view ID":"radius" pairs for the hand-detected spheres. + * @param outputPath Path to the output JSON file. + * @param fillMissingSpheres If enabled, view IDs for which no sphere has been hand-detected will use the location of the last detected sphere. + * @return True if the JSON file was correctly written, False otherwise. */ -void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, fs::path outputPath); +bool writeManualSphereJSON(const sfmData::SfMData& sfmData, + const std::vector& x, + const std::vector& y, + const std::vector& radius, + fs::path outputPath, + bool fillMissingSpheres); + +/** + * @brief Write a JSON file containing the shapes for the spheres, based on a provided JSON file that contains sphere locations. + * + * @param sfmData Input SfMData. + * @param sphereFile A JSON file containing the locations of the detected spheres. + * @param outputPath Path to the output JSON file. + * @param fillMissingSpheres If enabled, view IDs for which no sphere has been hand-detected will use the location of the last detected sphere. + * @return True if the JSON file was correctly written, False otherwise. + */ +bool writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::string& sphereFile, const std::string& outputPath, bool fillMissingSpheres); } // namespace sphereDetection } // namespace aliceVision diff --git a/src/software/pipeline/main_sphereDetection.cpp b/src/software/pipeline/main_sphereDetection.cpp index 5235290e27..5559677ecb 100644 --- a/src/software/pipeline/main_sphereDetection.cpp +++ b/src/software/pipeline/main_sphereDetection.cpp @@ -50,8 +50,6 @@ int aliceVision_main(int argc, char** argv) std::vector x, y, radius; std::string sphereFile; bool fillMissingSpheres; - Eigen::Vector2f sphereCenter(0, 0); - double sphereRadius = 1.0; // clang-format off po::options_description requiredParams("Required parameters"); @@ -120,12 +118,17 @@ int aliceVision_main(int argc, char** argv) } else { - std::array sphereParam; - sphereParam[0] = sphereCenter(0); - sphereParam[1] = sphereCenter(1); - sphereParam[2] = sphereRadius; - - sphereDetection::writeManualSphereJSON(sfmData, sphereParam, fsOutputPath); + if (sphereFile.empty()) + { + sphereDetection::writeManualSphereJSON(sfmData, x, y, radius, fsOutputPath, fillMissingSpheres); + } + else + { + if (!sphereDetection::writeManualSphereJSON(sfmData, sphereFile, outputPath, fillMissingSpheres)) + { + return EXIT_FAILURE; + } + } } ALICEVISION_LOG_INFO("Task done in (s): " + std::to_string(timer.elapsed())); From 613a482ca642d8cbecf585871dfb4882618b3271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 21 Oct 2025 17:59:09 +0200 Subject: [PATCH 11/11] [meshroom] `PhotometricStereo`: Update node version for `SphereDetection` --- meshroom/multi-viewPhotometricStereo.mg | 4 ++-- meshroom/photometricStereo.mg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meshroom/multi-viewPhotometricStereo.mg b/meshroom/multi-viewPhotometricStereo.mg index 3daf1f9666..47c2974554 100644 --- a/meshroom/multi-viewPhotometricStereo.mg +++ b/meshroom/multi-viewPhotometricStereo.mg @@ -1,6 +1,6 @@ { "header": { - "releaseVersion": "2025.1.0", + "releaseVersion": "2026.1.0+develop", "fileVersion": "2.0", "nodesVersions": { "CameraInit": "12.0", @@ -17,7 +17,7 @@ "PrepareDenseScene": "3.1", "SfMFilter": "1.0", "SfMTransfer": "2.1", - "SphereDetection": "1.0", + "SphereDetection": "2.0", "StructureFromMotion": "3.3", "Texturing": "6.0" }, diff --git a/meshroom/photometricStereo.mg b/meshroom/photometricStereo.mg index 2040916223..a090af6c40 100644 --- a/meshroom/photometricStereo.mg +++ b/meshroom/photometricStereo.mg @@ -1,13 +1,13 @@ { "header": { - "releaseVersion": "2025.1.0", + "releaseVersion": "2026.1.0+develop", "fileVersion": "2.0", "nodesVersions": { "CameraInit": "12.0", "CopyFiles": "1.3", "LightingCalibration": "1.0", "PhotometricStereo": "1.0", - "SphereDetection": "1.0" + "SphereDetection": "2.0" }, "template": true },