/*========================================================================= * * 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 itkBSplineTransformInitializer_hxx #define itkBSplineTransformInitializer_hxx #include "itkContinuousIndex.h" #include "itkPointSet.h" #include "itkBoundingBox.h" namespace itk { template void BSplineTransformInitializer::SetTransformDomainMeshSize(const MeshSizeType meshSize) { itkDebugMacro("setting m_TransformDomainMeshSize to " << meshSize); if (this->m_SetTransformDomainMeshSizeViaInitializer == false || this->m_TransformDomainMeshSize != meshSize) { this->m_SetTransformDomainMeshSizeViaInitializer = true; this->m_TransformDomainMeshSize = meshSize; this->Modified(); } } template void BSplineTransformInitializer::InitializeTransform() const { if (!this->m_Transform) { itkExceptionMacro("Transform has not been set."); } if (!this->m_Image) { itkExceptionMacro("Image has not been set."); } if (TImage::GetImageDimension() != SpaceDimension) { itkExceptionMacro("Image dimensionality does not match the transform."); } OriginType transformDomainOrigin; PhysicalDimensionsType transformDomainPhysicalDimensions; DirectionType transformDomainDirection; // Determine the image corners. We keep track of the relative location of // the corners using a binary labeling system. For example, in a 3-D // coordinate system aligned with the x,y,z axes, we have 8 points labeled as // follows: // // 1. 000 min_x, min_y, min_z // 2. 001 max_x, min_y, min_z // 3. 010 min_x, max_y, min_z // 4. 011 max_x, max_y, min_z // 5. 100 min_x, min_y, max_z // 6. 101 max_x, min_y, max_z // 7. 110 min_x, max_y, max_z // 8. 111 max_x, max_y, max_z // // We use this binary description of the corners in n-dimensions because it // allows us to know the adjacent neighbors of an arbitrary image corner. For // example, suppose we locate the transform domain origin at the corner 011 // the adjacent neighbors which form the rotated coordinate system are // 111, 001, and 010. Notice that we just change 1 bit at a time from the // origin to determine these axes. Thus bitwise operators are used // throughout the code so that the initializer is generalized to n-dimensions. using CoordRepType = typename ImagePointType::CoordRepType; using PointSetType = PointSet; auto cornerPoints = PointSetType::New(); cornerPoints->Initialize(); using PointType = typename PointSetType::PointType; using PointIdentifier = typename PointSetType::PointIdentifier; using RealType = typename PointType::RealType; using VectorType = typename PointType::VectorType; using ContinuousIndexType = ContinuousIndex; // We first convert the image corners into points which reside in physical // space and label them as indicated above. Note that the corners reside // at the extreme corners of the image and not just at the voxel centers. // We also store the corners using the point set class which gives us easy // access to the bounding box. const CoordRepType BSplineTransformDomainEpsilon = std::pow(2.0, -3); ContinuousIndexType startIndex; for (unsigned int i = 0; i < SpaceDimension; ++i) { startIndex[i] = this->m_Image->GetLargestPossibleRegion().GetIndex()[i] - 0.5 - BSplineTransformDomainEpsilon; } for (unsigned int d = 0, N = 1 << SpaceDimension; d < N; ++d) { ContinuousIndexType whichIndex; for (unsigned int i = 0; i < SpaceDimension; ++i) { whichIndex[i] = startIndex[i] + static_cast( ((d >> i) & 1) * (this->m_Image->GetLargestPossibleRegion().GetSize()[i] + 2.0 * BSplineTransformDomainEpsilon)); } ImagePointType point; this->m_Image->TransformContinuousIndexToPhysicalPoint(whichIndex, point); PointType corner; corner.CastFrom(point); cornerPoints->SetPoint(d, corner); } // We next determine which corner is the transform domain origin by which // point is closest to the minimum of the bounding box. using BoundingBoxType = BoundingBox; auto bbox = BoundingBoxType::New(); bbox->SetPoints(cornerPoints->GetPoints()); bbox->ComputeBoundingBox(); transformDomainOrigin.Fill(0); PointIdentifier transformDomainOriginId = 0; RealType minDistance = NumericTraits::max(); for (unsigned int d = 0; d < cornerPoints->GetNumberOfPoints(); ++d) { PointType corner; corner.Fill(0.0); cornerPoints->GetPoint(d, &corner); RealType distance = corner.SquaredEuclideanDistanceTo(bbox->GetMinimum()); if (distance < minDistance) { transformDomainOrigin.CastFrom(corner); minDistance = distance; transformDomainOriginId = static_cast(d); } } // Now we need to find the transform direction matrix. This is done // by using the domain origin and its adjacent neighbors to determine a new // rotated coordinate system. transformDomainDirection.SetIdentity(); // We first determine which image axis is the most aligned with each physical // axis. PointIdentifier minCornerId[SpaceDimension]; double minAngle[SpaceDimension]; for (unsigned int d = 0; d < SpaceDimension; ++d) { minAngle[d] = NumericTraits::max(); VectorType vectorAxis{}; vectorAxis[d] = 1.0; for (unsigned int i = 0; i < SpaceDimension; ++i) { PointIdentifier oppositeCornerId = (static_cast(1) << static_cast(i)) ^ transformDomainOriginId; PointType corner; corner.Fill(0.0); cornerPoints->GetPoint(oppositeCornerId, &corner); VectorType vector = corner - transformDomainOrigin; vector.Normalize(); double theta = angle(vectorAxis.GetVnlVector(), vector.GetVnlVector()); if (theta < minAngle[d]) { bool alreadyFound = false; for (unsigned int j = 0; j < d; ++j) { if (minCornerId[j] == oppositeCornerId) { alreadyFound = true; break; } } if (!alreadyFound) { minCornerId[d] = oppositeCornerId; minAngle[d] = theta; } } } } // Now that we know which image axes corresponds to the unrotated coordinate // axes in physical space, we can easily construct the rotation matrix which // rotates a point from the unrotated coordinate system to the rotated // coordinate system. This is done by placing the rotated axis vectors as // columns in the rotation matrix. for (unsigned int d = 0; d < SpaceDimension; ++d) { PointType corner; corner.Fill(0.0); cornerPoints->GetPoint(minCornerId[d], &corner); VectorType vector = corner - transformDomainOrigin; // Note that specifying the size and spacing separately doesn't matter in // the case of the B-spline transform since the B-spline transform is a // continuous object over its finite domain. transformDomainPhysicalDimensions[d] = vector.GetNorm(); vector.Normalize(); for (unsigned int i = 0; i < SpaceDimension; ++i) { transformDomainDirection[i][d] = vector[i]; } } this->m_Transform->SetTransformDomainOrigin(transformDomainOrigin); this->m_Transform->SetTransformDomainPhysicalDimensions(transformDomainPhysicalDimensions); this->m_Transform->SetTransformDomainDirection(transformDomainDirection); if (this->m_SetTransformDomainMeshSizeViaInitializer) { this->m_Transform->SetTransformDomainMeshSize(this->m_TransformDomainMeshSize); } } template void BSplineTransformInitializer::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); itkPrintSelfObjectMacro(Image); itkPrintSelfObjectMacro(Transform); if (this->m_SetTransformDomainMeshSizeViaInitializer) { os << indent << "Transform domain mesh size: " << this->m_TransformDomainMeshSize << std::endl; } } } // namespace itk #endif