From 225a10796e0cea0976d8d45e904c7ed5c30458f7 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Thu, 12 Jun 2025 13:41:27 +0200 Subject: [PATCH 1/2] Remove unmaintained import known poses --- meshroom/aliceVision/ImportKnownPoses.py | 44 -- src/software/convert/CMakeLists.txt | 13 - .../convert/main_importKnownPoses.cpp | 501 ------------------ 3 files changed, 558 deletions(-) delete mode 100644 meshroom/aliceVision/ImportKnownPoses.py delete mode 100644 src/software/convert/main_importKnownPoses.cpp diff --git a/meshroom/aliceVision/ImportKnownPoses.py b/meshroom/aliceVision/ImportKnownPoses.py deleted file mode 100644 index 0a7e5e9866..0000000000 --- a/meshroom/aliceVision/ImportKnownPoses.py +++ /dev/null @@ -1,44 +0,0 @@ -__version__ = "1.0" - -from meshroom.core import desc -from meshroom.core.utils import VERBOSE_LEVEL - - -class ImportKnownPoses(desc.AVCommandLineNode): - commandLine = 'aliceVision_importKnownPoses {allParams}' - size = desc.DynamicNodeSize('sfmData') - - documentation = ''' - Import known poses from various file formats like xmp or json. - ''' - - inputs = [ - desc.File( - name="sfmData", - label="SfMData", - description="Input SfMData file.", - value="", - ), - desc.File( - name="knownPosesData", - label="Known Poses Data", - description="Known poses data in the JSON or XMP format.", - 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="Output", - description="Path to the output SfMData file.", - value="{nodeCacheFolder}/sfmData.abc", - ), - ] diff --git a/src/software/convert/CMakeLists.txt b/src/software/convert/CMakeLists.txt index 73e20e7f99..0f1b2daf76 100644 --- a/src/software/convert/CMakeLists.txt +++ b/src/software/convert/CMakeLists.txt @@ -18,19 +18,6 @@ if (ALICEVISION_BUILD_SFM) Boost::system ) - alicevision_add_software(aliceVision_importKnownPoses - SOURCE main_importKnownPoses.cpp - FOLDER ${FOLDER_SOFTWARE_CONVERT} - LINKS aliceVision_localization - aliceVision_feature - aliceVision_sfmData - aliceVision_sfmDataIO - aliceVision_cmdline - Boost::program_options - Boost::boost - Boost::timer - ) - if (TARGET E57Format AND ALICEVISION_BUILD_MVS) alicevision_add_software(aliceVision_importE57 SOURCE main_importE57.cpp diff --git a/src/software/convert/main_importKnownPoses.cpp b/src/software/convert/main_importKnownPoses.cpp deleted file mode 100644 index 919d9b3838..0000000000 --- a/src/software/convert/main_importKnownPoses.cpp +++ /dev/null @@ -1,501 +0,0 @@ -// This file is part of the AliceVision project. -// Copyright (c) 2021 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 -#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 2 -#define ALICEVISION_SOFTWARE_VERSION_MINOR 0 - -using namespace aliceVision; - -namespace po = boost::program_options; -namespace json = boost::property_tree; -namespace fs = std::filesystem; - -struct XMPData -{ - std::vector rotation; - std::vector position; - std::vector distortionCoefficients; - std::string distortionModel; - double focalLength35mm = 0.0; - int skew = 0; - double aspectRatio = 1.0; - double principalPointU = 0.0; - double principalPointV = 0.0; - std::string calibrationPrior = "DEFAULT"; - int calibrationGroup = 0; - int distortionGroup = 0; - int inTexturing = 0; - int inMeshing = 0; -}; - -XMPData read_xmp(const std::string& xmpFilepath, const std::string& knownPosesFilePath, const std::string& stem, fs::directory_entry pathIt) -{ - XMPData xmp; - const fs::path path = pathIt.path(); - if (!is_regular_file(path)) - ALICEVISION_THROW_ERROR("Path isn't a regulat file: " << path); - std::string extension = path.extension().string(); - boost::to_lower(extension); - if (extension != ".xmp") - { - ALICEVISION_THROW_ERROR("Unknown extension: " << extension); - } - - json::ptree tree; - read_xml(knownPosesFilePath + '/' + stem + ".xmp", tree); - - xmp.distortionModel = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:DistortionModel", "DEFAULT"); - xmp.focalLength35mm = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:FocalLength35mm", 0.0); - xmp.skew = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:Skew", 0); - xmp.aspectRatio = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:AspectRatio", 1.0); - xmp.principalPointU = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:PrincipalPointU", 0); - xmp.principalPointV = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:PrincipalPointV", 0); - xmp.calibrationPrior = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:CalibrationPrior", "DEFAULT"); - xmp.calibrationGroup = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:CalibrationGroup", 0); - xmp.distortionGroup = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:DistortionGroup", 0); - xmp.inTexturing = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:InTexturing", 0); - xmp.inMeshing = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:InMeshing", 0); - - std::string rotationStr = tree.get("x:xmpmeta.rdf:RDF.rdf:Description.xcr:Rotation", ""); - std::vector rotationStrings; - boost::split(rotationStrings, rotationStr, boost::is_any_of(" \t"), boost::token_compress_on); - ALICEVISION_LOG_TRACE("stem: " << stem); - ALICEVISION_LOG_TRACE("rotation: " << rotationStrings); - - for (const auto& rot_val : rotationStrings) - { - xmp.rotation.push_back(std::stod(rot_val)); - } - - std::string positionStr = tree.get("x:xmpmeta.rdf:RDF.rdf:Description.xcr:Position", ""); - std::vector positionStrings; - if (!positionStr.empty()) - { - boost::split(positionStrings, positionStr, boost::is_any_of(" \t"), boost::token_compress_on); - ALICEVISION_LOG_TRACE("position: " << positionStrings); - } - else - { - positionStr = tree.get("x:xmpmeta.rdf:RDF.rdf:Description..xcr:Position", ""); - boost::split(positionStrings, positionStr, boost::is_any_of(" \t"), boost::token_compress_on); - } - - for (const auto& pos_val : positionStrings) - { - xmp.position.push_back(std::stod(pos_val)); - } - std::string distortionStr = tree.get("x:xmpmeta.rdf:RDF.rdf:Description.xcr:DistortionCoeficients", ""); - std::vector distortionStrings; - boost::split(distortionStrings, distortionStr, boost::is_any_of(" \t"), boost::token_compress_on); - ALICEVISION_LOG_TRACE("distortion: " << distortionStrings); - - for (const auto& disto_val : distortionStrings) - { - xmp.distortionCoefficients.push_back(std::stod(disto_val)); - } - return xmp; -} - -// import from a SfMData format to another -int aliceVision_main(int argc, char** argv) -{ - // command-line parameters - std::string knownPosesFilePath; - std::string sfmDataFilePath; - std::string outputFilename; - - sfmData::SfMData sfmData; - - // clang-format off - po::options_description requiredParams("Required parameters"); - requiredParams.add_options() - ("knownPosesData", po::value(&knownPosesFilePath)->required(), - "Input path to a json file or a folder containing an XMP file per image.") - ("sfmData", po::value(&sfmDataFilePath)->required(), - "SfmData filepath.") - ("output,o", po::value(&outputFilename)->required(), - "Output sfmData filepath."); - // clang-format on - - CmdLine cmdline("AliceVision importKnownPoses"); - cmdline.add(requiredParams); - if (!cmdline.execute(argc, argv)) - { - return EXIT_FAILURE; - } - - // Loading the sfmData to modify it - if (!sfmDataIO::load(sfmData, sfmDataFilePath, sfmDataIO::ESfMData::ALL)) - { - ALICEVISION_LOG_ERROR("The input SfMData file '" << sfmDataFilePath << "' cannot be read."); - return EXIT_FAILURE; - } - if (sfmData.getViews().empty()) - { - ALICEVISION_LOG_ERROR("The input SfMData file '" << sfmDataFilePath << "' is empty."); - return EXIT_FAILURE; - } - - std::map viewIdPerStem; - for (const auto viewIt : sfmData.getViews()) - { - const std::string stem = fs::path(viewIt.second->getImage().getImagePath()).stem().string(); - viewIdPerStem[stem] = viewIt.first; - } - fs::path knownPosesPath(knownPosesFilePath); - if (fs::is_directory(knownPosesPath)) - { - try - { - for (const auto& pathIt : fs::directory_iterator(knownPosesPath)) - { - const std::string stem = pathIt.path().stem().string(); - if (viewIdPerStem.count(stem) == 0) - { - continue; - } - - const XMPData xmp = read_xmp(pathIt.path().string(), knownPosesFilePath, stem, pathIt); - - const IndexT viewId = viewIdPerStem[stem]; - sfmData::View& view = sfmData.getView(viewId); - sfmData::CameraPose& pose = sfmData.getPoses()[view.getPoseId()]; - - std::shared_ptr intrinsicBase = sfmData.getIntrinsicSharedPtr(view.getIntrinsicId()); - - Eigen::Map> rot(xmp.rotation.data()); - - Vec3 pos_vec(xmp.position[0], xmp.position[1], xmp.position[2]); - Vec3 translation = -rot * pos_vec; - - Eigen::Matrix4d T; - T.setIdentity(); - T.block<3, 3>(0, 0) = rot; - T.block<3, 1>(0, 3) = translation; - - Eigen::Matrix4d av_T_cr = Eigen::Matrix4d::Zero(); - av_T_cr(0, 0) = 1.0; - av_T_cr(1, 2) = -1.0; - av_T_cr(2, 1) = 1.0; - av_T_cr(3, 3) = 1.0; - - T = T * av_T_cr; - Eigen::Matrix3d R = T.block<3, 3>(0, 0); - translation = T.block<3, 1>(0, 3); - pos_vec = -R.transpose() * translation; - - geometry::Pose3 pos3(R, pos_vec); - pose.setTransform(pos3); - - std::shared_ptr intrinsic = - std::dynamic_pointer_cast(intrinsicBase); - if (intrinsic == nullptr) - { - ALICEVISION_THROW_ERROR("Invalid intrinsic"); - } - - const double imageRatio = static_cast(view.getImage().getWidth()) / static_cast(view.getImage().getHeight()); - const double sensorWidth = intrinsic->sensorWidth(); - const double maxSize = std::max(view.getImage().getWidth(), view.getImage().getHeight()); - const double focalLengthmm = (sensorWidth * xmp.focalLength35mm) / 36.0; - const double focalLengthPix = maxSize * focalLengthmm / sensorWidth; - const double offsetX = (double(view.getImage().getWidth()) * 0.5) + (xmp.principalPointU * maxSize); - const double offsetY = (double(view.getImage().getHeight()) * 0.5) + (xmp.principalPointV * maxSize); - - intrinsic->setScale({focalLengthPix, focalLengthPix}); - intrinsic->setOffset({offsetX, offsetY}); - - std::cout << focalLengthPix << std::endl; - - if (xmp.distortionModel == "brown3t2") - { - std::shared_ptr camera = std::dynamic_pointer_cast(intrinsic); - if (camera == nullptr) - { - camera = camera::createPinhole(camera::EDISTORTION::DISTORTION_NONE, camera::EUNDISTORTION::UNDISTORTION_NONE); - camera->copyFrom(*intrinsic); - sfmData.getIntrinsics().at(view.getIntrinsicId()) = camera; - } - - if (xmp.distortionCoefficients.size() == 6) - { - std::vector distortionCoefficients; - - distortionCoefficients.push_back(xmp.distortionCoefficients[0]); - distortionCoefficients.push_back(xmp.distortionCoefficients[1]); - distortionCoefficients.push_back(xmp.distortionCoefficients[2]); - // Skip element at index 3 as it is empty - distortionCoefficients.push_back(xmp.distortionCoefficients[5]); - distortionCoefficients.push_back(xmp.distortionCoefficients[4]); - camera->setDistortionParams(distortionCoefficients); // vector of 5 elements (r1, r2, r3, t1, t2) - } - else - { - ALICEVISION_THROW_ERROR("Error in xmp file: " << stem << " the distortion coefficient doesn't have the right size."); - } - } - else if (xmp.distortionModel == "brown3") - { - std::shared_ptr camera = std::dynamic_pointer_cast(intrinsic); - if (camera == nullptr) - { - camera = camera::createPinhole(camera::EDISTORTION::DISTORTION_NONE, camera::EUNDISTORTION::UNDISTORTION_NONE); - camera->copyFrom(*intrinsic); - sfmData.getIntrinsics().at(view.getIntrinsicId()) = camera; - } - - if (xmp.distortionCoefficients.size() == 3) - { - std::vector distortionCoefficients = xmp.distortionCoefficients; - camera->setDistortionParams(distortionCoefficients); // vector of 5 elements (r1, r2, r3) - } - else - { - ALICEVISION_THROW_ERROR("Error in xmp file: " << stem << " the distortion coefficient doesn't have the right size."); - } - } - else - { - ALICEVISION_THROW_ERROR("Unsupported distortion model: " << xmp.distortionModel); - } - } - } - catch (boost::program_options::error& e) - { - ALICEVISION_CERR("ERROR: " << e.what() << std::endl); - return EXIT_FAILURE; - } - } - else if (is_regular_file(knownPosesPath)) - { - std::string extension = knownPosesPath.extension().string(); - boost::to_lower(extension); - if (extension == ".json") - { - std::ifstream jsonFile(knownPosesFilePath); - if (!jsonFile) - { - ALICEVISION_LOG_ERROR("Error opening file: " << knownPosesFilePath); - return EXIT_FAILURE; - } - - std::string line; - size_t count = 0; - std::vector> records; - std::vector> frameIdToPoseId; - - // Here we are making a vector that associate a frameId to a PoseId so we can access each easier - for (const auto& view : sfmData.getViews()) - { - frameIdToPoseId.emplace_back(view.second->getFrameId(), view.second->getPoseId()); - } - std::sort(frameIdToPoseId.begin(), frameIdToPoseId.end()); - - // ensure there is no duplicated frameId - auto it = - std::adjacent_find(frameIdToPoseId.begin(), frameIdToPoseId.end(), [](const auto& a, const auto& b) { return a.first == b.first; }); - if (it != frameIdToPoseId.end()) - { - ALICEVISION_THROW_ERROR("Duplicated frameId in sfmData: " << sfmDataFilePath << ", frameID: " << it->first); - } - // This is where we start to read our json line by line - while (getline(jsonFile, line)) - { - std::stringstream linestream(line); - std::vector up; - std::vector forward; - std::vector pose; - json::ptree pt; - - // We put each line in a stringstream because that is what boost's parser needs. - // The parser turns the json into a property tree. - json::json_parser::read_json(linestream, pt); - const int sensor = pt.get("sensorwidth", 0); - const double fov = pt.get("xFovDegrees", 0); - const long timestamp = pt.get("tstamp", 0); - const int frmcnt = pt.get("frmcnt", 0); - const double expoff = pt.get("exposureOff", 0); - const double expdur = pt.get("exposureDur", 0); - // These arguments are lists so we need to loop to store them properly - for (json::ptree::value_type& up_val : pt.get_child("up")) - { - std::string value = up_val.second.data(); - up.push_back(std::stof(value)); - } - for (json::ptree::value_type& for_val : pt.get_child("forward")) - { - std::string value = for_val.second.data(); - forward.push_back(std::stof(value)); - } - for (json::ptree::value_type& pose_val : pt.get_child("pose")) - { - std::string value = pose_val.second.data(); - pose.push_back(std::stof(value)); - } - // We use records to indexify our frame count this way we know which frame started the list, this will be our offset - records.emplace_back(count, frmcnt); - - // Without surprise we store our vector pose to get our position - Vec3 pos_vec(pose[0], pose[1], pose[2]); - Mat3 rot; - // And we need those two vectors to calculate the rotation matrix - Vec3 up_vec(up[0], up[1], up[2]); - Vec3 forward_vec(forward[0], forward[1], forward[2]); - - rot.row(0) = up_vec.cross(forward_vec); - rot.row(1) = -up_vec; - rot.row(2) = forward_vec; - // we store this new information into a pose3 - geometry::Pose3 pos3(rot, pos_vec); - - // And we set this pose into the sfmData using our frameId (which corresponds to the count) to set a new pos3 transform - IndexT sfmPoseId = frameIdToPoseId[count].second; - sfmData.getPoses()[sfmPoseId].setTransform(pos3); - count++; - // We repeat this to each line of the file which contains a json - } - } - else if (extension == ".ma") - { - std::ifstream file(knownPosesPath.string()); - - std::string line; - std::string name; - bool hasName = false; - bool hasPosition = false; - bool hasRotation = false; - double pos[3] = {0.0, 0.0, 0.0}; - double rot[3] = {0.0, 0.0, 0.0}; - - while (std::getline(file, line)) - { - std::regex regex("[^\\s\\t;]+"); - std::vector words; - - for (auto it = std::sregex_iterator(line.begin(), line.end(), regex); it != std::sregex_iterator(); it++) - { - std::string tok = it->str(); - tok.erase(std::remove(tok.begin(), tok.end(), '\"'), tok.end()); - words.push_back(tok); - } - - if (words.empty()) - continue; - - if (words[0] == "createNode") - { - if (words.size() == 4) - { - name = words[3]; - hasName = true; - hasPosition = false; - hasRotation = false; - } - } - - if (words[0] == "setAttr") - { - if (words[1] == ".translate") - { - if (hasName && (!hasPosition)) - { - hasPosition = true; - pos[0] = std::stod(words[4]); - pos[1] = std::stod(words[5]); - pos[2] = std::stod(words[6]); - } - } - - if (words[1] == ".rotate") - { - if (hasName && (!hasRotation)) - { - hasRotation = true; - rot[0] = std::stod(words[4]); - rot[1] = std::stod(words[5]); - rot[2] = std::stod(words[6]); - } - } - } - - if (hasName && hasRotation && hasPosition) - { - if (viewIdPerStem.count(name) == 0) - { - continue; - } - - const IndexT viewId = viewIdPerStem[name]; - sfmData::View& view = sfmData.getView(viewId); - sfmData::CameraPose& pose = sfmData.getPoses()[view.getPoseId()]; - - Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); - const Eigen::AngleAxis MX(degreeToRadian(rot[0]), Eigen::Vector3d::UnitX()); - const Eigen::AngleAxis MY(degreeToRadian(rot[1]), Eigen::Vector3d::UnitY()); - const Eigen::AngleAxis MZ(degreeToRadian(rot[2]), Eigen::Vector3d::UnitZ()); - R = MZ * MY * MX; - - Eigen::Vector3d position; - position(0) = pos[0]; - position(1) = pos[1]; - position(2) = pos[2]; - - Vec3 translation = -R * position; - - Eigen::Matrix3d alice_R_maya = Eigen::Matrix3d::Identity(); - - alice_R_maya(0, 0) = 1.0; - alice_R_maya(1, 1) = -1.0; - alice_R_maya(2, 2) = -1.0; - position = position; - - R = R * alice_R_maya; - - geometry::Pose3 pose3(R.transpose(), position); - pose.setTransform(pose3); - ALICEVISION_LOG_TRACE("Read maya: " << name); - - hasName = false; - hasRotation = false; - hasPosition = false; - } - } - } - } - - // export the SfMData scene in the expected format - if (!sfmDataIO::save(sfmData, outputFilename, sfmDataIO::ESfMData::ALL)) - { - ALICEVISION_LOG_ERROR("An error occurred while trying to save '" << outputFilename << "'"); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} From 513ce00897180ca5694b500c549e1309159624ec Mon Sep 17 00:00:00 2001 From: Fabien Servant <100348063+servantftransperfect@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:42:56 +0000 Subject: [PATCH 2/2] Enable Sfm Pose injecting --- meshroom/aliceVision/SfMPoseInjecting.py | 67 +++++++ src/software/utils/main_sfmPoseInjecting.cpp | 187 ++++++++++++++++--- 2 files changed, 225 insertions(+), 29 deletions(-) diff --git a/meshroom/aliceVision/SfMPoseInjecting.py b/meshroom/aliceVision/SfMPoseInjecting.py index f855235c04..6004aa4a28 100644 --- a/meshroom/aliceVision/SfMPoseInjecting.py +++ b/meshroom/aliceVision/SfMPoseInjecting.py @@ -13,6 +13,64 @@ class SfMPoseInjecting(desc.AVCommandLineNode): category = "Utils" documentation = """ Use a JSON file to inject poses inside the SfMData. + +[ + { + 'frame_no': 45, + 'rx': 1.2, + 'ry': -0.3, + 'rz': 0.1 + 'tx': 5, + 'ty': 6, + 'tz': -2 + } +] + +or + +[ + { + 'path': "image_filename", + 'rx': 1.2, + 'ry': -0.3, + 'rz': 0.1 + 'tx': 5, + 'ty': 6, + 'tz': -2 + } +] + +or + +[ + { + 'path': "/path/to/image", + 'rx': 1.2, + 'ry': -0.3, + 'rz': 0.1 + 'tx': 5, + 'ty': 6, + 'tz': -2 + } +] + + +frame_no is the detected frameId which is set by the number found in the image filename. + +Let's say you have a point with coordinates in the *camera frame*. The coordinates in the common world frame are given by the rotation matrix world_R_camera and the translation vector world_t_camera such that + +worldFramePoint = world_R_camera * cameraFramePoint + world_t_camera + +world_t_camera is defined by the triplet [tx, ty, tz] in the json file +world_R_camera is defined by the triplet [rx, ry, rz] (in degrees) in the json file. + +The matrix world_R_camera is built from the triplet, transformed using a function R depending on rotationFormat. + +If rotationFormat is EulerZXY, then world_R_camera = R_y(ry) * R_x(rx) * R_z(rz) +Where R_x(rx) is the rotation along the x axis of rx degrees, R_y(ry) is the rotation along the y axis of ry degrees, R_z(rz) is the rotation along the z axis of rz degrees. It is the ZXY euler representation. + +Meshroom assumes that the axis x, y and z of the geometric frame in which you define the rotation and the translation is in Right hand coordinates where X points to the right, Y points downward, and Z points away from the camera. But you can change this using geometricFrame. + """ inputs = [ @@ -36,6 +94,15 @@ class SfMPoseInjecting(desc.AVCommandLineNode): values=["EulerZXY"], value="EulerZXY", ), + desc.ChoiceParam( + name="geometricFrame", + label="Geometric Frame", + description="Defines the geometric frame for the input poses:\n" + " - RHXrYbZf : Right hand coordinates, X right, Y down, Z far" + " - RHXrYtZb : Right hand coordinates, X right, Y up, Z behind", + values=["RHXrYbZf", "RHXrYtZb"], + value="RHXrYbZf", + ), desc.ChoiceParam( name="verboseLevel", label="Verbose Level", diff --git a/src/software/utils/main_sfmPoseInjecting.cpp b/src/software/utils/main_sfmPoseInjecting.cpp index 9909dc3237..bbe40f1f7e 100644 --- a/src/software/utils/main_sfmPoseInjecting.cpp +++ b/src/software/utils/main_sfmPoseInjecting.cpp @@ -18,6 +18,7 @@ #include #include +#include // These constants define the current software version. // They must be updated when the command line is changed. @@ -30,6 +31,7 @@ namespace po = boost::program_options; struct PoseInput { IndexT frameId; + std::filesystem::path path; Eigen::Matrix4d T; }; @@ -76,41 +78,120 @@ inline std::istream& operator>>(std::istream& in, ERotationFormat& s) return in; } +/** + * I/O for Rotation format choice + */ + +enum class EGeometricFrameFormat +{ + RHXrYbZf, + RHXrYtZb +}; + +inline std::string EGeometricFrameFormat_enumToString(EGeometricFrameFormat format) +{ + switch (format) + { + case EGeometricFrameFormat::RHXrYbZf: + { + return "RHXrYbZf"; + } + case EGeometricFrameFormat::RHXrYtZb: + { + return "RHXrYtZb"; + } + } + throw std::out_of_range("Invalid Geometric format type Enum: " + std::to_string(int(format))); +} + +inline EGeometricFrameFormat EGeometricFrameFormat_stringToEnum(const std::string& format) +{ + if (format == "RHXrYbZf") + { + return EGeometricFrameFormat::RHXrYbZf; + } + + if (format == "RHXrYtZb") + { + return EGeometricFrameFormat::RHXrYtZb; + } + + throw std::out_of_range("Invalid Geometric frame format type Enum: " + format); +} + +inline std::ostream& operator<<(std::ostream& os, EGeometricFrameFormat s) +{ + return os << EGeometricFrameFormat_enumToString(s); +} + +inline std::istream& operator>>(std::istream& in, EGeometricFrameFormat& s) +{ + std::string token(std::istreambuf_iterator(in), {}); + s = EGeometricFrameFormat_stringToEnum(token); + return in; +} + + + /** * @brief get a pose from a boost json object (assume the file format is ok) * @param obj the input json object - * @param format the required rotation format to transform to rotation matrix + * @param rotationFormat the required rotation format to transform to rotation matrix + * @param geometricFrameFormat the geometric frame format used to interpret the camera pose * @param readPose the output pose information * @return false if the process failed */ -bool getPoseFromJson(const boost::json::object& obj, ERotationFormat format, PoseInput& readPose) +bool getPoseFromJson(const boost::json::object& obj, ERotationFormat rotationFormat, EGeometricFrameFormat & geometricFormat, PoseInput& readPose) { Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); - if (format == ERotationFormat::EulerZXY) + if (rotationFormat == ERotationFormat::EulerZXY) { - // Reading information from lineup - const double rx = degreeToRadian(boost::json::value_to(obj.at("rx"))); - const double ry = degreeToRadian(boost::json::value_to(obj.at("ry"))); - const double rz = degreeToRadian(boost::json::value_to(obj.at("rz"))); + try + { + const double rx = degreeToRadian(boost::json::value_to(obj.at("rx"))); + const double ry = degreeToRadian(boost::json::value_to(obj.at("ry"))); + const double rz = degreeToRadian(boost::json::value_to(obj.at("rz"))); - Eigen::AngleAxisd Rx(rx, Eigen::Vector3d::UnitX()); - Eigen::AngleAxisd Ry(ry, Eigen::Vector3d::UnitY()); - Eigen::AngleAxisd Rz(rz, Eigen::Vector3d::UnitZ()); + Eigen::AngleAxisd Rx(rx, Eigen::Vector3d::UnitX()); + Eigen::AngleAxisd Ry(ry, Eigen::Vector3d::UnitY()); + Eigen::AngleAxisd Rz(rz, Eigen::Vector3d::UnitZ()); - R = Ry.toRotationMatrix() * Rx.toRotationMatrix() * Rz.toRotationMatrix(); + R = Ry.toRotationMatrix() * Rx.toRotationMatrix() * Rz.toRotationMatrix(); + } + catch (boost::system::system_error error) + { + ALICEVISION_LOG_ERROR("Incorrect rotation parameters"); + return false; + } } else { + ALICEVISION_LOG_ERROR("Unsupported rotation parameters"); return false; } - readPose.frameId = boost::json::value_to(obj.at("frame_no")); - Eigen::Vector3d t; - t.x() = boost::json::value_to(obj.at("tx")); - t.y() = boost::json::value_to(obj.at("ty")); - t.z() = boost::json::value_to(obj.at("tz")); + try + { + t.x() = boost::json::value_to(obj.at("tx")); + t.y() = boost::json::value_to(obj.at("ty")); + t.z() = boost::json::value_to(obj.at("tz")); + } + catch (boost::system::system_error error) + { + //If no tx,ty,tz we assume 0 translation + } + + readPose.frameId = UndefinedIndexT; + if (obj.if_contains("frame_no")) + { + readPose.frameId = boost::json::value_to(obj.at("frame_no")); + } + else if (obj.if_contains("path")) + { + readPose.path = boost::json::value_to(obj.at("filename")); + } Eigen::Matrix4d world_T_camera = Eigen::Matrix4d::Identity(); world_T_camera.block<3, 3>(0, 0) = R; @@ -118,10 +199,20 @@ bool getPoseFromJson(const boost::json::object& obj, ERotationFormat format, Pos //Get transform in av coordinates Eigen::Matrix4d aliceTinput = Eigen::Matrix4d::Identity(); - aliceTinput(1, 1) = -1; - aliceTinput(1, 2) = 0; - aliceTinput(2, 1) = 0; - aliceTinput(2, 2) = -1; + + switch (geometricFormat) + { + case EGeometricFrameFormat::RHXrYtZb: + { + aliceTinput(1, 1) = -1; + aliceTinput(1, 2) = 0; + aliceTinput(2, 1) = 0; + aliceTinput(2, 2) = -1; + break; + } + default: + break; + } world_T_camera = aliceTinput * world_T_camera * aliceTinput.inverse(); @@ -135,10 +226,11 @@ bool getPoseFromJson(const boost::json::object& obj, ERotationFormat format, Pos * The JSON file contains an array of objects. Each object describes a frameId, a rotation and a translation. * @param posesFilename the input JSON filename * @param format the required rotation format to transform to rotation matrix + * @param geometricFrameFormat the geometric frame format used to interpret the camera pose * @param readPose the output poses vector * @return false if the process failed, true otherwise */ -bool getPosesFromJson(const std::string& posesFilename, ERotationFormat format, std::vector& readPoses) +bool getPosesFromJson(const std::string& posesFilename, ERotationFormat format, EGeometricFrameFormat & geometricFormat, std::vector& readPoses) { std::ifstream inputfile(posesFilename); if (!inputfile.is_open()) @@ -162,7 +254,7 @@ bool getPosesFromJson(const std::string& posesFilename, ERotationFormat format, const boost::json::object& obj = item.as_object(); PoseInput input; - if (getPoseFromJson(obj, format, input)) + if (getPoseFromJson(obj, format, geometricFormat, input)) { readPoses.push_back(input); } @@ -178,6 +270,7 @@ int aliceVision_main(int argc, char** argv) std::string sfmDataOutputFilename; std::string posesFilename; ERotationFormat format; + EGeometricFrameFormat gframe; // clang-format off po::options_description requiredParams("Required parameters"); @@ -187,7 +280,9 @@ int aliceVision_main(int argc, char** argv) ("output,o", po::value(&sfmDataOutputFilename)->required(), "SfMData output file with the injected poses.") ("rotationFormat,r", po::value(&format)->required(), - "Rotation format for the input poses: EulerZXY."); + "Rotation format for the input poses: EulerZXY.") + ("geometricFrame, g", po::value(&gframe)->required(), + "Defines the geometric frame for the input poses"); po::options_description optionalParams("Optional parameters"); optionalParams.add_options() @@ -224,7 +319,7 @@ int aliceVision_main(int argc, char** argv) } std::vector readPoses; - if (!getPosesFromJson(posesFilename, format, readPoses)) + if (!getPosesFromJson(posesFilename, format, gframe, readPoses)) { ALICEVISION_LOG_ERROR("Cannot read the poses"); return EXIT_FAILURE; @@ -235,13 +330,47 @@ int aliceVision_main(int argc, char** argv) { for (const auto& rpose : readPoses) { - if (pview->getFrameId() == rpose.frameId) + //Check if the frameid is the same than described + bool found = false; + if (rpose.frameId != UndefinedIndexT) { - geometry::Pose3 pose(rpose.T); - sfmData::CameraPose cpose(pose, false); - cpose.setRemovable(false); - sfmData.setAbsolutePose(id, cpose); + if (pview->getFrameId() == rpose.frameId) + { + found = true; + } } + + if (!found && !rpose.path.empty()) + { + //Check if this view has the same filename + std::filesystem::path viewPath(pview->getImage().getImagePath()); + if (rpose.path.has_relative_path()) + { + if (std::filesystem::canonical(viewPath) == std::filesystem::canonical(rpose.path)) + { + found = true; + } + } + else + { + if (viewPath.filename() == rpose.path.filename()) + { + found = true; + } + } + } + + if (!found) + { + continue; + } + + ALICEVISION_LOG_INFO("Pose assigned to image with path " << pview->getImage().getImagePath()); + + geometry::Pose3 pose(rpose.T); + sfmData::CameraPose cpose(pose, false); + cpose.setRemovable(false); + sfmData.setAbsolutePose(id, cpose); } }