/*========================================================================= * * 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. * *=========================================================================*/ #ifndef itkShapeLabelObject_h #define itkShapeLabelObject_h #include "itkLabelObject.h" #include "itkLabelMap.h" #include "itkMath.h" #include "itkAffineTransform.h" namespace itk { /** * \class ShapeLabelObject * \brief A Label object to store the common attributes related to the shape of the object * * ShapeLabelObject stores the common attributes related to the shape of the object * * \author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. * * This implementation was taken from the Insight Journal paper: * https://www.insight-journal.org/browse/publication/176 * * \ingroup DataRepresentation * \ingroup ITKLabelMap */ template class ITK_TEMPLATE_EXPORT ShapeLabelObject : public LabelObject { public: ITK_DISALLOW_COPY_AND_MOVE(ShapeLabelObject); /** Standard class type aliases */ using Self = ShapeLabelObject; using Superclass = LabelObject; using LabelObjectType = typename Superclass::LabelObjectType; using Pointer = SmartPointer; using ConstPointer = SmartPointer; using ConstWeakPointer = WeakPointer; /** Method for creation through the object factory. */ itkNewMacro(Self); /** \see LightObject::GetNameOfClass() */ itkOverrideGetNameOfClassMacro(ShapeLabelObject); using LabelMapType = LabelMap; static constexpr unsigned int ImageDimension = VImageDimension; using typename Superclass::IndexType; using LabelType = TLabel; using typename Superclass::LineType; using typename Superclass::LengthType; using typename Superclass::AttributeType; /** The number of pixels. */ static constexpr AttributeType NUMBER_OF_PIXELS = 100; /** PhysicalSize is the size of the object in physical units. * It is equal to the NumberOfPixels multiplied by the * physical pixel size. Its type is double. */ static constexpr AttributeType PHYSICAL_SIZE = 101; /** Centroid is the position of the center of the shape in * physical coordinates. It is not constrained to be in the * object, and thus can be outside if the object is not convex.*/ static constexpr AttributeType CENTROID = 104; static constexpr AttributeType BOUNDING_BOX = 105; /** NumberOfPixelsOnBorder is the number of pixels in the objects * which are on the border of the image. A pixel on several borders * (a pixel in a corner) is counted only one time, so the size on * border can't be greater than the size of the object. This attribute * is particularly useful to remove the objects which are touching * too much the border. Its type is unsigned long.*/ static constexpr AttributeType NUMBER_OF_PIXELS_ON_BORDER = 106; /** PerimeterOnBorder is the physical size of the objects which are on * the border of the image. In 2D, it is a distance, in 3D, a surface, * etc. Contrary to the PhysicalSize attribute which is directly linked to * the NumberOfPixels, this attribute is not directly linked to the * NumberOfPixelsOnBorder attribute. This attribute is particularly useful * to remove the objects which are touching too much the border. * Its type is double.*/ static constexpr AttributeType PERIMETER_ON_BORDER = 107; /** FeretDiameter is the diameter in physical units of the sphere which * include all the object. The feret diameter is not computed by default, * because of its high computation. Its type is double.*/ static constexpr AttributeType FERET_DIAMETER = 108; /** PrincipalMoments contains the principal moments.*/ static constexpr AttributeType PRINCIPAL_MOMENTS = 109; /** BinaryPrincipalAxes contains the principal axes of the object.*/ static constexpr AttributeType PRINCIPAL_AXES = 110; /** Elongation is the ratio of the largest principal moment to the * second largest principal moment. Its value is greater or equal to 1. * Its type is double.*/ static constexpr AttributeType ELONGATION = 111; /** The perimeter of the object.*/ static constexpr AttributeType PERIMETER = 112; static constexpr AttributeType ROUNDNESS = 113; /** EquivalentRadius is the equivalent radius of the hypersphere of the * same size than the label object. The value depends on the image spacing. * Its type is double.*/ static constexpr AttributeType EQUIVALENT_SPHERICAL_RADIUS = 114; /** EquivalentPerimeter is the equivalent perimeter of the hypersphere of * the same size than the label object. The value depends on the image spacing. * Its type is double.*/ static constexpr AttributeType EQUIVALENT_SPHERICAL_PERIMETER = 115; /** EquivalentEllipsoidPerimeter is the size of the ellipsoid of the same size * and the same ratio on all the axes than the label object. The value depends * on the image spacing.*/ static constexpr AttributeType EQUIVALENT_ELLIPSOID_DIAMETER = 116; static constexpr AttributeType FLATNESS = 117; static constexpr AttributeType PERIMETER_ON_BORDER_RATIO = 118; /** Origin of the oriented bounding box defined by the principle * axes, and the oriented bounding box size */ static constexpr AttributeType ORIENTED_BOUNDING_BOX_ORIGIN = 119; /** Size of the oriented bounding box defined by the principle axes, * and the bounding box origin. * * The combination of the OBB origin, OBB size and OBB direction ( * principal axes ) defines a coordinate system suitable to use * resample the OBB onto its own image. */ static constexpr AttributeType ORIENTED_BOUNDING_BOX_SIZE = 120; static AttributeType GetAttributeFromName(const std::string & s) { if (s == "NumberOfPixels") { return NUMBER_OF_PIXELS; } else if (s == "PhysicalSize") { return PHYSICAL_SIZE; } else if (s == "Centroid") { return CENTROID; } else if (s == "BoundingBox") { return BOUNDING_BOX; } else if (s == "NumberOfPixelsOnBorder") { return NUMBER_OF_PIXELS_ON_BORDER; } else if (s == "PerimeterOnBorder") { return PERIMETER_ON_BORDER; } else if (s == "FeretDiameter") { return FERET_DIAMETER; } else if (s == "PrincipalMoments") { return PRINCIPAL_MOMENTS; } else if (s == "PrincipalAxes") { return PRINCIPAL_AXES; } else if (s == "Elongation") { return ELONGATION; } else if (s == "Perimeter") { return PERIMETER; } else if (s == "Roundness") { return ROUNDNESS; } else if (s == "EquivalentSphericalRadius") { return EQUIVALENT_SPHERICAL_RADIUS; } else if (s == "EquivalentSphericalPerimeter") { return EQUIVALENT_SPHERICAL_PERIMETER; } else if (s == "EquivalentEllipsoidDiameter") { return EQUIVALENT_ELLIPSOID_DIAMETER; } else if (s == "Flatness") { return FLATNESS; } else if (s == "PerimeterOnBorderRatio") { return PERIMETER_ON_BORDER_RATIO; } else if (s == "OrientedBoundingBoxOrigin") { return ORIENTED_BOUNDING_BOX_ORIGIN; } else if (s == "OrientedBoundingBoxSize") { return ORIENTED_BOUNDING_BOX_SIZE; } // can't recognize the name return Superclass::GetAttributeFromName(s); } static std::string GetNameFromAttribute(const AttributeType & a) { std::string name; switch (a) { case NUMBER_OF_PIXELS: name = "NumberOfPixels"; break; case PHYSICAL_SIZE: name = "PhysicalSize"; break; case CENTROID: name = "Centroid"; break; case BOUNDING_BOX: name = "BoundingBox"; break; case NUMBER_OF_PIXELS_ON_BORDER: name = "NumberOfPixelsOnBorder"; break; case PERIMETER_ON_BORDER: name = "PerimeterOnBorder"; break; case FERET_DIAMETER: name = "FeretDiameter"; break; case PRINCIPAL_MOMENTS: name = "PrincipalMoments"; break; case PRINCIPAL_AXES: name = "PrincipalAxes"; break; case ELONGATION: name = "Elongation"; break; case PERIMETER: name = "Perimeter"; break; case ROUNDNESS: name = "Roundness"; break; case EQUIVALENT_SPHERICAL_RADIUS: name = "EquivalentSphericalRadius"; break; case EQUIVALENT_SPHERICAL_PERIMETER: name = "EquivalentSphericalPerimeter"; break; case EQUIVALENT_ELLIPSOID_DIAMETER: name = "EquivalentEllipsoidDiameter"; break; case FLATNESS: name = "Flatness"; break; case PERIMETER_ON_BORDER_RATIO: name = "PerimeterOnBorderRatio"; break; case ORIENTED_BOUNDING_BOX_ORIGIN: name = "OrientedBoundingBoxOrigin"; break; case ORIENTED_BOUNDING_BOX_SIZE: name = "OrientedBoundingBoxSize"; break; default: // can't recognize the name name = Superclass::GetNameFromAttribute(a); break; } return name; } using RegionType = ImageRegion; using CentroidType = Point; using MatrixType = Matrix; using VectorType = Vector; public: using OrientedBoundingBoxDirectionType = MatrixType; using OrientedBoundingBoxPointType = Point; using OrientedBoundingBoxSizeType = Vector; using OrientedBoundingBoxVerticesType = FixedArray(2, ImageDimension)>; const RegionType & GetBoundingBox() const { return m_BoundingBox; } void SetBoundingBox(const RegionType & v) { m_BoundingBox = v; } const double & GetPhysicalSize() const { return m_PhysicalSize; } void SetPhysicalSize(const double v) { m_PhysicalSize = v; } const SizeValueType & GetNumberOfPixels() const { return m_NumberOfPixels; } void SetNumberOfPixels(const SizeValueType & v) { m_NumberOfPixels = v; } const CentroidType & GetCentroid() const { return m_Centroid; } void SetCentroid(const CentroidType & centroid) { m_Centroid = centroid; } const SizeValueType & GetNumberOfPixelsOnBorder() const { return m_NumberOfPixelsOnBorder; } void SetNumberOfPixelsOnBorder(const SizeValueType & v) { m_NumberOfPixelsOnBorder = v; } const double & GetPerimeterOnBorder() const { return m_PerimeterOnBorder; } void SetPerimeterOnBorder(const double v) { m_PerimeterOnBorder = v; } const double & GetFeretDiameter() const { return m_FeretDiameter; } void SetFeretDiameter(const double v) { m_FeretDiameter = v; } const VectorType & GetPrincipalMoments() const { return m_PrincipalMoments; } void SetPrincipalMoments(const VectorType & v) { m_PrincipalMoments = v; } const MatrixType & GetPrincipalAxes() const { return m_PrincipalAxes; } void SetPrincipalAxes(const MatrixType & v) { m_PrincipalAxes = v; } const double & GetElongation() const { return m_Elongation; } void SetElongation(const double v) { m_Elongation = v; } const double & GetPerimeter() const { return m_Perimeter; } void SetPerimeter(const double v) { m_Perimeter = v; } const double & GetRoundness() const { return m_Roundness; } void SetRoundness(const double v) { m_Roundness = v; } const double & GetEquivalentSphericalRadius() const { return m_EquivalentSphericalRadius; } void SetEquivalentSphericalRadius(const double v) { m_EquivalentSphericalRadius = v; } const double & GetEquivalentSphericalPerimeter() const { return m_EquivalentSphericalPerimeter; } void SetEquivalentSphericalPerimeter(const double v) { m_EquivalentSphericalPerimeter = v; } const VectorType & GetEquivalentEllipsoidDiameter() const { return m_EquivalentEllipsoidDiameter; } void SetEquivalentEllipsoidDiameter(const VectorType & v) { m_EquivalentEllipsoidDiameter = v; } const double & GetFlatness() const { return m_Flatness; } void SetFlatness(const double v) { m_Flatness = v; } const double & GetPerimeterOnBorderRatio() const { return m_PerimeterOnBorderRatio; } void SetPerimeterOnBorderRatio(const double v) { m_PerimeterOnBorderRatio = v; } const OrientedBoundingBoxPointType & GetOrientedBoundingBoxOrigin() const { return m_OrientedBoundingBoxOrigin; } void SetOrientedBoundingBoxOrigin(const OrientedBoundingBoxPointType & v) { m_OrientedBoundingBoxOrigin = v; } const OrientedBoundingBoxSizeType & GetOrientedBoundingBoxSize() const { return m_OrientedBoundingBoxSize; } void SetOrientedBoundingBoxSize(const OrientedBoundingBoxSizeType & v) { m_OrientedBoundingBoxSize = v; } // some helper methods - not really required, but really useful! /** Get the BoundingBox as an ImageRegion. */ const RegionType & GetRegion() const { return m_BoundingBox; } /** Get the direction matrix for the oriented bounding box * coordinates. This is an alias for the principal axes. */ const OrientedBoundingBoxDirectionType & GetOrientedBoundingBoxDirection() const { return this->GetPrincipalAxes(); } /** Get an array of point vertices which define the corners of the * oriented bounding box in physical space. * * The first element in the array contains minimum coordinate values * which correspond to the origin while the last contains the maximum. * Use the index of the array in binary to determine min/max for the * indexed vertex. For example, in 2D, binary counting will give[0,0], [0,1], * [1,0], [1,1], which corresponds to [minX,minY], [minX,maxY], * [maxX,minY], [maxX,maxY]. */ OrientedBoundingBoxVerticesType GetOrientedBoundingBoxVertices() const { const MatrixType obbToPhysical(this->GetOrientedBoundingBoxDirection().GetTranspose()); OrientedBoundingBoxVerticesType vertices; // Use binary index to map the vertices of the OBB to an array. For // example, in 2D, binary counting will give[0,0], [0,1], [1,0], // [1,1], which corresponds to [minX,minY], [minX,maxY], // [maxX,minY], [maxX,maxY]. for (unsigned int i = 0; i < OrientedBoundingBoxVerticesType::Length; ++i) { constexpr unsigned int msb = 1 << (ImageDimension - 1); Vector offset; for (unsigned int j = 0; j < ImageDimension; ++j) { if (i & msb >> j) { offset[j] = m_OrientedBoundingBoxSize[j]; } else { offset[j] = 0; } } vertices[i] = m_OrientedBoundingBoxOrigin + obbToPhysical * offset; } return vertices; } /** Affine transform for mapping to and from principal axis */ using AffineTransformType = AffineTransform; using AffineTransformPointer = typename AffineTransformType::Pointer; /** Get the affine transform from principal axes to physical axes * This method returns an affine transform which transforms from * the principal axes coordinate system to physical coordinates. */ AffineTransformPointer GetPrincipalAxesToPhysicalAxesTransform() const { typename AffineTransformType::MatrixType matrix; typename AffineTransformType::OffsetType offset; for (unsigned int i = 0; i < VImageDimension; ++i) { offset[i] = m_Centroid[i]; for (unsigned int j = 0; j < VImageDimension; ++j) { matrix[j][i] = m_PrincipalAxes[i][j]; // Note the transposition } } AffineTransformPointer result = AffineTransformType::New(); result->SetMatrix(matrix); result->SetOffset(offset); return result; } /** Get the affine transform from physical axes to principal axes * This method returns an affine transform which transforms from * the physical coordinate system to the principal axes coordinate * system. */ AffineTransformPointer GetPhysicalAxesToPrincipalAxesTransform() const { typename AffineTransformType::MatrixType matrix; typename AffineTransformType::OffsetType offset; for (unsigned int i = 0; i < VImageDimension; ++i) { offset[i] = m_Centroid[i]; for (unsigned int j = 0; j < VImageDimension; ++j) { matrix[j][i] = m_PrincipalAxes[i][j]; // Note the transposition } } AffineTransformPointer result = AffineTransformType::New(); result->SetMatrix(matrix); result->SetOffset(offset); AffineTransformPointer inverse = AffineTransformType::New(); result->GetInverse(inverse); return inverse; } template void CopyAttributesFrom(const TSourceLabelObject * src) { Superclass::template CopyAttributesFrom(src); m_BoundingBox = src->GetBoundingBox(); m_NumberOfPixels = src->GetNumberOfPixels(); m_PhysicalSize = src->GetPhysicalSize(); m_Centroid = src->GetCentroid(); m_NumberOfPixelsOnBorder = src->GetNumberOfPixelsOnBorder(); m_PerimeterOnBorder = src->GetPerimeterOnBorder(); m_FeretDiameter = src->GetFeretDiameter(); m_PrincipalMoments = src->GetPrincipalMoments(); m_PrincipalAxes = src->GetPrincipalAxes(); m_Elongation = src->GetElongation(); m_Perimeter = src->GetPerimeter(); m_Roundness = src->GetRoundness(); m_EquivalentSphericalRadius = src->GetEquivalentSphericalRadius(); m_EquivalentSphericalPerimeter = src->GetEquivalentSphericalPerimeter(); m_EquivalentEllipsoidDiameter = src->GetEquivalentEllipsoidDiameter(); m_Flatness = src->GetFlatness(); m_PerimeterOnBorderRatio = src->GetPerimeterOnBorderRatio(); m_OrientedBoundingBoxOrigin = src->GetOrientedBoundingBoxOrigin(); m_OrientedBoundingBoxSize = src->GetOrientedBoundingBoxSize(); } template void CopyAllFrom(const TSourceLabelObject * src) { itkAssertOrThrowMacro((src != nullptr), "Null Pointer"); this->template CopyLinesFrom(src); this->template CopyAttributesFrom(src); } protected: ShapeLabelObject() { m_NumberOfPixels = 0; m_PhysicalSize = 0; m_Centroid.Fill(0); m_NumberOfPixelsOnBorder = 0; m_PerimeterOnBorder = 0; m_FeretDiameter = 0; m_PrincipalMoments.Fill(0); m_PrincipalAxes.Fill(0); m_Elongation = 0; m_Perimeter = 0; m_Roundness = 0; m_EquivalentSphericalRadius = 0; m_EquivalentSphericalPerimeter = 0; m_EquivalentEllipsoidDiameter.Fill(0); m_Flatness = 0; m_PerimeterOnBorderRatio = 0; m_OrientedBoundingBoxSize.Fill(0); m_OrientedBoundingBoxOrigin.Fill(0); } void PrintSelf(std::ostream & os, Indent indent) const override { Superclass::PrintSelf(os, indent); os << indent << "BoundingBox: " << m_BoundingBox << std::endl; os << indent << "NumberOfPixels: " << static_cast::PrintType>(m_NumberOfPixels) << std::endl; os << indent << "PhysicalSize: " << m_PhysicalSize << std::endl; os << indent << "Centroid: " << static_cast::PrintType>(m_Centroid) << std::endl; os << indent << "NumberOfPixelsOnBorder: " << static_cast::PrintType>(m_NumberOfPixelsOnBorder) << std::endl; os << indent << "PerimeterOnBorder: " << m_PerimeterOnBorder << std::endl; os << indent << "FeretDiameter: " << m_FeretDiameter << std::endl; os << indent << "PrincipalMoments: " << m_PrincipalMoments << std::endl; os << indent << "PrincipalAxes: " << std::endl << m_PrincipalAxes; os << indent << "Elongation: " << m_Elongation << std::endl; os << indent << "Perimeter: " << m_Perimeter << std::endl; os << indent << "Roundness: " << m_Roundness << std::endl; os << indent << "EquivalentSphericalRadius: " << m_EquivalentSphericalRadius << std::endl; os << indent << "EquivalentSphericalPerimeter: " << m_EquivalentSphericalPerimeter << std::endl; os << indent << "EquivalentEllipsoidDiameter: " << m_EquivalentEllipsoidDiameter << std::endl; os << indent << "Flatness: " << m_Flatness << std::endl; os << indent << "PerimeterOnBorderRatio: " << m_PerimeterOnBorderRatio << std::endl; os << indent << "OrientedBoundingBoxSize: " << static_cast::PrintType>(m_OrientedBoundingBoxSize) << std::endl; os << indent << "OrientedBoundingBoxOrigin: " << static_cast::PrintType>(m_OrientedBoundingBoxOrigin) << std::endl; } private: RegionType m_BoundingBox{}; SizeValueType m_NumberOfPixels{}; double m_PhysicalSize{}; CentroidType m_Centroid{}; SizeValueType m_NumberOfPixelsOnBorder{}; double m_PerimeterOnBorder{}; double m_FeretDiameter{}; VectorType m_PrincipalMoments{}; MatrixType m_PrincipalAxes{}; double m_Elongation{}; double m_Perimeter{}; double m_Roundness{}; double m_EquivalentSphericalRadius{}; double m_EquivalentSphericalPerimeter{}; VectorType m_EquivalentEllipsoidDiameter{}; double m_Flatness{}; double m_PerimeterOnBorderRatio{}; OrientedBoundingBoxSizeType m_OrientedBoundingBoxSize{}; OrientedBoundingBoxPointType m_OrientedBoundingBoxOrigin{}; }; } // end namespace itk #endif