/*========================================================================= Program: Insight Segmentation & Registration Toolkit Module: $RCSfile: itkShapeLabelCollectionImageFilter.txx,v $ Language: C++ Date: $Date: 2005/08/23 15:09:03 $ Version: $Revision: 1.6 $ Copyright (c) Insight Software Consortium. All rights reserved. See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef __itkShapeLabelCollectionImageFilter_txx #define __itkShapeLabelCollectionImageFilter_txx #include "itkShapeLabelCollectionImageFilter.h" #include "itkProgressReporter.h" #include "itkNeighborhoodIterator.h" #include "itkLabelCollectionImageToLabelImageFilter.h" #include "itkConstantBoundaryCondition.h" #include "vnl/algo/vnl_real_eigensystem.h" #include "vnl/algo/vnl_symmetric_eigensystem.h" namespace itk { template ShapeLabelCollectionImageFilter ::ShapeLabelCollectionImageFilter() { m_ComputeFeretDiameter = false; m_ComputePerimeter = false; } template void ShapeLabelCollectionImageFilter ::BeforeThreadedGenerateData() { Superclass::BeforeThreadedGenerateData(); // generate the label image, if needed if( m_ComputeFeretDiameter || m_ComputePerimeter ) { if( !m_LabelImage ) { // generate an image of the labelized image typedef LabelCollectionImageToLabelImageFilter< TImage, LabelImageType > LCI2IType; typename LCI2IType::Pointer lci2i = LCI2IType::New(); lci2i->SetInput( this->GetOutput() ); // respect the number of threads of the filter lci2i->SetNumberOfThreads( this->GetNumberOfThreads() ); lci2i->Update(); m_LabelImage = lci2i->GetOutput(); } } // delegate the computation of the perimeter to a dedicated calculator if( m_ComputePerimeter ) { m_PerimeterCalculator = PerimeterCalculatorType::New(); m_PerimeterCalculator->SetImage( m_LabelImage ); // m_PerimeterCalculator->SetNumberOfThreads( this->GetNumberOfThreads() ); m_PerimeterCalculator->Compute(); } } template void ShapeLabelCollectionImageFilter ::ThreadedGenerateData( LabelObjectType * labelObject ) { ImageType * output = this->GetOutput(); const LabelPixelType & label = labelObject->GetLabel(); // TODO: compute sizePerPixel, borderMin and borderMax in BeforeThreadedGenerateData() ? // compute the size per pixel, to be used later double sizePerPixel = 1; for( int i=0; iGetSpacing()[i]; } // compute the max the index on the border of the image IndexType borderMin = output->GetLargestPossibleRegion().GetIndex(); IndexType borderMax = borderMin; for( int i=0; iGetLargestPossibleRegion().GetSize()[i] - 1; } // init the vars unsigned long size = 0; ContinuousIndex< double, ImageDimension> centroid; centroid.Fill( 0 ); IndexType mins; mins.Fill( NumericTraits< long >::max() ); IndexType maxs; maxs.Fill( NumericTraits< long >::NonpositiveMin() ); unsigned long sizeOnBorder = 0; MatrixType centralMoments; centralMoments.Fill( 0 ); typename LabelObjectType::LineContainerType::const_iterator lit; typename LabelObjectType::LineContainerType lineContainer = labelObject->GetLineContainer(); // iterate over all the lines for( lit = lineContainer.begin(); lit != lineContainer.end(); lit++ ) { const IndexType & idx = lit->GetIndex(); unsigned long length = lit->GetLength(); // update the size size += length; // update the centroid - and report the progress // first, update the axes which are not 0 for( int i=1; i maxs[i] ) { maxs[i] = idx[i]; } } // must fix the max for the axis 0 if( idx[0] + length > maxs[0] ) { maxs[0] = idx[0] + length - 1; } // object is on a border ? bool isOnBorder = false; for( int i=1; i 1 ) { // we can check for the end of the line if( idx[0] + length - 1 == borderMax[0] ) { // one more pixel on the border sizeOnBorder++; } } } // moments computation // **************************************************************** // that commented code is the basic implementation. The next peace of code // give the same result in a much efficient way, by using expended formulae // allowed by the binary case instead of loops. // **************************************************************** // long endIdx0 = idx[0] + length; // for( IndexType iidx = idx; iidx[0]TransformIndexToPhysicalPoint(iidx, pP); // // for(unsigned int i=0; iTransformIndexToPhysicalPoint( idx, physicalPosition ); const typename ImageType::SpacingType & spacing = output->GetSpacing(); // the sum of x positions, also reused several times double sumX = physicalPosition[0] * length + spacing[0] * ( ( length * ( length - 1 ) ) / 2.0 ); // the real job - the sum of square of x positions // that's the central moments for dims 0, 0 centralMoments[0][0] += length * physicalPosition[0] * physicalPosition[0] + spacing[0] * spacing[0] * ( ( length - 1) * length * ( 2 * length - 1 ) / 6.0 ) + 2 * physicalPosition[0] * ( ( length * ( length - 1 ) ) / 2.0 ); // the other ones for( int i=1; i the tests should be in 3D at least double cm = length * physicalPosition[i] * physicalPosition[j]; centralMoments[i][j] += cm; centralMoments[j][i] += cm; } // the last moments: the ones for the dimension 0 double cm = sumX * physicalPosition[i]; centralMoments[i][0] += cm; centralMoments[0][i] += cm; } } // final computation typename LabelObjectType::RegionType::SizeType regionSize; double minSize = NumericTraits< double >::max(); double maxSize = NumericTraits< double >::NonpositiveMin(); for( int i=0; iGetSpacing()[i]; minSize = std::min( s, minSize ); maxSize = std::max( s, maxSize ); for(unsigned int j=0; jTransformContinuousIndexToPhysicalPoint( centroid, physicalCentroid ); // Center the second order moments for(unsigned int i=0; i eigen( centralMoments.GetVnlMatrix() ); vnl_diag_matrix pm = eigen.D; for(unsigned int i=0; i > eigenval = eigenrot.D; vcl_complex det( 1.0, 0.0 ); for(unsigned int i=0; i::max(); double maxPrincipalMoment = NumericTraits< double >::NonpositiveMin(); for( int i=0; iSetSize( size ); labelObject->SetPhysicalSize( size * sizePerPixel ); labelObject->SetRegion( region ); labelObject->SetCentroid( centroid ); labelObject->SetRegionElongation( maxSize / minSize ); labelObject->SetSizeRegionRatio( size / (double)region.GetNumberOfPixels() ); labelObject->SetSizeOnBorder( sizeOnBorder ); labelObject->SetBinaryPrincipalMoments( principalMoments ); labelObject->SetBinaryPrincipalAxes( principalAxes ); labelObject->SetBinaryElongation( elongation ); if( m_ComputeFeretDiameter ) { const PixelType & label = labelObject->GetLabel(); // init the vars unsigned long size = 0; typedef typename std::deque< IndexType > IndexListType; IndexListType idxList; // the iterators typename LabelObjectType::LineContainerType::const_iterator lit; typename LabelObjectType::LineContainerType lineContainer = labelObject->GetLineContainer(); typedef typename itk::ConstNeighborhoodIterator< LabelImageType > NeighborIteratorType; SizeType neighborHoodRadius; neighborHoodRadius.Fill( 1 ); NeighborIteratorType it( neighborHoodRadius, m_LabelImage, m_LabelImage->GetBufferedRegion() ); ConstantBoundaryCondition lcbc; // use label + 1 to have a label different of the current label on the border lcbc.SetConstant( label + 1 ); it.OverrideBoundaryCondition( &lcbc ); it.GoToBegin(); // iterate over all the lines for( lit = lineContainer.begin(); lit != lineContainer.end(); lit++ ) { const IndexType & firstIdx = lit->GetIndex(); unsigned long length = lit->GetLength(); long endIdx0 = firstIdx[0] + length; for( IndexType idx = firstIdx; idx[0]operator[]( i ) - iIt2->operator[]( i ) ) * output->GetSpacing()[i], 2 ); } if( feretDiameter < length ) { feretDiameter = length; } } } // final computation feretDiameter = vcl_sqrt( feretDiameter ); // finally put the values in the label object labelObject->SetFeretDiameter( feretDiameter ); } // be sure tha the calculator has the perimeter estimation for that label. // The calculator may not have the label if the object is only on a border. // It will occurre for sure when processing a 2D image with a 3D filter. if( m_ComputePerimeter && m_PerimeterCalculator->HasLabel( label ) ) { double perimeter = m_PerimeterCalculator->GetPerimeter( label ); double expectedRadius = hyperSphereRadiusFromVolume( labelObject->GetPhysicalSize() ); // std::cout << "expectedRadius: " << expectedRadius << std::endl; double expectedArea = hyperSphereArea( expectedRadius ); // std::cout << "expectedArea: " << expectedArea << std::endl; labelObject->SetPerimeter( perimeter ); labelObject->SetRoundness( expectedArea / perimeter ); } // std::cout << std::endl; // labelObject->Print( std::cout ); // std::cout << std::endl; } template void ShapeLabelCollectionImageFilter ::AfterThreadedGenerateData() { Superclass::AfterThreadedGenerateData(); // release the label image m_LabelImage = NULL; // and the perimeter calculator m_PerimeterCalculator = NULL; } template void ShapeLabelCollectionImageFilter ::PrintSelf(std::ostream& os, Indent indent) const { Superclass::PrintSelf(os,indent); os << indent << "ComputeFeretDiameter: " << m_ComputeFeretDiameter << std::endl; os << indent << "ComputePerimeter: " << m_ComputePerimeter << std::endl; } template long ShapeLabelCollectionImageFilter ::factorial( long n ) { if( n < 1 ) { return 1; } return n * factorial( n - 1 ); } template long ShapeLabelCollectionImageFilter ::doubleFactorial( long n ) { if( n < 2 ) { return 2; } return n * doubleFactorial( n - 2 ); } template double ShapeLabelCollectionImageFilter ::gammaN2p1( long n ) { bool even = ImageDimension % 2 == 1; if( even ) { return factorial( n / 2 ); } else { return vcl_sqrt( PI ) * doubleFactorial( n ) / vcl_pow( 2, ( n + 1 ) / 2.0 ); } } template double ShapeLabelCollectionImageFilter ::hyperSphereVolume( double radius ) { return vcl_pow( PI, ImageDimension / 2.0 ) * vcl_pow( radius, ImageDimension ) / gammaN2p1( ImageDimension ); } template double ShapeLabelCollectionImageFilter ::hyperSphereArea( double radius ) { return ImageDimension * hyperSphereVolume( radius ) / radius; } template double ShapeLabelCollectionImageFilter ::hyperSphereRadiusFromVolume( double volume ) { return vcl_pow( volume * gammaN2p1( ImageDimension ) / vcl_pow( PI, ImageDimension / 2.0 ), 1.0 / ImageDimension ); } }// end namespace itk #endif