Skip to content
Merged
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
59 changes: 59 additions & 0 deletions meshroom/aliceVision/SfMDataIntersection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
__version__ = "1.0"

from meshroom.core import desc
from meshroom.core.utils import VERBOSE_LEVEL


class SfMDataIntersection(desc.AVCommandLineNode):
commandLine = "aliceVision_sfmDataIntersection {allParams}"
size = desc.DynamicNodeSize("input")

category = "Utils"
documentation = """
Filters the 3D landmarks of a SfMData file to keep only the ones that have
observations from both camera group A and camera group B.

The main input SfMData provides all the content (views, poses, intrinsics, landmarks).
InputA and InputB each define a subset of cameras (by their view IDs) that must both
be represented in the observations of a landmark for it to be retained.

This is typically used after a merge (e.g. SfMMerge) to extract only the landmarks
that are co-observed by cameras from two distinct acquisition groups.
"""

inputs = [
desc.File(
name="input",
label="Input SfMData",
description="Main SfMData file providing all views, poses, intrinsics and landmarks.",
value="",
),
desc.File(
name="inputA",
label="Input SfMData A",
description="SfMData file whose view IDs define camera group A.",
value="",
),
desc.File(
name="inputB",
label="Input SfMData B",
description="SfMData file whose view IDs define camera group B.",
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="Path to the output SfMData file containing only intersection landmarks.",
value="{nodeCacheFolder}/sfmData.abc",
),
]
11 changes: 11 additions & 0 deletions src/software/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,17 @@ if (ALICEVISION_BUILD_SFM)
Boost::program_options
)

# SfM Data Intersection
alicevision_add_software(aliceVision_sfmDataIntersection
SOURCE main_sfmDataIntersection.cpp
FOLDER ${FOLDER_SOFTWARE_UTILS}
LINKS aliceVision_system
aliceVision_cmdline
aliceVision_sfmData
aliceVision_sfmDataIO
Boost::program_options
)

# Tracks Merge
alicevision_add_software(aliceVision_tracksMerging
SOURCE main_tracksMerging.cpp
Expand Down
142 changes: 142 additions & 0 deletions src/software/utils/main_sfmDataIntersection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// This file is part of the AliceVision project.
// Copyright (c) 2026 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 <aliceVision/sfmData/SfMData.hpp>
#include <aliceVision/sfmDataIO/sfmDataIO.hpp>
#include <aliceVision/cmdline/cmdline.hpp>
#include <aliceVision/system/main.hpp>
#include <boost/program_options.hpp>

#include <string>
#include <set>

// 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 sfmDataFilenameA;
std::string sfmDataFilenameB;
std::string outSfMDataFilename;

// clang-format off
po::options_description requiredParams("Required parameters");
requiredParams.add_options()
("input,i", po::value<std::string>(&sfmDataFilename)->required(),
"Path to the main SfMData file (content source: views, poses, intrinsics, landmarks).")
("inputA,a", po::value<std::string>(&sfmDataFilenameA)->required(),
"Path to SfMData file A. Only its view IDs are used to define camera group A.")
("inputB,b", po::value<std::string>(&sfmDataFilenameB)->required(),
"Path to SfMData file B. Only its view IDs are used to define camera group B.")
("output,o", po::value<std::string>(&outSfMDataFilename)->required(),
"Output SfMData scene.");
// clang-format on

CmdLine cmdline("AliceVision sfmDataIntersection");
cmdline.add(requiredParams);
if (!cmdline.execute(argc, argv))
{
return EXIT_FAILURE;
}

// Load the main input scene (content source)
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;
}

// Load inputA and inputB — only their view IDs are needed
sfmData::SfMData sfmDataA;
if (!sfmDataIO::load(sfmDataA, sfmDataFilenameA, sfmDataIO::ESfMData::VIEWS))
{
ALICEVISION_LOG_ERROR("The input SfMData file A '" << sfmDataFilenameA << "' cannot be read");
return EXIT_FAILURE;
}

sfmData::SfMData sfmDataB;
if (!sfmDataIO::load(sfmDataB, sfmDataFilenameB, sfmDataIO::ESfMData::VIEWS))
{
ALICEVISION_LOG_ERROR("The input SfMData file B '" << sfmDataFilenameB << "' cannot be read");
return EXIT_FAILURE;
}

// Build sets of view IDs from inputA and inputB
std::set<IndexT> viewsSetA;
for (const auto& [viewId, _] : sfmDataA.getViews())
{
viewsSetA.insert(viewId);
}

std::set<IndexT> viewsSetB;
for (const auto& [viewId, _] : sfmDataB.getViews())
{
viewsSetB.insert(viewId);
}

ALICEVISION_LOG_INFO("Input SfMData: " << sfmData.getViews().size() << " views, "
<< sfmData.getLandmarks().size() << " landmarks");
ALICEVISION_LOG_INFO("InputA: " << viewsSetA.size() << " views");
ALICEVISION_LOG_INFO("InputB: " << viewsSetB.size() << " views");

// The output is the main input SfMData with landmarks filtered
sfmData::SfMData outputSfmData = sfmData;

ALICEVISION_LOG_INFO("Total landmarks before filtering: " << outputSfmData.getLandmarks().size());

// Filter landmarks: keep only those with observations from BOTH camera groups
auto& landmarks = outputSfmData.getLandmarks();
for (auto it = landmarks.begin(); it != landmarks.end();)
{
bool hasObsFromSetA = false;
bool hasObsFromSetB = false;

for (const auto& [viewId, obs] : it->second.getObservations())
{
if (viewsSetA.count(viewId))
{
hasObsFromSetA = true;
}
if (viewsSetB.count(viewId))
{
hasObsFromSetB = true;
}

if (hasObsFromSetA && hasObsFromSetB)
{
break;
}
}

if (hasObsFromSetA && hasObsFromSetB)
{
++it;
}
else
{
it = landmarks.erase(it);
}
}

ALICEVISION_LOG_INFO("Total landmarks after filtering: " << outputSfmData.getLandmarks().size());

if (!sfmDataIO::save(outputSfmData, outSfMDataFilename, sfmDataIO::ESfMData::ALL))
{
ALICEVISION_LOG_ERROR("An error occurred while trying to save '" << outSfMDataFilename << "'");
return EXIT_FAILURE;
}

return EXIT_SUCCESS;

Check notice on line 141 in src/software/utils/main_sfmDataIntersection.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/software/utils/main_sfmDataIntersection.cpp#L25-L141

Complex Method
}
Loading