From 9cb3f7c7ffdb76c1efcd9077e35aaf1324a70935 Mon Sep 17 00:00:00 2001 From: Niels Dekker Date: Mon, 16 Sep 2024 21:46:29 +0200 Subject: [PATCH] ENH: Add `PointSet::SetPointsByCoordinates(coordinates)` Aims to provide a safe alternative to `PointSet::SetPoints(PointsVectorContainer *)`. --- Modules/Core/Common/include/itkPointSet.h | 4 + Modules/Core/Common/include/itkPointSet.hxx | 46 +++++++++++ Modules/Core/Common/test/CMakeLists.txt | 1 + Modules/Core/Common/test/itkPointSetGTest.cxx | 82 +++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 Modules/Core/Common/test/itkPointSetGTest.cxx diff --git a/Modules/Core/Common/include/itkPointSet.h b/Modules/Core/Common/include/itkPointSet.h index aeb3db8defe1..9300bc724888 100644 --- a/Modules/Core/Common/include/itkPointSet.h +++ b/Modules/Core/Common/include/itkPointSet.h @@ -169,6 +169,10 @@ class ITK_TEMPLATE_EXPORT PointSet : public DataObject void SetPoints(PointsVectorContainer *); + /** Sets the points by specifying its coordinates. */ + void + SetPointsByCoordinates(const std::vector & coordinates); + /** Get the points container. */ PointsContainer * GetPoints(); diff --git a/Modules/Core/Common/include/itkPointSet.hxx b/Modules/Core/Common/include/itkPointSet.hxx index f4731b7d4415..9b23d817c622 100644 --- a/Modules/Core/Common/include/itkPointSet.hxx +++ b/Modules/Core/Common/include/itkPointSet.hxx @@ -81,6 +81,52 @@ PointSet::SetPoints(PointsVectorContainer * this->Modified(); } + +template +void +PointSet::SetPointsByCoordinates(const std::vector & coordinates) +{ + itkDebugMacro("Setting the points to the specified coordinates"); + + const size_t numberOfCoordinates = coordinates.size(); + + if (numberOfCoordinates % PointDimension != 0) + { + itkExceptionMacro("Number of specified coordinates incompatible with the point dimension"); + } + + const size_t numberOfPoints = numberOfCoordinates / PointDimension; + + if (m_PointsContainer == nullptr) + { + m_PointsContainer = PointsContainer::New(); + } + + using STLContainerType = typename PointsContainer::STLContainerType; + + STLContainerType & points = m_PointsContainer->CastToSTLContainer(); + points.clear(); + + if constexpr (std::is_same_v>) + { + // STLContainerType is either an std::vector or an std::map. Only when it is an std::vector, it should be resized. + // std::map does not have a resize function. + points.resize(numberOfPoints); + } + + auto coordinateIterator = coordinates.cbegin(); + + for (PointIdentifier pointIdentifier{}; pointIdentifier < numberOfPoints; ++pointIdentifier) + { + PointType & point = points[pointIdentifier]; + std::copy_n(coordinateIterator, PointDimension, point.begin()); + coordinateIterator += PointDimension; + } + + this->Modified(); +} + + template auto PointSet::GetPoints() -> PointsContainer * diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 8f6c1a36334c..461b2f96ed79 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -1748,6 +1748,7 @@ set(ITKCommonGTests itkOffsetGTest.cxx itkOptimizerParametersGTest.cxx itkPointGTest.cxx + itkPointSetGTest.cxx itkRGBAPixelGTest.cxx itkRGBPixelGTest.cxx itkShapedImageNeighborhoodRangeGTest.cxx diff --git a/Modules/Core/Common/test/itkPointSetGTest.cxx b/Modules/Core/Common/test/itkPointSetGTest.cxx new file mode 100644 index 000000000000..6c1cb8f2ea5a --- /dev/null +++ b/Modules/Core/Common/test/itkPointSetGTest.cxx @@ -0,0 +1,82 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +// First include the header file to be tested: +#include "itkPointSet.h" +#include "../../QuadEdgeMesh/include/itkQuadEdgeMeshTraits.h" +#include +#include // For equal. +#include // For iota. + +namespace +{ +template +void +TestSetPointsByCoordinates(TPointSet & pointSet) +{ + using CoordRepType = typename TPointSet::CoordRepType; + + static constexpr auto PointDimension = TPointSet::PointDimension; + + for (unsigned int numberOfCoordinates{ 1 }; numberOfCoordinates < PointDimension; ++numberOfCoordinates) + { + // SetPointsByCoordinates is expected to throw an exception when the specified number of coordinates is not a + // multiple of PointDimension. + EXPECT_THROW(pointSet.SetPointsByCoordinates(std::vector(numberOfCoordinates)), itk::ExceptionObject); + } + + for (const unsigned int numberOfPoints : { 2, 1, 0 }) + { + std::vector coordinates(numberOfPoints * PointDimension); + + // Just make sure that all coordinates have different values, for the purpose of the test. + std::iota(coordinates.begin(), coordinates.end(), CoordRepType()); + { + const auto modifiedTime = pointSet.GetMTime(); + pointSet.SetPointsByCoordinates(coordinates); + EXPECT_GT(pointSet.GetMTime(), modifiedTime); + } + + using PointsContainerType = typename TPointSet::PointsContainer; + using PointIdentifier = typename TPointSet::PointIdentifier; + using PointType = typename TPointSet::PointType; + + const typename PointsContainerType::ConstPointer points = pointSet.GetPoints(); + + ASSERT_NE(points, nullptr); + ASSERT_EQ(points->size(), numberOfPoints); + + const typename PointsContainerType::STLContainerType & stlContainer = points->CastToSTLConstContainer(); + auto coordinateIterator = coordinates.cbegin(); + + for (PointIdentifier pointIdentifier{}; pointIdentifier < numberOfPoints; ++pointIdentifier) + { + const PointType & point = stlContainer.at(pointIdentifier); + EXPECT_TRUE(std::equal(point.cbegin(), point.cend(), coordinateIterator)); + coordinateIterator += PointDimension; + } + } +} +} // namespace + + +TEST(PointSet, SetPointsByCoordinates) +{ + TestSetPointsByCoordinates(*itk::PointSet::New()); + TestSetPointsByCoordinates(*itk::PointSet>::New()); +}