Commit 496f5df8 authored by Flavien BRIDAULT-LOUCHEZ's avatar Flavien BRIDAULT-LOUCHEZ
Browse files

merge(feat/PointList_helper): into dev

This MR promotes code for processing PointLists from rd into helper functions.

The functions are:
- computeDistance
- transform
- associate

Unit tests for those function are also added as part of fwDataTools tests.

See merge request FW4SPL/fw4spl!170
parents 2ef89650 c98bdbad
/* ***** BEGIN LICENSE BLOCK *****
* FW4SPL - Copyright (C) IRCAD, 2017-2018.
* Distributed under the terms of the GNU Lesser General Public License (LGPL) as
* published by the Free Software Foundation.
* ****** END LICENSE BLOCK ****** */
#ifndef __FWDATATOOLS_HELPER_POINTLIST_HPP__
#define __FWDATATOOLS_HELPER_POINTLIST_HPP__
#include "fwDataTools/config.hpp"
#include <fwData/Array.hpp>
#include <fwData/PointList.hpp>
#include <fwData/TransformationMatrix3D.hpp>
namespace fwDataTools
{
namespace helper
{
/**
* @brief Defines a helper to modify a ::fwData::PointList.
*/
class FWDATATOOLS_CLASS_API PointList
{
public:
/// Constructor. Does nothing.
FWDATATOOLS_API PointList();
/// Destructor. Does nothing.
FWDATATOOLS_API ~PointList();
/**
* @brief Computes the point-to-point distance between 2 pointlists
* @param pointList1 [::fwData::PointList]: first point list
* @param pointList2 [::fwData::PointList]: second point list
* @return array of the size of one the pointlists (they must have the same size)
*/
FWDATATOOLS_API static ::fwData::Array::sptr
computeDistance(::fwData::PointList::sptr pointList1, ::fwData::PointList::sptr pointList2);
/**
* @brief Transform a pointList with a transformation matrix
* @param pointList [::fwData::PointList]: pointlist to be transformed
* @param matrix [::fwData::TransformationMatrix3D]: transformation to apply to each points in pointlist
*/
FWDATATOOLS_API static void transform(::fwData::PointList::sptr& pointList,
const ::fwData::TransformationMatrix3D::csptr& matrix);
/**
* @brief Associate 2 pointLists:
* Take 2 pointLists as input and re-order the second one, so that the points at the
* same index on both lists are the closest to each other
* @param pointList1 [::fwData::PointList]: first pointlist
* @param pointList2 [::fwData::PointList]: pointlist that will be re-ordered
*/
FWDATATOOLS_API static void associate(const ::fwData::PointList::csptr& pointList1,
::fwData::PointList::sptr pointList2);
};
} // namespace helper
} // namespace fwDataTools
#endif // __FWDATATOOLS_HELPER_POINTLIST_HPP__
/* ***** BEGIN LICENSE BLOCK *****
* FW4SPL - Copyright (C) IRCAD, 2017-2018.
* Distributed under the terms of the GNU Lesser General Public License (LGPL) as
* published by the Free Software Foundation.
* ****** END LICENSE BLOCK ****** */
#include "fwDataTools/helper/PointList.hpp"
#include <fwDataTools/helper/Array.hpp>
#include <fwDataTools/TransformationMatrix3D.hpp>
#include <fwData/Point.hpp>
#include <fwData/PointList.hpp>
#include <glm/geometric.hpp>
#include <glm/vec3.hpp>
#include <list>
namespace fwDataTools
{
namespace helper
{
//-----------------------------------------------------------------------------
PointList::PointList()
{
}
//-----------------------------------------------------------------------------
PointList::~PointList()
{
}
//-----------------------------------------------------------------------------
::fwData::Array::sptr
PointList::computeDistance(::fwData::PointList::sptr pointList1,
::fwData::PointList::sptr pointList2)
{
SLM_ASSERT("the 2 pointLists must have the same number of points",
pointList1->getPoints().size() == pointList2->getCRefPoints().size() );
const ::fwData::PointList::PointListContainer points1 = pointList1->getCRefPoints();
const ::fwData::PointList::PointListContainer points2 = pointList2->getCRefPoints();
const size_t size = points1.size();
::fwData::Array::sptr outputArray = ::fwData::Array::New();
::fwData::Array::SizeType arraySize;
arraySize.push_back(size);
outputArray->resize("double", arraySize, 1, true);
::fwDataTools::helper::Array arrayHelper(outputArray);
double* distanceArray = arrayHelper.begin<double>();
for (size_t i = 0; i < size; ++i)
{
const ::fwData::Point::PointCoordArrayType tmp1 = points1[i]->getCRefCoord();
const ::fwData::Point::PointCoordArrayType tmp2 = points2[i]->getCRefCoord();
const ::glm::dvec3 pt1 = ::glm::dvec3(tmp1[0], tmp1[1], tmp1[2]);
const ::glm::dvec3 pt2 = ::glm::dvec3(tmp2[0], tmp2[1], tmp2[2]);
distanceArray[i] = ::glm::distance(pt1, pt2);
}
return outputArray;
}
//------------------------------------------------------------------------------
void PointList::transform(::fwData::PointList::sptr& pointList,
const ::fwData::TransformationMatrix3D::csptr& matrix)
{
::fwData::PointList::PointListContainer points = pointList->getRefPoints();
const size_t size = points.size();
for(size_t i = 0; i < size; ++i)
{
::fwData::Point::sptr& pt = points[i];
// Transform the current point with the input matrix
::fwDataTools::TransformationMatrix3D::multiply(matrix, pt, pt);
}
}
//------------------------------------------------------------------------------
void PointList::associate(const ::fwData::PointList::csptr& pointList1,
::fwData::PointList::sptr pointList2)
{
SLM_ASSERT("the 2 pointLists must have the same number of points",
pointList1->getCRefPoints().size() == pointList2->getCRefPoints().size() );
::fwData::PointList::PointListContainer points1 = pointList1->getPoints();
::fwData::PointList::PointListContainer points2 = pointList2->getRefPoints();
const size_t size = points1.size();
// Transform first point list into vector< ::glm::dvec3 > (no erase is performed)
std::vector< ::glm::dvec3 > vec1;
vec1.reserve(size);
//and second one into a list (since we will erase associated points)
std::list< ::glm::dvec3 > list2;
for(size_t i = 0; i < size; ++i)
{
const ::fwData::Point::PointCoordArrayType tmp1 = points1[i]->getCoord();
const ::fwData::Point::PointCoordArrayType tmp2 = points2[i]->getCoord();
// Add the point to vector/list
vec1.push_back(::glm::dvec3( tmp1[0], tmp1[1], tmp1[2]));
list2.push_back(::glm::dvec3( tmp2[0], tmp2[1], tmp2[2]));
}
size_t index = 0;
for(auto point1 : vec1)
{
// Identify the closest point
double distanceMin = std::numeric_limits<double>::max();
std::list< ::glm::dvec3 >::iterator itClosestPoint;
std::list< ::glm::dvec3 >::iterator it2 = list2.begin();
for(; it2 != list2.end(); it2++)
{
const ::glm::dvec3 point2 = *it2;
const double distance = ::glm::distance(point1, point2);
if(distance < distanceMin)
{
distanceMin = distance;
itClosestPoint = it2;
}
}
::fwData::Point::PointCoordArrayType pointCoord;
pointCoord[0] = itClosestPoint->x;
pointCoord[1] = itClosestPoint->y;
pointCoord[2] = itClosestPoint->z;
::fwData::Point::sptr pt = points2[index];
pt->setCoord(pointCoord);
++index;
// Erase the already matched point
list2.erase(itClosestPoint);
}
}
//-----------------------------------------------------------------------------
} // namespace helper
} // namespace fwDataTools
/* ***** BEGIN LICENSE BLOCK *****
* FW4SPL - Copyright (C) IRCAD, 2017-2018.
* Distributed under the terms of the GNU Lesser General Public License (LGPL) as
* published by the Free Software Foundation.
* ****** END LICENSE BLOCK ****** */
#ifndef __FWDATATOOLS_UT_HELPER_POINTLISTTEST_HPP__
#define __FWDATATOOLS_UT_HELPER_POINTLISTTEST_HPP__
#include <fwData/PointList.hpp>
#include <cppunit/extensions/HelperMacros.h>
namespace fwDataTools
{
namespace ut
{
class PointListTest : public CPPUNIT_NS::TestFixture
{
private:
CPPUNIT_TEST_SUITE( PointListTest );
CPPUNIT_TEST( computeDistance );
CPPUNIT_TEST( transform );
CPPUNIT_TEST( associate );
CPPUNIT_TEST_SUITE_END();
public:
// interface
void setUp();
void tearDown();
void computeDistance();
void transform();
void associate();
};
} //namespace ut
} //namespace fwDataTools
#endif //__FWDATATOOLS_UT_HELPER_POINTLISTTEST_HPP__
/* ***** BEGIN LICENSE BLOCK *****
* FW4SPL - Copyright (C) IRCAD, 2017-2018.
* Distributed under the terms of the GNU Lesser General Public License (LGPL) as
* published by the Free Software Foundation.
* ****** END LICENSE BLOCK ****** */
#include "helper/PointListTest.hpp"
#include <fwDataTools/helper/Array.hpp>
#include <fwDataTools/helper/PointList.hpp>
#include <fwCore/Exception.hpp>
#include <fwData/Point.hpp>
#include <glm/geometric.hpp>
#include <glm/vec3.hpp>
#include <random>
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( ::fwDataTools::ut::PointListTest );
namespace fwDataTools
{
namespace ut
{
//------------------------------------------------------------------------------
void PointListTest::setUp()
{
// Set up context before running a test.
}
//------------------------------------------------------------------------------
void PointListTest::tearDown()
{
// Clean up after the test run.
}
//------------------------------------------------------------------------------
void PointListTest::computeDistance()
{
const size_t nbPoints = 42;
::fwData::PointList::sptr pl1;
::fwData::PointList::sptr pl2;
::fwData::Point::sptr p;
// Simple test with empty point lists
{
pl1 = ::fwData::PointList::New();
pl2 = ::fwData::PointList::New();
// Compare the point lists
::fwData::Array::sptr outputArray = ::fwDataTools::helper::PointList::computeDistance(pl1, pl2);
CPPUNIT_ASSERT(outputArray->getNumberOfElements() == 0);
}
// Simple test with parallel point lists
{
pl1 = ::fwData::PointList::New();
pl2 = ::fwData::PointList::New();
// Build 2 pointlists:
// The first one with increasing x values
// And the second one with inscreasing x values but shifted in y
for(size_t i = 0; i < nbPoints; i++)
{
p = ::fwData::Point::New(static_cast<float>(i), 0.0f, 0.0f);
pl1->pushBack(p);
p = ::fwData::Point::New(static_cast<float>(i), 1.0f, 0.0f);
pl2->pushBack(p);
}
// Compare the point lists
::fwData::Array::sptr outputArray = ::fwDataTools::helper::PointList::computeDistance(pl1, pl2);
::fwDataTools::helper::Array arrayHelper(outputArray);
double* distanceArray = arrayHelper.begin<double>();
for(size_t i = 0; i < nbPoints; i++)
{
CPPUNIT_ASSERT_DOUBLES_EQUAL(distanceArray[i], 1.0, 1e-8);
}
}
// Simple test with diverging point lists
{
pl1 = ::fwData::PointList::New();
pl2 = ::fwData::PointList::New();
// Build 2 point lists:
// The first one with increasing x values
// And the second one with increasing x values but shifted in y
for(size_t i = 0; i < nbPoints; i++)
{
p = ::fwData::Point::New(static_cast<float>(i), 0.0f, 0.0f);
pl1->pushBack(p);
p = ::fwData::Point::New(static_cast<float>(i), static_cast<float>(i), 0.0f);
pl2->pushBack(p);
}
// Compare the point lists
::fwData::Array::sptr outputArray = ::fwDataTools::helper::PointList::computeDistance(pl1, pl2);
::fwDataTools::helper::Array arrayHelper(outputArray);
double* distanceArray = arrayHelper.begin<double>();
for(size_t i = 0; i < nbPoints; i++)
{
CPPUNIT_ASSERT_DOUBLES_EQUAL(distanceArray[i], static_cast<double>(i), 1e-8);
}
}
}
//------------------------------------------------------------------------------
void PointListTest::transform()
{
// Simple test with identity
{
// Test sample
::fwData::PointList::sptr pl1 = ::fwData::PointList::New();
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 1.0f));
// Reference list
const ::fwData::PointList::PointListContainer points1 = pl1->getPoints();
const size_t size = points1.size();
::fwData::PointList::sptr pl2 = ::fwData::PointList::New();
for(size_t i = 0; i < size; i++)
{
const ::fwData::Point::PointCoordArrayType tmp = points1[i]->getCRefCoord();
pl2->pushBack(::fwData::Point::New(tmp[0], tmp[1], tmp[2]));
}
const ::fwData::TransformationMatrix3D::sptr tf1 = ::fwData::TransformationMatrix3D::New();
::fwDataTools::helper::PointList::transform(pl1, tf1);
const ::fwData::PointList::PointListContainer points2 = pl2->getPoints();
for(size_t i = 0; i < size; i++)
{
const ::fwData::Point::PointCoordArrayType tmp1 = points1[i]->getCRefCoord();
const ::fwData::Point::PointCoordArrayType tmp2 = points2[i]->getCRefCoord();
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[0], tmp2[0], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[1], tmp2[1], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[2], tmp2[2], 1e-8);
}
}
// Simple test with translation
{
std::vector<float> translation(3, 0.0f);
translation[0] = 8.0;
translation[1] = 16.0;
translation[2] = 32.0;
// Test sample
::fwData::PointList::sptr pl1 = ::fwData::PointList::New();
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 1.0f));
// Reference list
const ::fwData::PointList::PointListContainer points1 = pl1->getPoints();
const size_t size = points1.size();
::fwData::PointList::sptr pl2 = ::fwData::PointList::New();
for(size_t i = 0; i < size; i++)
{
const ::fwData::Point::PointCoordArrayType tmp = points1[i]->getCRefCoord();
pl2->pushBack(::fwData::Point::New(tmp[0] + translation[0], tmp[1] + translation[1],
tmp[2] + translation[2]));
}
const ::fwData::TransformationMatrix3D::sptr tf1 = ::fwData::TransformationMatrix3D::New();
tf1->setCoefficient(0, 3, translation[0]);
tf1->setCoefficient(1, 3, translation[1]);
tf1->setCoefficient(2, 3, translation[2]);
::fwDataTools::helper::PointList::transform(pl1, tf1);
const ::fwData::PointList::PointListContainer points2 = pl2->getPoints();
for(size_t i = 0; i < size; i++)
{
const ::fwData::Point::PointCoordArrayType tmp1 = points1[i]->getCRefCoord();
const ::fwData::Point::PointCoordArrayType tmp2 = points2[i]->getCRefCoord();
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[0], tmp2[0], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[1], tmp2[1], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[2], tmp2[2], 1e-8);
}
}
// Simple test with rotation
{
::fwData::PointList::sptr pl1 = ::fwData::PointList::New();
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 0.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 0.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(1.0f, 1.0f, 1.0f));
pl1->pushBack(::fwData::Point::New(0.0f, 1.0f, 1.0f));
::fwData::PointList::sptr pl2 = ::fwData::PointList::New();
pl2->pushBack(::fwData::Point::New(0.0f, 0.0f, 0.0f));
pl2->pushBack(::fwData::Point::New(-1.0f, 0.0f, 0.0f));
pl2->pushBack(::fwData::Point::New(-1.0f, -1.0f, 0.0f));
pl2->pushBack(::fwData::Point::New(0.0f, -1.0f, 0.0f));
pl2->pushBack(::fwData::Point::New(0.0f, 0.0f, 1.0f));
pl2->pushBack(::fwData::Point::New(-1.0f, 0.0f, 1.0f));
pl2->pushBack(::fwData::Point::New(-1.0f, -1.0f, 1.0f));
pl2->pushBack(::fwData::Point::New(0.0f, -1.0f, 1.0f));
// Perform a 180° rotation around Z
const ::fwData::TransformationMatrix3D::sptr tf1 = ::fwData::TransformationMatrix3D::New();
tf1->setCoefficient(0, 0, -1.0f);
tf1->setCoefficient(0, 1, 0.0f);
tf1->setCoefficient(1, 0, 0.0f);
tf1->setCoefficient(1, 1, -1.0f);
::fwDataTools::helper::PointList::transform(pl1, tf1);
const ::fwData::PointList::PointListContainer points1 = pl1->getPoints();
size_t size = points1.size();
const ::fwData::PointList::PointListContainer points2 = pl2->getPoints();
for(size_t i = 0; i < size; i++)
{
const ::fwData::Point::PointCoordArrayType tmp1 = points1[i]->getCRefCoord();
const ::fwData::Point::PointCoordArrayType tmp2 = points2[i]->getCRefCoord();
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[0], tmp2[0], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[1], tmp2[1], 1e-8);
CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp1[2], tmp2[2], 1e-8);
}
}
}
//------------------------------------------------------------------------------
void PointListTest::associate()
{
const size_t nbPoints = 42;
::fwData::PointList::sptr pl1;
::fwData::PointList::sptr pl2;
::fwData::Point::sptr p;
// Simple test with empty point lists
{
pl1 = ::fwData::PointList::New();
pl2 = ::fwData::PointList::New();
// Associate empty point lists
::fwDataTools::helper::PointList::associate(pl1, pl2);
// No results expected
}
// Test with simple matrices
// Create two lists with the same sets of points and shift them with transformation matrices
// Associating them should make the x components match
{
pl1 = ::fwData::PointList::New();
pl2 = ::fwData::PointList::New();
// Build 2 point lists with the same points, the point are in the inverse order in the second list
for(size_t i = 0; i <= nbPoints; i++)
{
p = ::fwData::Point::New(static_cast<float>(i), 0.0f, 0.0f);
pl1->pushBack(p);
p = ::fwData::Point::New(static_cast<float>(nbPoints - i), 0.0f, 0.0f);
pl2->pushBack(p);
}
// Transform the point lists, shift the points in y
::fwData::TransformationMatrix3D::sptr tf1 = ::fwData::TransformationMatrix3D::New();
tf1->setCoefficient(1, 3, 42.0);
::fwData::TransformationMatrix3D::sptr tf2 = ::fwData::TransformationMatrix3D::New();
tf2->setCoefficient(1, 3, -42.0);
::fwDataTools::helper::PointList::transform(pl1, tf1);
::fwDataTools::helper::PointList::transform(pl2, tf2);