/*========================================================================= * * 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 itkJointHistogramMutualInformationImageToImageMetricv4_hxx #define itkJointHistogramMutualInformationImageToImageMetricv4_hxx #include "itkCompensatedSummation.h" #include "itkImageIterator.h" #include "itkDiscreteGaussianImageFilter.h" namespace itk { template JointHistogramMutualInformationImageToImageMetricv4< TFixedImage, TMovingImage, TVirtualImage, TInternalComputationValueType, TMetricTraits>::JointHistogramMutualInformationImageToImageMetricv4() { // Initialize histogram properties this->m_NumberOfHistogramBins = 20; this->m_FixedImageTrueMin = TInternalComputationValueType{}; this->m_FixedImageTrueMax = TInternalComputationValueType{}; this->m_MovingImageTrueMin = TInternalComputationValueType{}; this->m_MovingImageTrueMax = TInternalComputationValueType{}; this->m_FixedImageBinSize = TInternalComputationValueType{}; this->m_MovingImageBinSize = TInternalComputationValueType{}; this->m_Padding = 2; this->m_JointPDFSum = TInternalComputationValueType{}; this->m_Log2 = std::log(2.0); this->m_VarianceForJointPDFSmoothing = 1.5; // We have our own GetValueAndDerivativeThreader's that we want // ImageToImageMetricv4 to use. this->m_DenseGetValueAndDerivativeThreader = JointHistogramMutualInformationDenseGetValueAndDerivativeThreaderType::New(); this->m_SparseGetValueAndDerivativeThreader = JointHistogramMutualInformationSparseGetValueAndDerivativeThreaderType::New(); this->m_JointHistogramMutualInformationDenseComputeJointPDFThreader = JointHistogramMutualInformationDenseComputeJointPDFThreaderType::New(); this->m_JointHistogramMutualInformationSparseComputeJointPDFThreader = JointHistogramMutualInformationSparseComputeJointPDFThreaderType::New(); this->m_JointPDF = JointPDFType::New(); } template void JointHistogramMutualInformationImageToImageMetricv4::Initialize() { Superclass::Initialize(); /** Get the fixed and moving image true max's and mins. * Initialize them to the PixelType min and max. */ this->m_FixedImageTrueMin = NumericTraits::max(); this->m_FixedImageTrueMax = NumericTraits::NonpositiveMin(); this->m_MovingImageTrueMin = NumericTraits::max(); this->m_MovingImageTrueMax = NumericTraits::NonpositiveMin(); /** Iterate through the fixed image and set the true * max and min for the fixed image. */ itk::ImageRegionConstIteratorWithIndex fi(this->m_FixedImage, this->m_FixedImage->GetRequestedRegion()); /** \todo multi-thread me */ while (!fi.IsAtEnd()) { typename TFixedImage::PointType fixedSpacePhysicalPoint; this->m_FixedImage->TransformIndexToPhysicalPoint(fi.GetIndex(), fixedSpacePhysicalPoint); /* A null mask implies entire space is to be used.*/ if (this->m_FixedImageMask.IsNull() || this->m_FixedImageMask->IsInsideInWorldSpace(fixedSpacePhysicalPoint)) { const typename TFixedImage::PixelType currentValue = fi.Get(); // update the Fixed Image true min accordingly if (currentValue < this->m_FixedImageTrueMin) { this->m_FixedImageTrueMin = currentValue; } // update the Fixed Image true max accordingly if (currentValue > this->m_FixedImageTrueMax) { this->m_FixedImageTrueMax = currentValue; } } ++fi; } /** Iterate through the moving image and set the true * max and min for the moving image. */ itk::ImageRegionConstIteratorWithIndex mi(this->m_MovingImage, this->m_MovingImage->GetBufferedRegion()); while (!mi.IsAtEnd()) { typename TMovingImage::PointType movingSpacePhysicalPoint; this->m_MovingImage->TransformIndexToPhysicalPoint(mi.GetIndex(), movingSpacePhysicalPoint); /* A null mask implies entire space is to be used.*/ if (this->m_MovingImageMask.IsNull() || this->m_MovingImageMask->IsInsideInWorldSpace(movingSpacePhysicalPoint)) { const typename TMovingImage::PixelType currentValue = mi.Get(); // update the Moving Image true min accordingly if (currentValue < this->m_MovingImageTrueMin) { this->m_MovingImageTrueMin = currentValue; } // update the Moving Image true max accordingly if (currentValue > this->m_MovingImageTrueMax) { this->m_MovingImageTrueMax = currentValue; } } ++mi; } itkDebugMacro(" FixedImageMin: " << this->m_FixedImageTrueMin << " FixedImageMax: " << this->m_FixedImageTrueMax << std::endl); itkDebugMacro(" MovingImageMin: " << this->m_MovingImageTrueMin << " MovingImageMax: " << this->m_MovingImageTrueMax << std::endl); // Allocate memory for the joint PDF. // Instantiate a region // the jointPDF is of size NumberOfBins x NumberOfBins const JointPDFRegionType jointPDFRegion(JointPDFSizeType::Filled(m_NumberOfHistogramBins)); // Set the regions and allocate this->m_JointPDF->SetRegions(jointPDFRegion); // By setting these values, the joint histogram physical locations will correspond to intensity values. JointPDFSpacingType spacing; spacing[0] = 1 / (TInternalComputationValueType)(this->m_NumberOfHistogramBins - (TInternalComputationValueType)this->m_Padding * 2 - 1); spacing[1] = spacing[0]; this->m_JointPDF->SetSpacing(spacing); this->m_JointPDFSpacing = this->m_JointPDF->GetSpacing(); JointPDFPointType origin; origin[0] = this->m_JointPDFSpacing[0] * (TInternalComputationValueType)this->m_Padding * (-1.0); origin[1] = origin[0]; this->m_JointPDF->SetOrigin(origin); this->m_JointPDF->Allocate(); // do the same thing for the marginal pdfs this->m_FixedImageMarginalPDF = MarginalPDFType::New(); this->m_MovingImageMarginalPDF = MarginalPDFType::New(); // Instantiate a region using MarginalPDFRegionType = typename MarginalPDFType::RegionType; using MarginalPDFSizeType = typename MarginalPDFType::SizeType; // the marginalPDF is of size NumberOfBins x NumberOfBins const MarginalPDFRegionType marginalPDFRegion(MarginalPDFSizeType::Filled(m_NumberOfHistogramBins)); // Set the regions and allocate this->m_FixedImageMarginalPDF->SetRegions(marginalPDFRegion); this->m_MovingImageMarginalPDF->SetRegions(marginalPDFRegion); // By setting these values, the marginal histogram physical locations will correspond to intensity values. typename MarginalPDFType::PointType fixedorigin; typename MarginalPDFType::PointType movingorigin; fixedorigin[0] = origin[0]; movingorigin[0] = origin[1]; this->m_FixedImageMarginalPDF->SetOrigin(fixedorigin); this->m_MovingImageMarginalPDF->SetOrigin(movingorigin); typename MarginalPDFType::SpacingType mspacing; mspacing[0] = spacing[0]; this->m_FixedImageMarginalPDF->SetSpacing(mspacing); mspacing[0] = spacing[1]; this->m_MovingImageMarginalPDF->SetSpacing(mspacing); this->m_FixedImageMarginalPDF->Allocate(); this->m_MovingImageMarginalPDF->Allocate(); } template void JointHistogramMutualInformationImageToImageMetricv4::InitializeForIteration() const { Superclass::InitializeForIteration(); /* Prepare histograms for use in GetValueAndDerivative */ // Initialize the joint pdf and the fixed and moving image marginal pdfs PDFValueType pdfzero{}; this->m_JointPDF->FillBuffer(pdfzero); this->m_FixedImageMarginalPDF->FillBuffer(pdfzero); this->m_MovingImageMarginalPDF->FillBuffer(pdfzero); /** * First, we compute the joint histogram */ if (this->m_UseSampledPointSet) { SizeValueType numberOfPoints = this->GetNumberOfDomainPoints(); if (numberOfPoints < 1) { itkExceptionMacro("VirtualSampledPointSet must have 1 or more points."); } typename JointHistogramMutualInformationSparseComputeJointPDFThreaderType::DomainType sampledRange; sampledRange[0] = 0; sampledRange[1] = numberOfPoints - 1; this->m_JointHistogramMutualInformationSparseComputeJointPDFThreader->Execute(const_cast(this), sampledRange); } else { this->m_JointHistogramMutualInformationDenseComputeJointPDFThreader->Execute(const_cast(this), this->GetVirtualRegion()); } // Optionally smooth the joint pdf if (this->m_VarianceForJointPDFSmoothing > JointPDFValueType{}) { using DgType = DiscreteGaussianImageFilter; auto dg = DgType::New(); dg->SetInput(this->m_JointPDF); dg->SetVariance(this->m_VarianceForJointPDFSmoothing); dg->UseImageSpacingOff(); dg->SetMaximumError(.01f); dg->Update(); this->m_JointPDF = (dg->GetOutput()); } // Compute moving image marginal PDF by summing over fixed image bins. using JointPDFLinearIterator = ImageLinearIteratorWithIndex; JointPDFLinearIterator linearIter(m_JointPDF, m_JointPDF->GetBufferedRegion()); linearIter.SetDirection(0); linearIter.GoToBegin(); unsigned int fixedIndex = 0; CompensatedSummation sum; while (!linearIter.IsAtEnd()) { sum.ResetToZero(); while (!linearIter.IsAtEndOfLine()) { sum += linearIter.Get(); ++linearIter; } MarginalPDFIndexType mind; mind[0] = fixedIndex; m_FixedImageMarginalPDF->SetPixel(mind, static_cast(sum.GetSum())); linearIter.NextLine(); ++fixedIndex; } linearIter.SetDirection(1); linearIter.GoToBegin(); unsigned int movingIndex = 0; while (!linearIter.IsAtEnd()) { sum.ResetToZero(); while (!linearIter.IsAtEndOfLine()) { sum += linearIter.Get(); ++linearIter; } MarginalPDFIndexType mind; mind[0] = movingIndex; m_MovingImageMarginalPDF->SetPixel(mind, static_cast(sum.GetSum())); linearIter.NextLine(); ++movingIndex; } } template typename JointHistogramMutualInformationImageToImageMetricv4::MeasureType JointHistogramMutualInformationImageToImageMetricv4::GetValue() const { DerivativeType dummyDeriviative; this->m_DerivativeResult = &dummyDeriviative; this->InitializeForIteration(); this->m_NumberOfValidPoints = this->m_JointHistogramTotalCount; MeasureType value; /* This checks for the default minimum number of valid points. * It returns true if minimum is met, otherwise is put default * values into value and dummyDerivative and returns false. */ if (this->VerifyNumberOfValidPoints(value, dummyDeriviative)) { this->m_Value = this->ComputeValue(); } return this->m_Value; } template typename JointHistogramMutualInformationImageToImageMetricv4::MeasureType JointHistogramMutualInformationImageToImageMetricv4::ComputeValue() const { /** 1- The padding is 2 in this implementation. 2- The MI energy is bounded in the range of [0 min(H(x),H(y))]. 3- The ComputeMutualInformation() iterator range should cover the entire PDF. 4- The normalization is done based on NumberOfHistogramBins-1 instead of NumberOfHistogramBins. */ TInternalComputationValueType px, py, pxy; CompensatedSummation total_mi; TInternalComputationValueType local_mi; TInternalComputationValueType eps = NumericTraits::epsilon(); typename JointPDFType::IndexType index; for (SizeValueType ii = 0; ii < m_NumberOfHistogramBins; ++ii) { MarginalPDFIndexType mind; mind[0] = ii; px = this->m_FixedImageMarginalPDF->GetPixel(mind); for (SizeValueType jj = 0; jj < m_NumberOfHistogramBins; ++jj) { mind[0] = jj; py = this->m_MovingImageMarginalPDF->GetPixel(mind); TInternalComputationValueType denom = px * py; index[0] = ii; index[1] = jj; pxy = m_JointPDF->GetPixel(index); local_mi = 0; if (itk::Math::abs(denom) > eps) { if (pxy / denom > eps) { // the classic mi calculation local_mi = pxy * std::log(pxy / denom); } } total_mi += local_mi; } // over jh bins 2 } // over jh bins 1 return (-1.0 * total_mi.GetSum() / this->m_Log2); } template void JointHistogramMutualInformationImageToImageMetricv4< TFixedImage, TMovingImage, TVirtualImage, TInternalComputationValueType, TMetricTraits>::ComputeJointPDFPoint(const FixedImagePixelType fixedImageValue, const MovingImagePixelType movingImageValue, JointPDFPointType & jointPDFpoint) const { TInternalComputationValueType a = (fixedImageValue - this->m_FixedImageTrueMin) / (this->m_FixedImageTrueMax - this->m_FixedImageTrueMin); TInternalComputationValueType b = (movingImageValue - this->m_MovingImageTrueMin) / (this->m_MovingImageTrueMax - this->m_MovingImageTrueMin); jointPDFpoint[0] = a; jointPDFpoint[1] = b; } template void JointHistogramMutualInformationImageToImageMetricv4::PrintSelf(std::ostream & os, Indent indent) const { // Print the superclass Superclass::PrintSelf(os, indent); os << indent << "NumberOfHistogramBins: "; os << this->m_NumberOfHistogramBins << std::endl; os << indent << "MovingImageTrueMin: "; os << this->m_MovingImageTrueMin << std::endl; os << indent << "MovingImageTrueMax: "; os << this->m_MovingImageTrueMax << std::endl; os << indent << "FixedImageBinSize: "; os << this->m_FixedImageBinSize << std::endl; os << indent << "MovingImageBinSize: "; os << this->m_MovingImageBinSize << std::endl; if (this->m_JointPDF.IsNotNull()) { os << indent << "JointPDF: "; os << this->m_JointPDF << std::endl; } } } // end namespace itk #endif