/*========================================================================= * * 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 itkBSplineTransform_hxx #define itkBSplineTransform_hxx #include "itkContinuousIndex.h" #include "itkImageScanlineConstIterator.h" #include "itkImageRegionConstIteratorWithIndex.h" namespace itk { template BSplineTransform::BSplineTransform() : Superclass() { /** Fixed Parameters store the following information: * grid size * grid origin * grid spacing * grid direction * The size of these is equal to the NInputDimensions */ // For example 3D image has FixedParameters of: // [size[0],size[1],size[2], // origin[0],origin[1],origin[2], // spacing[0],spacing[1],spacing[2], // dir[0][0],dir[1][0],dir[2][0], // dir[0][1],dir[1][1],dir[2][1], // dir[0][2],dir[1][2],dir[2][2]] OriginType meshOrigin; meshOrigin.Fill(0.0); PhysicalDimensionsType meshPhysical; meshPhysical.Fill(1.0); DirectionType meshDirection; meshDirection.SetIdentity(); MeshSizeType meshSize; meshSize.Fill(1); this->m_FixedParameters.SetSize(VDimension * (VDimension + 3)); this->SetFixedParametersFromTransformDomainInformation(meshOrigin, meshPhysical, meshDirection, meshSize); this->SetCoefficientImageInformationFromFixedParameters(); } template std::string BSplineTransform::GetTransformTypeAsString() const { if (VSplineOrder != 3) { std::ostringstream n; n << Superclass::GetTransformTypeAsString() << '_' << VSplineOrder; return n.str(); } return Superclass::GetTransformTypeAsString(); } template auto BSplineTransform::GetNumberOfParameters() const -> NumberOfParametersType { // The number of parameters equals SpaceDimension * number of // of pixels in the grid region. return SpaceDimension * this->GetNumberOfParametersPerDimension(); } template auto BSplineTransform::GetNumberOfParametersPerDimension() const -> NumberOfParametersType { // The number of parameters per dimension equals the number of // of pixels in the grid region. NumberOfParametersType numberOfParametersPerDimension = 1; for (unsigned int i = 0; i < SpaceDimension; ++i) { numberOfParametersPerDimension *= static_cast(this->m_FixedParameters[i]); } return numberOfParametersPerDimension; } template void BSplineTransform::SetTransformDomainOrigin(const OriginType & origin) { if (this->GetTransformDomainOrigin() != origin) { this->SetFixedParametersFromTransformDomainInformation(origin, this->GetTransformDomainPhysicalDimensions(), this->GetTransformDomainDirection(), this->GetTransformDomainMeshSize()); this->SetCoefficientImageInformationFromFixedParameters(); this->Modified(); } } template auto BSplineTransform::GetTransformDomainOrigin() const -> OriginType { OriginType origin; for (unsigned int i = 0; i < VDimension; ++i) { const ScalarType spacing = this->m_FixedParameters[2 * VDimension + i]; origin[i] = spacing * 0.5 * (SplineOrder - 1); } origin = this->GetTransformDomainDirection() * origin; for (unsigned int i = 0; i < VDimension; ++i) { const ScalarType grid_origin = this->m_FixedParameters[VDimension + i]; origin[i] += grid_origin; } return origin; } template void BSplineTransform::SetTransformDomainPhysicalDimensions( const PhysicalDimensionsType & dims) { if (this->GetTransformDomainPhysicalDimensions() != dims) { this->SetFixedParametersFromTransformDomainInformation( this->GetTransformDomainOrigin(), dims, this->GetTransformDomainDirection(), this->GetTransformDomainMeshSize()); this->SetCoefficientImageInformationFromFixedParameters(); this->Modified(); } } template auto BSplineTransform::GetTransformDomainPhysicalDimensions() const -> PhysicalDimensionsType { const MeshSizeType size = this->GetTransformDomainMeshSize(); PhysicalDimensionsType physicalDim; for (unsigned int i = 0; i < VDimension; ++i) { ScalarType spacing = this->m_FixedParameters[2 * VDimension + i]; physicalDim[i] = size[i] * spacing; } return physicalDim; } template void BSplineTransform::SetTransformDomainDirection( const DirectionType & direction) { if (this->GetTransformDomainDirection() != direction) { this->SetFixedParametersFromTransformDomainInformation(this->GetTransformDomainOrigin(), this->GetTransformDomainPhysicalDimensions(), direction, this->GetTransformDomainMeshSize()); this->SetCoefficientImageInformationFromFixedParameters(); this->Modified(); } } template auto BSplineTransform::GetTransformDomainDirection() const -> DirectionType { DirectionType direction; for (unsigned int di = 0; di < VDimension; ++di) { for (unsigned int dj = 0; dj < VDimension; ++dj) { const FixedParametersValueType v = this->m_FixedParameters[3 * VDimension + (di * VDimension + dj)]; direction[di][dj] = static_cast(v); } } return direction; } template void BSplineTransform::SetTransformDomainMeshSize( const MeshSizeType & meshSize) { if (this->GetTransformDomainMeshSize() != meshSize) { this->SetFixedParametersFromTransformDomainInformation(this->GetTransformDomainOrigin(), this->GetTransformDomainPhysicalDimensions(), this->GetTransformDomainDirection(), meshSize); this->SetCoefficientImageInformationFromFixedParameters(); this->Modified(); } } template auto BSplineTransform::GetTransformDomainMeshSize() const -> MeshSizeType { MeshSizeType meshSize; for (unsigned int i = 0; i < VDimension; ++i) { using ValueType = typename MeshSizeType::SizeValueType; meshSize[i] = static_cast(this->m_FixedParameters[i]) - SplineOrder; } return meshSize; } template void BSplineTransform::SetFixedParametersFromCoefficientImageInformation() { // Fixed Parameters store the following information: // grid size // grid origin // grid spacing // grid direction // Set the grid size parameters const SizeType & gridSize = this->m_CoefficientImages[0]->GetLargestPossibleRegion().GetSize(); for (unsigned int i = 0; i < VDimension; ++i) { this->m_FixedParameters[i] = static_cast(gridSize[i]); } const OriginType & origin = this->m_CoefficientImages[0]->GetOrigin(); for (unsigned int i = 0; i < VDimension; ++i) { this->m_FixedParameters[VDimension + i] = static_cast(origin[i]); } // Set the spacing parameters const SpacingType & spacing = this->m_CoefficientImages[0]->GetSpacing(); for (unsigned int i = 0; i < VDimension; ++i) { this->m_FixedParameters[2 * VDimension + i] = static_cast(spacing[i]); } // Set the direction parameters const DirectionType & direction = this->m_CoefficientImages[0]->GetDirection(); for (unsigned int di = 0; di < VDimension; ++di) { for (unsigned int dj = 0; dj < VDimension; ++dj) { this->m_FixedParameters[3 * VDimension + (di * VDimension + dj)] = static_cast(direction[di][dj]); } } } template void BSplineTransform::SetFixedParametersFromTransformDomainInformation( const OriginType & meshOrigin, const PhysicalDimensionsType & meshPhysical, const DirectionType & meshDirection, const MeshSizeType & meshSize) { // Set the grid size parameters for (unsigned int i = 0; i < VDimension; ++i) { this->m_FixedParameters[i] = static_cast(meshSize[i] + SplineOrder); } // Set the origin parameters using PointType = typename ImageType::PointType; PointType origin; origin.Fill(0.0); for (unsigned int i = 0; i < VDimension; ++i) { ScalarType gridSpacing = meshPhysical[i] / static_cast(meshSize[i]); origin[i] = -0.5 * gridSpacing * (SplineOrder - 1); } origin = meshDirection * origin; for (unsigned int i = 0; i < VDimension; ++i) { this->m_FixedParameters[VDimension + i] = static_cast(origin[i] + meshOrigin[i]); } // Set the spacing parameters for (unsigned int i = 0; i < VDimension; ++i) { ScalarType gridSpacing = meshPhysical[i] / static_cast(meshSize[i]); this->m_FixedParameters[2 * VDimension + i] = static_cast(gridSpacing); } // Set the direction parameters for (unsigned int di = 0; di < VDimension; ++di) { for (unsigned int dj = 0; dj < VDimension; ++dj) { this->m_FixedParameters[3 * VDimension + (di * VDimension + dj)] = static_cast(meshDirection[di][dj]); } } } template void BSplineTransform::SetCoefficientImageInformationFromFixedParameters() { // Fixed Parameters store the following information: // grid size // grid origin // grid spacing // grid direction // The size of these is equal to the NInputDimensions // set the grid size parameters SizeType gridSize; for (unsigned int i = 0; i < VDimension; ++i) { gridSize[i] = static_cast(this->m_FixedParameters[i]); } this->m_CoefficientImages[0]->SetRegions(gridSize); // Set the origin parameters OriginType origin; for (unsigned int i = 0; i < VDimension; ++i) { origin[i] = this->m_FixedParameters[VDimension + i]; } this->m_CoefficientImages[0]->SetOrigin(origin); // Set the spacing parameters SpacingType spacing; for (unsigned int i = 0; i < VDimension; ++i) { spacing[i] = this->m_FixedParameters[2 * VDimension + i]; } this->m_CoefficientImages[0]->SetSpacing(spacing); // Set the direction parameters DirectionType direction; for (unsigned int di = 0; di < VDimension; ++di) { for (unsigned int dj = 0; dj < VDimension; ++dj) { direction[di][dj] = this->m_FixedParameters[3 * VDimension + (di * VDimension + dj)]; } } this->m_CoefficientImages[0]->SetDirection(direction); // Copy the information to the rest of the images for (unsigned int i = 1; i < SpaceDimension; ++i) { this->m_CoefficientImages[i]->CopyInformation(this->m_CoefficientImages[0]); this->m_CoefficientImages[i]->SetRegions(this->m_CoefficientImages[0]->GetLargestPossibleRegion()); } // Check if we need to resize the default parameter buffer. if (this->m_InternalParametersBuffer.GetSize() != this->GetNumberOfParameters()) { this->m_InternalParametersBuffer.SetSize(this->GetNumberOfParameters()); // Fill with zeros for identity. this->m_InternalParametersBuffer.Fill(0); // Set the image's pixel container to this buffer this->SetParameters(this->m_InternalParametersBuffer); } } template void BSplineTransform::SetFixedParameters( const FixedParametersType & passedParameters) { // Check if the number of passedParameters match the // expected number of this->m_FixedParameters if (passedParameters.Size() == this->m_FixedParameters.Size()) { for (unsigned int i = 0; i < VDimension * (3 + VDimension); ++i) { this->m_FixedParameters[i] = passedParameters[i]; } this->Modified(); } else { itkExceptionMacro("Mismatched between parameters size " << passedParameters.size() << " and the required number of fixed parameters " << this->m_FixedParameters.Size()); } this->SetCoefficientImageInformationFromFixedParameters(); } template void BSplineTransform::SetCoefficientImages( const CoefficientImageArray & images) { bool validArrayOfImages = true; for (unsigned int j = 0; j < SpaceDimension; ++j) { validArrayOfImages &= (images[j].IsNotNull()); } if (!validArrayOfImages) { itkExceptionMacro("SetCoefficientImage() requires that an array of " << "correctly sized images be supplied."); } // update coefficient images and internal parameter buffer const SizeValueType numberOfPixels = images[0]->GetLargestPossibleRegion().GetNumberOfPixels(); const SizeValueType totalParameters = numberOfPixels * SpaceDimension; this->m_InternalParametersBuffer.SetSize(totalParameters); for (unsigned int j = 0; j < SpaceDimension; ++j) { const SizeValueType numberOfPixels_j = images[j]->GetLargestPossibleRegion().GetNumberOfPixels(); if (numberOfPixels_j * SpaceDimension != totalParameters) { itkExceptionMacro("SetCoefficientImage() has array of images that are " << "not the correct size. " << numberOfPixels_j * SpaceDimension << " != " << totalParameters << " for image at index " << j << " \n" << images[j]); } const ParametersValueType * const baseImagePointer = images[j]->GetBufferPointer(); ParametersValueType * dataPointer = this->m_InternalParametersBuffer.data_block(); std::copy_n(baseImagePointer, numberOfPixels, dataPointer + j * numberOfPixels); this->m_CoefficientImages[j]->CopyInformation(images[j]); this->m_CoefficientImages[j]->SetRegions(images[j]->GetLargestPossibleRegion()); } // synchronize parameters this->SetFixedParametersFromCoefficientImageInformation(); this->SetParameters(this->m_InternalParametersBuffer); } template bool BSplineTransform::InsideValidRegion(ContinuousIndexType & index) const { const SizeType gridSize = this->m_CoefficientImages[0]->GetLargestPossibleRegion().GetSize(); const ScalarType minLimit = 0.5 * static_cast(SplineOrder - 1); // Needed so that index can be changed bool inside = true; for (unsigned int j = 0; j < SpaceDimension; ++j) { const ScalarType maxLimit = static_cast(gridSize[j]) - 0.5 * static_cast(SplineOrder - 1) - 1.0; if (Math::FloatAlmostEqual(index[j], maxLimit, 4)) { index[j] = Math::FloatAddULP(maxLimit, -6); } else if (index[j] >= maxLimit) { inside = false; break; } else if (index[j] < minLimit) { inside = false; break; } } return inside; } template void BSplineTransform::TransformPoint(const InputPointType & point, OutputPointType & outputPoint, WeightsType & weights, ParameterIndexArrayType & indices, bool & inside) const { inside = true; if (this->m_CoefficientImages[0]->GetBufferPointer()) { ContinuousIndexType index = this->m_CoefficientImages[0] ->template TransformPhysicalPointToContinuousIndex(point); // NOTE: if the support region does not lie totally within the grid // we assume zero displacement and return the input point inside = this->InsideValidRegion(index); if (!inside) { outputPoint = point; return; } IndexType supportIndex; // Compute interpolation weights this->m_WeightsFunction->Evaluate(index, weights, supportIndex); // For each dimension, correlate coefficient with weights constexpr auto supportSize = SizeType::Filled(SplineOrder + 1); const RegionType supportRegion(supportIndex, supportSize); outputPoint.Fill(ScalarType{}); using IteratorType = ImageScanlineConstIterator; IteratorType coeffIterator[SpaceDimension]; unsigned long counter = 0; const ParametersValueType * basePointer = this->m_CoefficientImages[0]->GetBufferPointer(); for (unsigned int j = 0; j < SpaceDimension; ++j) { coeffIterator[j] = IteratorType(this->m_CoefficientImages[j], supportRegion); } while (!coeffIterator[0].IsAtEnd()) { while (!coeffIterator[0].IsAtEndOfLine()) { // Multiply weight with coefficient for (unsigned int j = 0; j < SpaceDimension; ++j) { outputPoint[j] += static_cast(weights[counter] * coeffIterator[j].Get()); } // Populate the indices array indices[counter] = &(coeffIterator[0].Value()) - basePointer; // Go to next coefficient in the support region ++counter; for (unsigned int j = 0; j < SpaceDimension; ++j) { ++(coeffIterator[j]); } } // end scanline for (unsigned int j = 0; j < SpaceDimension; ++j) { coeffIterator[j].NextLine(); } } // Return results for (unsigned int j = 0; j < SpaceDimension; ++j) { outputPoint[j] += point[j]; } } else { itkWarningMacro("B-spline coefficients have not been set"); for (unsigned int j = 0; j < SpaceDimension; ++j) { outputPoint[j] = point[j]; } } } template void BSplineTransform::ComputeJacobianWithRespectToParameters( const InputPointType & point, JacobianType & jacobian) const { // Zero all components of jacobian jacobian.SetSize(SpaceDimension, this->GetNumberOfParameters()); jacobian.Fill(0.0); constexpr auto supportSize = SizeType::Filled(SplineOrder + 1); ContinuousIndexType index = this->m_CoefficientImages[0] ->template TransformPhysicalPointToContinuousIndex(point); // NOTE: if the support region does not lie totally within the grid we assume // zero displacement and do no computations beyond zeroing out the value // return the input point if (!this->InsideValidRegion(index)) { return; } // Compute interpolation weights WeightsType weights; IndexType supportIndex; this->m_WeightsFunction->Evaluate(index, weights, supportIndex); const RegionType supportRegion(supportIndex, supportSize); IndexType startIndex = this->m_CoefficientImages[0]->GetLargestPossibleRegion().GetIndex(); const MeshSizeType meshSize = this->GetTransformDomainMeshSize(); SizeType cumulativeGridSizes; cumulativeGridSizes[0] = (meshSize[0] + SplineOrder); for (unsigned int d = 1; d < SpaceDimension; ++d) { cumulativeGridSizes[d] = cumulativeGridSizes[d - 1] * (meshSize[d] + SplineOrder); } SizeValueType numberOfParametersPerDimension = this->GetNumberOfParametersPerDimension(); unsigned long counter = 0; for (ImageRegionConstIteratorWithIndex It(this->m_CoefficientImages[0], supportRegion); !It.IsAtEnd(); ++It) { typename ImageType::OffsetType currentIndex = It.GetIndex() - startIndex; unsigned long number = currentIndex[0]; for (unsigned int d = 1; d < SpaceDimension; ++d) { number += (currentIndex[d] * cumulativeGridSizes[d - 1]); } for (unsigned int d = 0; d < SpaceDimension; ++d) { jacobian(d, number + d * numberOfParametersPerDimension) = weights[counter]; } ++counter; } } template void BSplineTransform::PrintSelf(std::ostream & os, Indent indent) const { this->Superclass::PrintSelf(os, indent); os << indent << "TransformDomainOrigin: " << this->GetTransformDomainOrigin() << std::endl; os << indent << "TransformDomainPhysicalDimensions: " << this->GetTransformDomainPhysicalDimensions() << std::endl; os << indent << "TransformDomainDirection: " << this->GetTransformDomainDirection() << std::endl; os << indent << "TransformDomainMeshSize: " << this->GetTransformDomainMeshSize() << std::endl; os << indent << "GridSize: " << this->m_CoefficientImages[0]->GetLargestPossibleRegion().GetSize() << std::endl; os << indent << "GridOrigin: " << this->m_CoefficientImages[0]->GetOrigin() << std::endl; os << indent << "GridSpacing: " << this->m_CoefficientImages[0]->GetSpacing() << std::endl; os << indent << "GridDirection: " << this->m_CoefficientImages[0]->GetDirection() << std::endl; } } // namespace itk #endif