Memory::computeTransform(): keep global descriptors when reloading node data (#1788)

When a registration needs data that a node no longer has in RAM, its
SensorData is replaced by getNodeData(), which doesn't load global
descriptors from the database. The next Signature::compareTo() with that
node then fails its assertion on the number of global descriptors. Same
issue as #1686 in copyData().
This commit is contained in:
Artem
2026-10-11 19:57:02 -07:00
committed by GitHub
parent cf4dcab118
commit 29e4346986
2 changed files with 64 additions and 0 deletions
+5
View File
@@ -3453,13 +3453,18 @@ Transform Memory::computeTransform(
(_registrationPipeline->isScanRequired() && fromS.sensorData().imageCompressed().empty() && fromS.sensorData().laserScanCompressed().isEmpty()) ||
(_registrationPipeline->isUserDataRequired() && fromS.sensorData().imageCompressed().empty() && fromS.sensorData().userDataCompressed().empty()))
{
// getNodeData() doesn't load global descriptors from the database, keep those in RAM
std::vector<GlobalDescriptor> globalDescriptors = fromS.sensorData().globalDescriptors();
fromS.sensorData() = getNodeData(fromS.id(), true, true, true, true);
fromS.sensorData().setGlobalDescriptors(globalDescriptors);
}
if(((_reextractLoopClosureFeatures && (_registrationPipeline->isImageRequired() || guess.isNull())) && toS.sensorData().imageCompressed().empty()) ||
(_registrationPipeline->isScanRequired() && toS.sensorData().imageCompressed().empty() && toS.sensorData().laserScanCompressed().isEmpty()) ||
(_registrationPipeline->isUserDataRequired() && toS.sensorData().imageCompressed().empty() && toS.sensorData().userDataCompressed().empty()))
{
std::vector<GlobalDescriptor> globalDescriptors = toS.sensorData().globalDescriptors();
toS.sensorData() = getNodeData(toS.id(), true, true, true, true);
toS.sensorData().setGlobalDescriptors(globalDescriptors);
}
// uncompress only what we need
cv::Mat imgBuf, depthBuf, userBuf;
+59
View File
@@ -4437,6 +4437,65 @@ TEST_F(MemoryFixture, ComputeIcpTransformMultiRejectsScansTooFarApart)
<< "unexpected reason: " << info.rejectedMsg;
}
// ---------------------------------------------------------------------------
// Memory::computeTransform(): reloading a saved node's data for a registration.
//
// Once a node is saved to the database, its compressed data is dropped from RAM,
// and a registration that needs it (here ICP) reloads it with getNodeData(),
// which doesn't load global descriptors. The node must keep them: a later
// Signature::compareTo() asserts that both nodes have the same number of global
// descriptors (same issue as #1686 in copyData()).
// ---------------------------------------------------------------------------
TEST(MemoryTest, ComputeTransformKeepsGlobalDescriptorsWhenReloadingData)
{
const std::string dbPath = uniqueDbPath();
{
Memory memory(icpMemoryParams());
ASSERT_TRUE(memory.init(dbPath));
const cv::Mat image(8, 8, CV_8UC1, cv::Scalar(128));
const cv::Mat covariance = cv::Mat::eye(6, 6, CV_64FC1) * 0.01;
const Transform motion(0.10f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
const LaserScan corner = memoryCorner2D();
const cv::Mat descriptor(1, 4, CV_32FC1, cv::Scalar(0.5f)); // normalized
SensorData first(image);
first.setLaserScan(corner);
first.addGlobalDescriptor(GlobalDescriptor(1, descriptor));
ASSERT_TRUE(memory.update(first, Transform::getIdentity(), covariance));
const int oldId = memory.getLastSignatureId();
SensorData second(image);
second.setLaserScan(util3d::transformLaserScan(corner, motion.inverse()));
second.addGlobalDescriptor(GlobalDescriptor(1, descriptor));
ASSERT_TRUE(memory.update(second, motion, covariance));
const int newId = memory.getLastSignatureId();
// As Rtabmap::process() does after adding a node, with a database on disk.
memory.saveLocationData(oldId);
memory.emptyTrash();
memory.joinTrashThread(); // emptyTrash() is async; wait for the DB write
ASSERT_TRUE(memory.getSignature(oldId)->isSaved());
ASSERT_TRUE(memory.getSignature(oldId)->sensorData().laserScanCompressed().isEmpty());
ASSERT_EQ(memory.getSignature(oldId)->sensorData().globalDescriptors().size(), 1u);
RegistrationInfo info;
memory.computeTransform(oldId, newId, motion, &info);
const Signature * oldS = memory.getSignature(oldId);
const Signature * newS = memory.getSignature(newId);
ASSERT_NE(oldS, nullptr);
ASSERT_NE(newS, nullptr);
EXPECT_FALSE(oldS->sensorData().laserScanCompressed().isEmpty()); // reloaded
EXPECT_EQ(oldS->sensorData().globalDescriptors().size(), 1u);
EXPECT_NO_THROW(newS->compareTo(*oldS));
memory.close(false);
}
UFile::erase(dbPath);
}
// ---------------------------------------------------------------------------
// createSignature() reuses the caller's compressed blob instead of
// re-compressing, but only while the pixels it would store are provably the