/*========================================================================= * * 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 itkImagePCAShapeModelEstimator_hxx #define itkImagePCAShapeModelEstimator_hxx #include "itkPrintHelper.h" namespace itk { template ImagePCAShapeModelEstimator::ImagePCAShapeModelEstimator() { m_EigenVectors.set_size(0, 0); m_EigenValues.set_size(0); m_NumberOfPrincipalComponentsRequired = 0; this->SetNumberOfPrincipalComponentsRequired(1); } template void ImagePCAShapeModelEstimator::PrintSelf(std::ostream & os, Indent indent) const { using namespace print_helper; Superclass::PrintSelf(os, indent); os << indent << "InputImageIteratorArray size: " << m_InputImageIteratorArray.size() << std::endl; os << indent << "Means: " << m_Means << std::endl; os << indent << "InnerProduct: " << m_InnerProduct << std::endl; os << indent << "EigenVectors: " << m_EigenVectors << std::endl; os << indent << "EigenValues: " << m_EigenValues << std::endl; os << indent << "EigenVectorNormalizedEnergy: " << m_EigenVectorNormalizedEnergy << std::endl; os << indent << "InputImageSize: " << static_cast::PrintType>(m_InputImageSize) << std::endl; os << indent << "NumberOfPixels: " << m_NumberOfPixels << std::endl; os << indent << "NumberOfTrainingImages: " << m_NumberOfTrainingImages << std::endl; os << indent << "NumberOfPrincipalComponentsRequired: " << m_NumberOfPrincipalComponentsRequired << std::endl; } template void ImagePCAShapeModelEstimator::EnlargeOutputRequestedRegion(DataObject * itkNotUsed(output)) { // Require all of the output images to be in the buffer for (unsigned int idx = 0; idx < this->GetNumberOfIndexedOutputs(); ++idx) { if (this->GetOutput(idx)) { this->GetOutput(idx)->SetRequestedRegionToLargestPossibleRegion(); } } } template void ImagePCAShapeModelEstimator::GenerateInputRequestedRegion() { Superclass::GenerateInputRequestedRegion(); // Require all of the inputs to be in the buffer up to the // LargestPossibleRegion of the first input. if (this->GetInput(0)) { // Set the requested region of the first input to largest possible region InputImagePointer input = const_cast(this->GetInput(0)); input->SetRequestedRegionToLargestPossibleRegion(); // Set the requested region of the remaining input to the largest possible // region of the first input unsigned int idx; for (idx = 1; idx < this->GetNumberOfIndexedInputs(); ++idx) { if (this->GetInput(idx)) { typename TInputImage::RegionType requestedRegion = this->GetInput(0)->GetLargestPossibleRegion(); typename TInputImage::RegionType largestRegion = this->GetInput(idx)->GetLargestPossibleRegion(); if (!largestRegion.IsInside(requestedRegion)) { itkExceptionMacro("LargestPossibleRegion of input " << idx << " is not a superset of the LargestPossibleRegion of input 0"); } InputImagePointer ptr = const_cast(this->GetInput(idx)); ptr->SetRequestedRegion(requestedRegion); } } } } template void ImagePCAShapeModelEstimator::GenerateData() { this->EstimateShapeModels(); // Allocate memory for each output. auto numberOfOutputs = static_cast(this->GetNumberOfIndexedOutputs()); InputImagePointer input = const_cast(this->GetInput(0)); unsigned int j; for (j = 0; j < numberOfOutputs; ++j) { OutputImagePointer output = this->GetOutput(j); output->SetBufferedRegion(output->GetRequestedRegion()); output->Allocate(); } // Fill the output images. VectorOfDoubleType m_OneEigenVector; using OutputIterator = ImageRegionIterator; // Fill the mean image first typename OutputImageType::RegionType region = this->GetOutput(0)->GetRequestedRegion(); OutputIterator outIter(this->GetOutput(0), region); unsigned int i = 0; while (!outIter.IsAtEnd()) { outIter.Set(static_cast(m_Means[i])); ++outIter; ++i; } // Now fill the principal component outputs unsigned int kthLargestPrincipalComp = m_NumberOfTrainingImages; unsigned int numberOfValidOutputs = std::min(numberOfOutputs, m_NumberOfTrainingImages + 1); for (j = 1; j < numberOfValidOutputs; ++j) { // Extract one column vector at a time m_OneEigenVector = m_EigenVectors.get_column(kthLargestPrincipalComp - 1); region = this->GetOutput(j)->GetRequestedRegion(); OutputIterator outIterJ(this->GetOutput(j), region); unsigned int idx = 0; outIterJ.GoToBegin(); while (!outIterJ.IsAtEnd()) { outIterJ.Set(static_cast(m_OneEigenVector[idx])); ++outIterJ; ++idx; } // Decrement to get the next principal component --kthLargestPrincipalComp; } // Fill extraneous outputs with zero for (; j < numberOfOutputs; ++j) { region = this->GetOutput(j)->GetRequestedRegion(); OutputIterator outIterJ(this->GetOutput(j), region); outIterJ.GoToBegin(); while (!outIterJ.IsAtEnd()) { outIterJ.Set(0); ++outIterJ; } } // Delete eigenvectors at the end of generateData to free memory if (this->GetReleaseDataFlag()) { m_EigenVectors.set_size(0, 0); } } template void ImagePCAShapeModelEstimator::SetNumberOfPrincipalComponentsRequired(unsigned int n) { if (m_NumberOfPrincipalComponentsRequired != n) { m_NumberOfPrincipalComponentsRequired = n; this->Modified(); // Modify the required number of outputs ( 1 extra for the mean image ) this->SetNumberOfRequiredOutputs(m_NumberOfPrincipalComponentsRequired + 1); auto numberOfOutputs = static_cast(this->GetNumberOfIndexedOutputs()); unsigned int idx; if (numberOfOutputs < m_NumberOfPrincipalComponentsRequired + 1) { // Make and add extra outputs for (idx = numberOfOutputs; idx <= m_NumberOfPrincipalComponentsRequired; ++idx) { typename DataObject::Pointer output = this->MakeOutput(idx); this->SetNthOutput(idx, output.GetPointer()); } } else if (numberOfOutputs > m_NumberOfPrincipalComponentsRequired + 1) { // Remove the extra outputs for (idx = numberOfOutputs - 1; idx >= m_NumberOfPrincipalComponentsRequired + 1; idx--) { this->RemoveOutput(idx); } } } } template void ImagePCAShapeModelEstimator::SetNumberOfTrainingImages(unsigned int n) { if (m_NumberOfTrainingImages != n) { m_NumberOfTrainingImages = n; this->Modified(); // Modify the required number of inputs this->SetNumberOfRequiredInputs(m_NumberOfTrainingImages); } } template void ImagePCAShapeModelEstimator::EstimateShapeModels() { this->CalculateInnerProduct(); this->EstimatePCAShapeModelParameters(); } template void ImagePCAShapeModelEstimator::CalculateInnerProduct() { // Get the pointers to the input images and initialize the iterators // We use dynamic_cast since inputs are stored as DataObjects. The // ImageToImageFilter::GetInput(int) always returns a pointer to a // TInputImage1 so it cannot be used for the second input. InputImagePointerArray inputImagePointerArray(m_NumberOfTrainingImages); m_InputImageIteratorArray.resize(m_NumberOfTrainingImages); for (unsigned int i = 0; i < m_NumberOfTrainingImages; ++i) { InputImageConstPointer inputImagePtr = dynamic_cast(ProcessObject::GetInput(i)); inputImagePointerArray[i] = inputImagePtr; InputImageConstIterator inputImageIt(inputImagePtr, inputImagePtr->GetBufferedRegion()); m_InputImageIteratorArray[i] = inputImageIt; m_InputImageIteratorArray[i].GoToBegin(); } // Set up the matrix to hold the inner product and the means from the // training data m_InputImageSize = (inputImagePointerArray[0])->GetBufferedRegion().GetSize(); m_NumberOfPixels = 1; for (unsigned int i = 0; i < InputImageDimension; ++i) { m_NumberOfPixels *= m_InputImageSize[i]; } // Calculate the means m_Means.set_size(m_NumberOfPixels); m_Means.fill(0); InputImageConstIterator tempImageItA; for (unsigned int img_number = 0; img_number < m_NumberOfTrainingImages; ++img_number) { tempImageItA = m_InputImageIteratorArray[img_number]; for (unsigned int band_x = 0; band_x < m_NumberOfPixels; ++band_x) { m_Means[band_x] += tempImageItA.Get(); ++tempImageItA; } } m_Means /= m_NumberOfTrainingImages; // Calculate the inner product m_InnerProduct.set_size(m_NumberOfTrainingImages, m_NumberOfTrainingImages); m_InnerProduct.fill(0); InputImageConstIterator tempImageItB; for (unsigned int band_x = 0; band_x < m_NumberOfTrainingImages; ++band_x) { for (unsigned int band_y = 0; band_y <= band_x; ++band_y) { // Pointer to the vector (in original matrix) tempImageItA = m_InputImageIteratorArray[band_x]; // Pointer to the vector in the transposed matrix tempImageItB = m_InputImageIteratorArray[band_y]; for (unsigned int pix_number = 0; pix_number < m_NumberOfPixels; ++pix_number) { m_InnerProduct[band_x][band_y] += (tempImageItA.Get() - m_Means[pix_number]) * (tempImageItB.Get() - m_Means[pix_number]); ++tempImageItA; ++tempImageItB; } } } // Fill the rest of the inner product matrix and make it symmetric for (unsigned int band_x = 0; band_x < (m_NumberOfTrainingImages - 1); ++band_x) { for (unsigned int band_y = band_x + 1; band_y < m_NumberOfTrainingImages; ++band_y) { m_InnerProduct[band_x][band_y] = m_InnerProduct[band_y][band_x]; } } if ((m_NumberOfTrainingImages - 1) != 0) { m_InnerProduct /= (m_NumberOfTrainingImages - 1); } else { m_InnerProduct.fill(0); } } template void ImagePCAShapeModelEstimator::EstimatePCAShapeModelParameters() { MatrixOfDoubleType identityMatrix(m_NumberOfTrainingImages, m_NumberOfTrainingImages); identityMatrix.set_identity(); vnl_generalized_eigensystem eigenVectors_eigenValues(m_InnerProduct, identityMatrix); MatrixOfDoubleType eigenVectorsOfInnerProductMatrix = eigenVectors_eigenValues.V; // Calculate the principal shape variations // // m_EigenVectors capture the principal shape variations // m_EigenValues capture the relative weight of each variation // Multiply original image vectors with the eigenVectorsOfInnerProductMatrix // to derive the principal shapes. m_EigenVectors.set_size(m_NumberOfPixels, m_NumberOfTrainingImages); m_EigenVectors.fill(0); double pix_value; InputImageConstIterator tempImageItA; for (unsigned int img_number = 0; img_number < m_NumberOfTrainingImages; ++img_number) { tempImageItA = m_InputImageIteratorArray[img_number]; for (unsigned int pix_number = 0; pix_number < m_NumberOfPixels; ++pix_number) { pix_value = tempImageItA.Get(); for (unsigned int vec_number = 0; vec_number < m_NumberOfTrainingImages; ++vec_number) { m_EigenVectors[pix_number][vec_number] += (pix_value * eigenVectorsOfInnerProductMatrix[img_number][vec_number]); } ++tempImageItA; } } m_EigenVectors.normalize_columns(); m_EigenValues.set_size(m_NumberOfTrainingImages); // Extract the diagonal elements into the Eigen value vector m_EigenValues = (eigenVectors_eigenValues.D).diagonal(); // Flip the eigen values since the eigen vectors output // is ordered in descending order of their corresponding eigen values. m_EigenValues.flip(); // Normalize the eigen values m_EigenVectorNormalizedEnergy = m_EigenValues; m_EigenVectorNormalizedEnergy.normalize(); } } // namespace itk #endif