/*========================================================================= * * 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 itkImageToImageMetricv4_hxx #define itkImageToImageMetricv4_hxx #include "itkPixelTraits.h" #include "itkDisplacementFieldTransform.h" #include "itkCompositeTransform.h" #include "itkLinearInterpolateImageFunction.h" #include "itkIdentityTransform.h" namespace itk { template ImageToImageMetricv4:: ImageToImageMetricv4() { /* Interpolators. Default to linear. */ using FixedLinearInterpolatorType = LinearInterpolateImageFunction; using MovingLinearInterpolatorType = LinearInterpolateImageFunction; this->m_FixedInterpolator = FixedLinearInterpolatorType::New(); this->m_MovingInterpolator = MovingLinearInterpolatorType::New(); /* Setup default gradient filter. It gets initialized with default * parameters during Initialize. */ this->m_DefaultFixedImageGradientFilter = DefaultFixedImageGradientFilter::New(); this->m_DefaultMovingImageGradientFilter = DefaultMovingImageGradientFilter::New(); this->m_FixedImageGradientFilter = this->m_DefaultFixedImageGradientFilter; this->m_MovingImageGradientFilter = this->m_DefaultMovingImageGradientFilter; /* Interpolators for image gradient filters */ this->m_FixedImageGradientInterpolator = FixedImageGradientInterpolatorType::New(); this->m_MovingImageGradientInterpolator = MovingImageGradientInterpolatorType::New(); /* Setup default gradient image function */ this->m_DefaultFixedImageGradientCalculator = DefaultFixedImageGradientCalculator::New(); this->m_DefaultFixedImageGradientCalculator->UseImageDirectionOn(); this->m_FixedImageGradientCalculator = this->m_DefaultFixedImageGradientCalculator; this->m_DefaultMovingImageGradientCalculator = DefaultMovingImageGradientCalculator::New(); this->m_DefaultMovingImageGradientCalculator->UseImageDirectionOn(); this->m_MovingImageGradientCalculator = this->m_DefaultMovingImageGradientCalculator; /* Setup default options assuming dense-sampling */ this->m_UseFixedImageGradientFilter = true; this->m_UseMovingImageGradientFilter = true; this->m_UseSampledPointSet = false; this->m_UseVirtualSampledPointSet = false; this->m_FloatingPointCorrectionResolution = 1e6; this->m_UseFloatingPointCorrection = false; this->m_HaveMadeGetValueWarning = false; this->m_NumberOfSkippedFixedSampledPoints = 0; this->m_Value = NumericTraits::max(); this->m_DerivativeResult = nullptr; this->m_ComputeDerivative = false; } template void ImageToImageMetricv4:: Initialize() { itkDebugMacro("Initialize entered"); /* Verify things are connected */ if (this->m_FixedImage.IsNull()) { itkExceptionMacro("FixedImage is not present"); } if (this->m_MovingImage.IsNull()) { itkExceptionMacro("MovingImage is not present"); } if (this->m_FixedTransform.IsNull()) { itkExceptionMacro("FixedTransform is not present"); } if (this->m_MovingTransform.IsNull()) { itkExceptionMacro("MovingTransform is not present"); } // If the image is provided by a source, update the source. this->m_MovingImage->UpdateSource(); // If the image is provided by a source, update the source. this->m_FixedImage->UpdateSource(); /* If a virtual image has not been set or created, * create one from fixed image settings */ if (!this->m_UserHasSetVirtualDomain) { /* Instantiate a virtual image, but do not call Allocate to allocate * the data, to save memory. We don't need data. We'll simply be iterating * over the image to get indices and transform to points. * Note that it will be safer to have a dedicated VirtualImage class * that prevents accidental access of data. */ /* Just copy information from fixed image */ VirtualImagePointer image = VirtualImageType::New(); image->CopyInformation(this->m_FixedImage); /* CopyInformation does not copy buffered region */ image->SetBufferedRegion(this->m_FixedImage->GetBufferedRegion()); image->SetRequestedRegion(this->m_FixedImage->GetRequestedRegion()); this->SetVirtualDomainFromImage(image); } /* * Superclass Initialize. * Requires the above actions to already have been taken. */ Superclass::Initialize(); /* Map the fixed samples into the virtual domain and store in * a separate point set. */ if (this->m_UseSampledPointSet && !this->m_UseVirtualSampledPointSet) { this->MapFixedSampledPointSetToVirtual(); } /* Initialize interpolators. */ itkDebugMacro("Initialize Interpolators"); this->m_FixedInterpolator->SetInputImage(this->m_FixedImage); this->m_MovingInterpolator->SetInputImage(this->m_MovingImage); /* Setup for image gradient calculations. */ if (!this->m_UseFixedImageGradientFilter) { itkDebugMacro("Initialize FixedImageGradientCalculator"); this->m_FixedImageGradientImage = nullptr; this->m_FixedImageGradientCalculator->SetInputImage(this->m_FixedImage); } if (!this->m_UseMovingImageGradientFilter) { itkDebugMacro("Initialize MovingImageGradientCalculator"); this->m_MovingImageGradientImage = nullptr; this->m_MovingImageGradientCalculator->SetInputImage(this->m_MovingImage); } /* Initialize default gradient image filters. */ itkDebugMacro("InitializeDefaultFixedImageGradientFilter"); this->InitializeDefaultFixedImageGradientFilter(); itkDebugMacro("InitializeDefaultMovingImageGradientFilter"); this->InitializeDefaultMovingImageGradientFilter(); /* If user set to use a pre-calculated fixed gradient image, * and the metric is set to use fixed image gradients, * then we need to calculate the gradient image. * We only need to compute once. */ if (this->GetGradientSourceIncludesFixed() && this->m_UseFixedImageGradientFilter) { itkDebugMacro("Initialize: ComputeFixedImageGradientFilterImage"); this->ComputeFixedImageGradientFilterImage(); } /* Compute gradient image for moving image. */ if (this->GetGradientSourceIncludesMoving() && this->m_UseMovingImageGradientFilter) { itkDebugMacro("Initialize: ComputeMovingImageGradientFilterImage"); this->ComputeMovingImageGradientFilterImage(); } } template typename ImageToImageMetricv4:: MeasureType ImageToImageMetricv4:: GetValue() const { this->m_ComputeDerivative = false; DerivativeType local_derivative; this->m_DerivativeResult = &local_derivative; this->InitializeForIteration(); // Do the threaded processing using the appropriate // GetValueAndDerivativeThreader. Results get written to // member vars. this->GetValueAndDerivativeExecute(); this->m_DerivativeResult = nullptr; // Pointer to temporary local_derivative is invalid after function return return this->m_Value; } template void ImageToImageMetricv4:: GetDerivative(DerivativeType & derivative) const { MeasureType value; this->GetValueAndDerivative(value, derivative); } template void ImageToImageMetricv4:: GetValueAndDerivative(MeasureType & value, DerivativeType & derivative) const { this->m_ComputeDerivative = true; this->m_DerivativeResult = &derivative; this->InitializeForIteration(); // Do the threaded processing using the appropriate // GetValueAndDerivativeThreader. Results get written to // member vars. this->GetValueAndDerivativeExecute(); value = this->m_Value; } template void ImageToImageMetricv4:: GetValueAndDerivativeExecute() const { if (this->m_UseSampledPointSet) // sparse sampling { SizeValueType numberOfPoints = this->GetNumberOfDomainPoints(); if (numberOfPoints < 1) { itkExceptionMacro("VirtualSampledPointSet must have 1 or more points."); } typename ImageToImageMetricv4GetValueAndDerivativeThreader::DomainType range; range[0] = 0; range[1] = numberOfPoints - 1; this->m_SparseGetValueAndDerivativeThreader->Execute(const_cast(this), range); } else // dense sampling { this->m_DenseGetValueAndDerivativeThreader->Execute(const_cast(this), this->GetVirtualRegion()); } } template void ImageToImageMetricv4:: InitializeForIteration() const { if (this->m_ComputeDerivative) { /* This size always comes from the active transform */ const NumberOfParametersType globalDerivativeSize = this->GetNumberOfParameters(); if (this->m_DerivativeResult->GetSize() != globalDerivativeSize) { this->m_DerivativeResult->SetSize(globalDerivativeSize); } /* Clear derivative final result. */ this->m_DerivativeResult->Fill(DerivativeValueType{}); } } template bool ImageToImageMetricv4:: TransformAndEvaluateFixedPoint(const VirtualPointType & virtualPoint, FixedImagePointType & mappedFixedPoint, FixedImagePixelType & mappedFixedPixelValue) const { bool pointIsValid = true; mappedFixedPixelValue = FixedImagePixelType{}; // map the point into fixed space this->LocalTransformPoint(virtualPoint, mappedFixedPoint); // check against the mask if one is assigned if (this->m_FixedImageMask) { // Check if mapped point is within the support region of the fixed image // mask pointIsValid = this->m_FixedImageMask->IsInsideInWorldSpace(mappedFixedPoint); if (!pointIsValid) { return pointIsValid; } } // Check if mapped point is inside image buffer pointIsValid = this->m_FixedInterpolator->IsInsideBuffer(mappedFixedPoint); // Evaluate if (pointIsValid) { mappedFixedPixelValue = this->m_FixedInterpolator->Evaluate(mappedFixedPoint); } return pointIsValid; } template bool ImageToImageMetricv4:: TransformAndEvaluateMovingPoint(const VirtualPointType & virtualPoint, MovingImagePointType & mappedMovingPoint, MovingImagePixelType & mappedMovingPixelValue) const { bool pointIsValid = true; mappedMovingPixelValue = MovingImagePixelType{}; // map the point into moving space // Before transforming points, we should convert their types from the ImagePointType (aka Point) // to TransformPointType (aka Point). typename MovingTransformType::OutputPointType localVirtualPoint; typename MovingTransformType::OutputPointType localMappedMovingPoint; localVirtualPoint.CastFrom(virtualPoint); localMappedMovingPoint.CastFrom(mappedMovingPoint); localMappedMovingPoint = this->m_MovingTransform->TransformPoint(localVirtualPoint); mappedMovingPoint.CastFrom(localMappedMovingPoint); // check against the mask if one is assigned if (this->m_MovingImageMask) { // Check if mapped point is within the support region of the fixed image // mask pointIsValid = this->m_MovingImageMask->IsInsideInWorldSpace(mappedMovingPoint); if (!pointIsValid) { return pointIsValid; } } // Check if mapped point is inside image buffer pointIsValid = this->m_MovingInterpolator->IsInsideBuffer(mappedMovingPoint); // Evaluate if (pointIsValid) { mappedMovingPixelValue = this->m_MovingInterpolator->Evaluate(mappedMovingPoint); } return pointIsValid; } template void ImageToImageMetricv4:: ComputeFixedImageGradientAtPoint(const FixedImagePointType & mappedPoint, FixedImageGradientType & gradient) const { if (this->m_UseFixedImageGradientFilter) { if (!this->GetGradientSourceIncludesFixed()) { itkExceptionMacro( "Attempted to retrieve fixed image gradient from gradient image filter, " "but GradientSource does not include 'fixed', and thus the gradient image has not been calculated."); } gradient = m_FixedImageGradientInterpolator->Evaluate(mappedPoint); } else { // if not using the gradient image gradient = this->m_FixedImageGradientCalculator->Evaluate(mappedPoint); } } template void ImageToImageMetricv4:: ComputeMovingImageGradientAtPoint(const MovingImagePointType & mappedPoint, MovingImageGradientType & gradient) const { if (this->m_UseMovingImageGradientFilter) { if (!this->GetGradientSourceIncludesMoving()) { itkExceptionMacro( "Attempted to retrieve moving image gradient from gradient image filter, " "but GradientSource does not include 'moving', and thus the gradient image has not been calculated."); } gradient = m_MovingImageGradientInterpolator->Evaluate(mappedPoint); } else { // if not using the gradient image gradient = this->m_MovingImageGradientCalculator->Evaluate(mappedPoint); } } template void ImageToImageMetricv4:: ComputeFixedImageGradientFilterImage() { this->m_FixedImageGradientFilter->SetInput(this->m_FixedImage); this->m_FixedImageGradientFilter->Update(); this->m_FixedImageGradientImage = this->m_FixedImageGradientFilter->GetOutput(); this->m_FixedImageGradientInterpolator->SetInputImage(this->m_FixedImageGradientImage); } template void ImageToImageMetricv4:: ComputeMovingImageGradientFilterImage() const { this->m_MovingImageGradientFilter->SetInput(this->m_MovingImage); this->m_MovingImageGradientFilter->Update(); this->m_MovingImageGradientImage = this->m_MovingImageGradientFilter->GetOutput(); this->m_MovingImageGradientInterpolator->SetInputImage(this->m_MovingImageGradientImage); } template void ImageToImageMetricv4:: InitializeDefaultFixedImageGradientFilter() { const typename FixedImageType::SpacingType & spacing = this->m_FixedImage->GetSpacing(); double maximumSpacing = 0.0; for (ImageDimensionType i = 0; i < FixedImageDimension; ++i) { if (spacing[i] > maximumSpacing) { maximumSpacing = spacing[i]; } } this->m_DefaultFixedImageGradientFilter->SetSigma(maximumSpacing); this->m_DefaultFixedImageGradientFilter->SetNormalizeAcrossScale(true); this->m_DefaultFixedImageGradientFilter->SetNumberOfWorkUnits(this->GetMaximumNumberOfWorkUnits()); this->m_DefaultFixedImageGradientFilter->SetUseImageDirection(true); } template void ImageToImageMetricv4:: InitializeDefaultMovingImageGradientFilter() { const typename MovingImageType::SpacingType & spacing = this->m_MovingImage->GetSpacing(); double maximumSpacing = 0.0; for (ImageDimensionType i = 0; i < MovingImageDimension; ++i) { if (spacing[i] > maximumSpacing) { maximumSpacing = spacing[i]; } } this->m_DefaultMovingImageGradientFilter->SetSigma(maximumSpacing); this->m_DefaultMovingImageGradientFilter->SetNormalizeAcrossScale(true); this->m_DefaultMovingImageGradientFilter->SetNumberOfWorkUnits(this->GetMaximumNumberOfWorkUnits()); this->m_DefaultMovingImageGradientFilter->SetUseImageDirection(true); } template void ImageToImageMetricv4:: SetMaximumNumberOfWorkUnits(const ThreadIdType number) { if (number != this->m_SparseGetValueAndDerivativeThreader->GetMaximumNumberOfThreads()) { this->m_SparseGetValueAndDerivativeThreader->SetMaximumNumberOfThreads(number); this->m_SparseGetValueAndDerivativeThreader->SetNumberOfWorkUnits(number); this->Modified(); } if (number != this->m_DenseGetValueAndDerivativeThreader->GetMaximumNumberOfThreads()) { this->m_DenseGetValueAndDerivativeThreader->SetMaximumNumberOfThreads(number); this->m_DenseGetValueAndDerivativeThreader->SetNumberOfWorkUnits(number); this->Modified(); } } template ThreadIdType ImageToImageMetricv4:: GetMaximumNumberOfWorkUnits() const { if (this->m_UseSampledPointSet) { return this->m_SparseGetValueAndDerivativeThreader->GetMaximumNumberOfThreads(); } return this->m_DenseGetValueAndDerivativeThreader->GetMaximumNumberOfThreads(); } template ThreadIdType ImageToImageMetricv4:: GetNumberOfWorkUnitsUsed() const { if (this->m_UseSampledPointSet) { return this->m_SparseGetValueAndDerivativeThreader->GetNumberOfWorkUnitsUsed(); } else { return this->m_DenseGetValueAndDerivativeThreader->GetNumberOfWorkUnitsUsed(); } } template void ImageToImageMetricv4:: MapFixedSampledPointSetToVirtual() { this->m_VirtualSampledPointSet = VirtualPointSetType::New(); this->m_VirtualSampledPointSet->Initialize(); using PointsContainer = typename FixedSampledPointSetType::PointsContainer; typename PointsContainer::ConstPointer points = this->m_FixedSampledPointSet->GetPoints(); if (points.IsNull()) { itkExceptionMacro("Fixed Sample point set is empty."); } typename PointsContainer::ConstIterator fixedIt = points->Begin(); typename FixedTransformType::InverseTransformBasePointer inverseTransform = this->m_FixedTransform->GetInverseTransform(); if (inverseTransform.IsNull()) { itkExceptionMacro("Unable to get inverse transform for mapping sampled " " point set."); } this->m_NumberOfSkippedFixedSampledPoints = 0; SizeValueType virtualIndex = 0; while (fixedIt != points->End()) { typename FixedSampledPointSetType::PointType point = inverseTransform->TransformPoint(fixedIt.Value()); typename VirtualImageType::IndexType tempIndex; /* Verify that the point is valid. We may be working with a resized virtual domain, * and a fixed sampled point list that was created before the resizing. */ if (this->TransformPhysicalPointToVirtualIndex(point, tempIndex)) { this->m_VirtualSampledPointSet->SetPoint(virtualIndex, point); ++virtualIndex; } else { this->m_NumberOfSkippedFixedSampledPoints++; } ++fixedIt; } if (this->m_VirtualSampledPointSet->GetNumberOfPoints() == 0) { itkExceptionMacro("The virtual sampled point set has zero points because " "no fixed sampled points were within the virtual " "domain after mapping. There are no points to evaulate."); } } template SizeValueType ImageToImageMetricv4:: GetNumberOfDomainPoints() const { if (this->m_UseSampledPointSet) { // The virtual sampled point set holds the actual points // over which we're evaluating over. return this->m_VirtualSampledPointSet->GetNumberOfPoints(); } else { typename VirtualImageType::RegionType region = this->GetVirtualRegion(); return region.GetNumberOfPixels(); } } template void ImageToImageMetricv4::PrintSelf( std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); os << indent << "ImageToImageMetricv4: " << std::endl << indent << "GetUseFixedImageGradientFilter: " << this->GetUseFixedImageGradientFilter() << std::endl << indent << "GetUseMovingImageGradientFilter: " << this->GetUseMovingImageGradientFilter() << std::endl << indent << "UseFloatingPointCorrection: " << this->GetUseFloatingPointCorrection() << std::endl << indent << "FloatingPointCorrectionResolution: " << this->GetFloatingPointCorrectionResolution() << std::endl; itkPrintSelfObjectMacro(FixedImage); itkPrintSelfObjectMacro(MovingImage); itkPrintSelfObjectMacro(FixedTransform); itkPrintSelfObjectMacro(MovingTransform); itkPrintSelfObjectMacro(FixedImageMask); itkPrintSelfObjectMacro(MovingImageMask); } } // namespace itk #endif