diff --git a/Core/Code/DataManagement/mitkBaseGeometry.cpp b/Core/Code/DataManagement/mitkBaseGeometry.cpp index b4c5572fdf..66228d6a62 100644 --- a/Core/Code/DataManagement/mitkBaseGeometry.cpp +++ b/Core/Code/DataManagement/mitkBaseGeometry.cpp @@ -1,904 +1,926 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include "mitkBaseGeometry.h" #include "mitkvector.h" #include "mitkMatrixConvert.h" #include #include #include "mitkRotationOperation.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkApplyTransformMatrixOperation.h" #include "mitkPointOperation.h" #include "mitkInteractionConst.h" mitk::BaseGeometry::BaseGeometry(): Superclass(), mitk::OperationActor(), m_Valid(true), m_FrameOfReferenceID(0), m_IndexToWorldTransformLastModified(0) { FillVector3D(m_FloatSpacing, 1,1,1); m_VtkMatrix = vtkMatrix4x4::New(); m_VtkIndexToWorldTransform = vtkMatrixToLinearTransform::New(); m_VtkIndexToWorldTransform->SetInput(m_VtkMatrix); Initialize(); } mitk::BaseGeometry::BaseGeometry(const BaseGeometry& other): Superclass(), m_TimeBounds(other.m_TimeBounds), m_Valid(other.m_Valid), m_FrameOfReferenceID(other.m_FrameOfReferenceID), m_IndexToWorldTransformLastModified(other.m_IndexToWorldTransformLastModified), m_Origin(other.m_Origin) { // DEPRECATED(m_RotationQuaternion = other.m_RotationQuaternion); // AffineGeometryFrame SetBounds(other.GetBounds()); m_VtkMatrix = vtkMatrix4x4::New(); m_VtkMatrix->DeepCopy(other.m_VtkMatrix); FillVector3D(m_FloatSpacing,other.m_FloatSpacing[0],other.m_FloatSpacing[1],other.m_FloatSpacing[2]); m_VtkIndexToWorldTransform = vtkMatrixToLinearTransform::New(); m_VtkIndexToWorldTransform->DeepCopy(other.m_VtkIndexToWorldTransform); m_VtkIndexToWorldTransform->SetInput(m_VtkMatrix); other.InitializeGeometry(this); } mitk::BaseGeometry::~BaseGeometry() { m_VtkMatrix->Delete(); m_VtkIndexToWorldTransform->Delete(); } const mitk::Point3D& mitk::BaseGeometry::GetOrigin() const { return m_Origin; } void mitk::BaseGeometry::SetOrigin(const Point3D & origin) { if(origin!=GetOrigin()) { m_Origin = origin; m_IndexToWorldTransform->SetOffset(m_Origin.GetVectorFromOrigin()); Modified(); TransferItkToVtkTransform(); } } void mitk::BaseGeometry::TransferItkToVtkTransform() { // copy m_IndexToWorldTransform into m_VtkIndexToWorldTransform TransferItkTransformToVtkMatrix(m_IndexToWorldTransform.GetPointer(), m_VtkMatrix); m_VtkIndexToWorldTransform->Modified(); } void mitk::BaseGeometry::CopySpacingFromTransform(mitk::AffineTransform3D* transform, mitk::Vector3D& spacing, float floatSpacing[3]) { mitk::AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = transform->GetMatrix().GetVnlMatrix(); spacing[0]=vnlmatrix.get_column(0).magnitude(); spacing[1]=vnlmatrix.get_column(1).magnitude(); spacing[2]=vnlmatrix.get_column(2).magnitude(); floatSpacing[0]=spacing[0]; floatSpacing[1]=spacing[1]; floatSpacing[2]=spacing[2]; } void mitk::BaseGeometry::Initialize() { float b[6] = {0,1,0,1,0,1}; SetFloatBounds(b); if(m_IndexToWorldTransform.IsNull()) m_IndexToWorldTransform = TransformType::New(); else m_IndexToWorldTransform->SetIdentity(); CopySpacingFromTransform(m_IndexToWorldTransform, m_Spacing, m_FloatSpacing); vtk2itk(m_IndexToWorldTransform->GetOffset(), m_Origin); m_VtkMatrix->Identity(); m_TimeBounds[0]=ScalarTypeNumericTraits::NonpositiveMin(); m_TimeBounds[1]=ScalarTypeNumericTraits::max(); m_FrameOfReferenceID = 0; this->InternPostInitialize(); } void mitk::BaseGeometry::SetFloatBounds(const float bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const float *input = bounds; int i=0; for(mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6 ;++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } void mitk::BaseGeometry::SetFloatBounds(const double bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const double *input = bounds; int i=0; for(mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6 ;++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } /** Initialize the geometry */ void mitk::BaseGeometry::InitializeGeometry(BaseGeometry* newGeometry) const { newGeometry->SetBounds(m_BoundingBox->GetBounds()); // we have to create a new transform!! newGeometry->SetTimeBounds(m_TimeBounds); newGeometry->SetFrameOfReferenceID(GetFrameOfReferenceID()); if(m_IndexToWorldTransform) { TransformType::Pointer indexToWorldTransform = TransformType::New(); indexToWorldTransform->SetCenter( m_IndexToWorldTransform->GetCenter() ); indexToWorldTransform->SetMatrix( m_IndexToWorldTransform->GetMatrix() ); indexToWorldTransform->SetOffset( m_IndexToWorldTransform->GetOffset() ); newGeometry->SetIndexToWorldTransform(indexToWorldTransform); } this->InternPostInitializeGeometry(newGeometry); } /** Set the bounds */ void mitk::BaseGeometry::SetBounds(const BoundsArrayType& bounds) { InternPreSetBounds(bounds); m_BoundingBox = BoundingBoxType::New(); BoundingBoxType::PointsContainer::Pointer pointscontainer = BoundingBoxType::PointsContainer::New(); BoundingBoxType::PointType p; BoundingBoxType::PointIdentifier pointid; for(pointid=0; pointid<2;++pointid) { unsigned int i; for(i=0; iInsertElement(pointid, p); } m_BoundingBox->SetPoints(pointscontainer); m_BoundingBox->ComputeBoundingBox(); this->Modified(); } void mitk::BaseGeometry::InternPreSetBounds(const BoundsArrayType& bounds){}; void mitk::BaseGeometry::SetIndexToWorldTransform(mitk::AffineTransform3D* transform) { + InternPreSetIndexToWorldTransform(transform); if(m_IndexToWorldTransform.GetPointer() != transform) { m_IndexToWorldTransform = transform; CopySpacingFromTransform(m_IndexToWorldTransform, m_Spacing, m_FloatSpacing); vtk2itk(m_IndexToWorldTransform->GetOffset(), m_Origin); TransferItkToVtkTransform(); Modified(); } + InternPostSetIndexToWorldTransform(transform); } +void mitk::BaseGeometry::InternPreSetIndexToWorldTransform(mitk::AffineTransform3D* transform) +{} +void mitk::BaseGeometry::InternPostSetIndexToWorldTransform(mitk::AffineTransform3D* transform) +{} + const mitk::BaseGeometry::BoundsArrayType mitk::BaseGeometry::GetBounds() const { assert(m_BoundingBox.IsNotNull()); return m_BoundingBox->GetBounds(); } bool mitk::BaseGeometry::IsValid() const { bool isValid = m_Valid; isValid = isValid && this->InternPostIsValid(); return isValid; } bool mitk::BaseGeometry::InternPostIsValid() const { return true; } const float* mitk::BaseGeometry::GetFloatSpacing() const { return m_FloatSpacing; } bool mitk::Equal(const BaseGeometry::TransformType *leftHandSide, const BaseGeometry::TransformType *rightHandSide, ScalarType eps, bool verbose ) { //Compare IndexToWorldTransform Matrix if( !mitk::MatrixEqualElementWise( leftHandSide->GetMatrix(), rightHandSide->GetMatrix() ) ) { if(verbose) { MITK_INFO << "[( BaseGeometry::TransformType )] Index to World Transformation matrix differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide->GetMatrix() << " : leftHandSide is " << leftHandSide->GetMatrix() << " and tolerance is " << eps; } return false; } return true; } bool mitk::Equal( const mitk::BaseGeometry::BoundingBoxType *leftHandSide, const mitk::BaseGeometry::BoundingBoxType *rightHandSide, ScalarType eps, bool verbose ) { bool result = true; if( rightHandSide == NULL ) { if(verbose) MITK_INFO << "[( BaseGeometry::BoundingBoxType )] rightHandSide NULL."; return false; } if( leftHandSide == NULL ) { if(verbose) MITK_INFO << "[( BaseGeometry::BoundingBoxType )] leftHandSide NULL."; return false; } BaseGeometry::BoundsArrayType rightBounds = rightHandSide->GetBounds(); BaseGeometry::BoundsArrayType leftBounds = leftHandSide->GetBounds(); BaseGeometry::BoundsArrayType::Iterator itLeft = leftBounds.Begin(); for( BaseGeometry::BoundsArrayType::Iterator itRight = rightBounds.Begin(); itRight != rightBounds.End(); ++itRight) { if(( !mitk::Equal( *itLeft, *itRight, eps )) ) { if(verbose) { MITK_INFO << "[( BaseGeometry::BoundingBoxType )] bounds are not equal."; MITK_INFO << "rightHandSide is " << setprecision(12) << *itRight << " : leftHandSide is " << *itLeft << " and tolerance is " << eps; } result = false; } itLeft++; } return result; } bool mitk::Equal(const mitk::BaseGeometry *leftHandSide, const mitk::BaseGeometry *rightHandSide, ScalarType eps, bool verbose) { bool result = true; if( rightHandSide == NULL ) { if(verbose) MITK_INFO << "[( BaseGeometry )] rightHandSide NULL."; return false; } if( leftHandSide == NULL) { if(verbose) MITK_INFO << "[( BaseGeometry )] leftHandSide NULL."; return false; } //Compare spacings if( !mitk::Equal( leftHandSide->GetSpacing(), rightHandSide->GetSpacing(), eps ) ) { if(verbose) { MITK_INFO << "[( BaseGeometry )] Spacing differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide->GetSpacing() << " : leftHandSide is " << leftHandSide->GetSpacing() << " and tolerance is " << eps; } result = false; } //Compare Origins if( !mitk::Equal( leftHandSide->GetOrigin(), rightHandSide->GetOrigin(), eps ) ) { if(verbose) { MITK_INFO << "[( BaseGeometry )] Origin differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide->GetOrigin() << " : leftHandSide is " << leftHandSide->GetOrigin() << " and tolerance is " << eps; } result = false; } //Compare Axis and Extents for( unsigned int i=0; i<3; ++i) { if( !mitk::Equal( leftHandSide->GetAxisVector(i), rightHandSide->GetAxisVector(i), eps)) { if(verbose) { MITK_INFO << "[( BaseGeometry )] AxisVector #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide->GetAxisVector(i) << " : leftHandSide is " << leftHandSide->GetAxisVector(i) << " and tolerance is " << eps; } result = false; } if( !mitk::Equal( leftHandSide->GetExtent(i), rightHandSide->GetExtent(i), eps) ) { if(verbose) { MITK_INFO << "[( BaseGeometry )] Extent #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide->GetExtent(i) << " : leftHandSide is " << leftHandSide->GetExtent(i) << " and tolerance is " << eps; } result = false; } } //Compare BoundingBoxes if( !mitk::Equal( leftHandSide->GetBoundingBox(), rightHandSide->GetBoundingBox(), eps, verbose) ) { result = false; } //Compare IndexToWorldTransform Matrix if( !mitk::Equal( leftHandSide->GetIndexToWorldTransform(), rightHandSide->GetIndexToWorldTransform(), eps, verbose) ) { result = false; } return result; } void mitk::BaseGeometry::SetSpacing(const mitk::Vector3D& aSpacing) { + InternPreSetSpacing(aSpacing); + InternSetSpacing(aSpacing); +} + +void mitk::BaseGeometry::InternPreSetSpacing(const mitk::Vector3D& aSpacing) +{} +void mitk::BaseGeometry::InternSetSpacing(const mitk::Vector3D& aSpacing){ if(mitk::Equal(m_Spacing, aSpacing) == false) { assert(aSpacing[0]>0 && aSpacing[1]>0 && aSpacing[2]>0); m_Spacing = aSpacing; AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = m_IndexToWorldTransform->GetMatrix().GetVnlMatrix(); mitk::VnlVector col; col = vnlmatrix.get_column(0); col.normalize(); col*=aSpacing[0]; vnlmatrix.set_column(0, col); col = vnlmatrix.get_column(1); col.normalize(); col*=aSpacing[1]; vnlmatrix.set_column(1, col); col = vnlmatrix.get_column(2); col.normalize(); col*=aSpacing[2]; vnlmatrix.set_column(2, col); Matrix3D matrix; matrix = vnlmatrix; AffineTransform3D::Pointer transform = AffineTransform3D::New(); transform->SetMatrix(matrix); transform->SetOffset(m_IndexToWorldTransform->GetOffset()); SetIndexToWorldTransform(transform.GetPointer()); itk2vtk(m_Spacing, m_FloatSpacing); } } mitk::Vector3D mitk::BaseGeometry::GetAxisVector(unsigned int direction) const { Vector3D frontToBack; frontToBack.SetVnlVector(m_IndexToWorldTransform->GetMatrix().GetVnlMatrix().get_column(direction)); frontToBack *= GetExtent(direction); return frontToBack; } mitk::ScalarType mitk::BaseGeometry::GetExtent(unsigned int direction) const { assert(m_BoundingBox.IsNotNull()); if (direction>=NDimensions) mitkThrow() << "Direction is too big. This geometry is for 3D Data"; BoundsArrayType bounds = m_BoundingBox->GetBounds(); return bounds[direction*2+1]-bounds[direction*2]; } bool mitk::BaseGeometry::Is2DConvertable() { bool isConvertableWithoutLoss = true; do { if (this->GetSpacing()[2] != 1) { isConvertableWithoutLoss = false; break; } if (this->GetOrigin()[2] != 0) { isConvertableWithoutLoss = false; break; } mitk::Vector3D col0, col1, col2; col0.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(0)); col1.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(1)); col2.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(2)); if ((col0[2] != 0) || (col1[2] != 0) || (col2[0] != 0) || (col2[1] != 0) || (col2[2] != 1)) { isConvertableWithoutLoss = false; break; } } while (0); return isConvertableWithoutLoss; } mitk::Point3D mitk::BaseGeometry::GetCenter() const { assert(m_BoundingBox.IsNotNull()); return m_IndexToWorldTransform->TransformPoint(m_BoundingBox->GetCenter()); } double mitk::BaseGeometry::GetDiagonalLength2() const { Vector3D diagonalvector = GetCornerPoint()-GetCornerPoint(false, false, false); return diagonalvector.GetSquaredNorm(); } //##Documentation //## @brief Get the length of the diagonal of the bounding-box in mm //## double mitk::BaseGeometry::GetDiagonalLength() const { return sqrt(GetDiagonalLength2()); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(int id) const { assert(id >= 0); assert(m_BoundingBox.IsNotNull()); BoundingBox::BoundsArrayType bounds = m_BoundingBox->GetBounds(); Point3D cornerpoint; switch(id) { case 0: FillVector3D(cornerpoint, bounds[0],bounds[2],bounds[4]); break; case 1: FillVector3D(cornerpoint, bounds[0],bounds[2],bounds[5]); break; case 2: FillVector3D(cornerpoint, bounds[0],bounds[3],bounds[4]); break; case 3: FillVector3D(cornerpoint, bounds[0],bounds[3],bounds[5]); break; case 4: FillVector3D(cornerpoint, bounds[1],bounds[2],bounds[4]); break; case 5: FillVector3D(cornerpoint, bounds[1],bounds[2],bounds[5]); break; case 6: FillVector3D(cornerpoint, bounds[1],bounds[3],bounds[4]); break; case 7: FillVector3D(cornerpoint, bounds[1],bounds[3],bounds[5]); break; default: { itkExceptionMacro(<<"A cube only has 8 corners. These are labeled 0-7."); } } return m_IndexToWorldTransform->TransformPoint(cornerpoint); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(bool xFront, bool yFront, bool zFront) const { assert(m_BoundingBox.IsNotNull()); BoundingBox::BoundsArrayType bounds = m_BoundingBox->GetBounds(); Point3D cornerpoint; cornerpoint[0] = (xFront ? bounds[0] : bounds[1]); cornerpoint[1] = (yFront ? bounds[2] : bounds[3]); cornerpoint[2] = (zFront ? bounds[4] : bounds[5]); return m_IndexToWorldTransform->TransformPoint(cornerpoint); } mitk::ScalarType mitk::BaseGeometry::GetExtentInMM(int direction) const { return m_IndexToWorldTransform->GetMatrix().GetVnlMatrix().get_column(direction).magnitude()*GetExtent(direction); } void mitk::BaseGeometry::SetExtentInMM(int direction, ScalarType extentInMM) { ScalarType len = GetExtentInMM(direction); if(fabs(len - extentInMM)>=mitk::eps) { AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = m_IndexToWorldTransform->GetMatrix().GetVnlMatrix(); if(len>extentInMM) vnlmatrix.set_column(direction, vnlmatrix.get_column(direction)/len*extentInMM); else vnlmatrix.set_column(direction, vnlmatrix.get_column(direction)*extentInMM/len); Matrix3D matrix; matrix = vnlmatrix; m_IndexToWorldTransform->SetMatrix(matrix); Modified(); } + InternPostSetExtentInMM(direction,extentInMM); } +void mitk::BaseGeometry::InternPostSetExtentInMM(int direction, ScalarType extentInMM){}; + bool mitk::BaseGeometry::IsInside(const mitk::Point3D& p) const { mitk::Point3D index; WorldToIndex(p, index); return IsIndexInside(index); } bool mitk::BaseGeometry::IsIndexInside(const mitk::Point3D& index) const { bool inside = false; //if it is an image geometry, we need to convert the index to discrete values //this is done by applying the rounding function also used in WorldToIndex (see line 323) inside = m_BoundingBox->IsInside(index); return inside; } //##Documentation //## @brief Convenience method for working with ITK indices template bool mitk::BaseGeometry::IsIndexInside(const itk::Index &index) const { int i, dim=index.GetIndexDimension(); Point3D pt_index; pt_index.Fill(0); for ( i = 0; i < dim; ++i ) { pt_index[i] = index[i]; } return IsIndexInside(pt_index); } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D &pt_mm, mitk::Point3D &pt_units) const { BackTransform(pt_mm, pt_units); } void mitk::BaseGeometry::WorldToIndex( const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { BackTransform( vec_mm, vec_units); } void mitk::BaseGeometry::BackTransform(const mitk::Vector3D& in, mitk::Vector3D& out) const { // Get WorldToIndex transform if (m_IndexToWorldTransformLastModified != m_IndexToWorldTransform->GetMTime()) { m_InvertedTransform = TransformType::New(); if (!m_IndexToWorldTransform->GetInverse( m_InvertedTransform.GetPointer() )) { itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed." ); } m_IndexToWorldTransformLastModified = m_IndexToWorldTransform->GetMTime(); } // Check for valid matrix inversion const TransformType::MatrixType& inverse = m_InvertedTransform->GetMatrix(); if(inverse.GetVnlMatrix().has_nans()) { itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed. Matrix was: " << std::endl << m_IndexToWorldTransform->GetMatrix() << "Suggested inverted matrix is:" << std::endl << inverse ); } // Transform vector for (unsigned int i = 0; i < 3; i++) { out[i] = 0.0; for (unsigned int j = 0; j < 3; j++) { out[i] += inverse[i][j]*in[j]; } } } void mitk::BaseGeometry::BackTransform(const mitk::Point3D &in, mitk::Point3D& out) const { ScalarType temp[3]; unsigned int i, j; const TransformType::OffsetType& offset = m_IndexToWorldTransform->GetOffset(); // Remove offset for (j = 0; j < 3; j++) { temp[j] = in[j] - offset[j]; } // Get WorldToIndex transform if (m_IndexToWorldTransformLastModified != m_IndexToWorldTransform->GetMTime()) { m_InvertedTransform = TransformType::New(); if (!m_IndexToWorldTransform->GetInverse( m_InvertedTransform.GetPointer() )) { itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed." ); } m_IndexToWorldTransformLastModified = m_IndexToWorldTransform->GetMTime(); } // Check for valid matrix inversion const TransformType::MatrixType& inverse = m_InvertedTransform->GetMatrix(); if(inverse.GetVnlMatrix().has_nans()) { itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed. Matrix was: " << std::endl << m_IndexToWorldTransform->GetMatrix() << "Suggested inverted matrix is:" << std::endl << inverse ); } // Transform point for (i = 0; i < 3; i++) { out[i] = 0.0; for (j = 0; j < 3; j++) { out[i] += inverse[i][j]*temp[j]; } } } mitk::VnlVector mitk::BaseGeometry::GetOriginVnl() const { return const_cast(this)->m_Origin.GetVnlVector(); } vtkLinearTransform* mitk::BaseGeometry::GetVtkTransform() const { return (vtkLinearTransform*)m_VtkIndexToWorldTransform; } void mitk::BaseGeometry::SetIdentity() { m_IndexToWorldTransform->SetIdentity(); m_Origin.Fill(0); + CopySpacingFromTransform(m_IndexToWorldTransform, m_Spacing, m_FloatSpacing); //NEW!!! Modified(); TransferItkToVtkTransform(); } void mitk::BaseGeometry::TransferVtkToItkTransform() { TransferVtkMatrixToItkTransform(m_VtkMatrix, m_IndexToWorldTransform.GetPointer()); CopySpacingFromTransform(m_IndexToWorldTransform, m_Spacing, m_FloatSpacing); vtk2itk(m_IndexToWorldTransform->GetOffset(), m_Origin); } void mitk::BaseGeometry::Compose( const mitk::BaseGeometry::TransformType * other, bool pre ) { m_IndexToWorldTransform->Compose(other, pre); CopySpacingFromTransform(m_IndexToWorldTransform, m_Spacing, m_FloatSpacing); vtk2itk(m_IndexToWorldTransform->GetOffset(), m_Origin); Modified(); TransferItkToVtkTransform(); } void mitk::BaseGeometry::Compose( const vtkMatrix4x4 * vtkmatrix, bool pre ) { mitk::BaseGeometry::TransformType::Pointer itkTransform = mitk::BaseGeometry::TransformType::New(); TransferVtkMatrixToItkTransform(vtkmatrix, itkTransform.GetPointer()); Compose(itkTransform, pre); } void mitk::BaseGeometry::Translate(const Vector3D & vector) { if((vector[0] != 0) || (vector[1] != 0) || (vector[2] != 0)) { this->SetOrigin(m_Origin + vector); } } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D &pt_units, mitk::Point3D &pt_mm) const { pt_mm = m_IndexToWorldTransform->TransformPoint(pt_units); } void mitk::BaseGeometry::IndexToWorld(const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { vec_mm = m_IndexToWorldTransform->TransformVector(vec_units); } #include void mitk::BaseGeometry::ExecuteOperation(Operation* operation) { vtkTransform *vtktransform = vtkTransform::New(); vtktransform->SetMatrix(m_VtkMatrix); switch (operation->GetOperationType()) { case OpNOTHING: break; case OpMOVE: { mitk::PointOperation *pointOp = dynamic_cast(operation); if (pointOp == NULL) { //mitk::StatusBar::GetInstance()->DisplayText("received wrong type of operation!See mitkAffineInteractor.cpp", 10000); return; } mitk::Point3D newPos = pointOp->GetPoint(); ScalarType data[3]; vtktransform->GetPosition(data); vtktransform->PostMultiply(); vtktransform->Translate(newPos[0], newPos[1], newPos[2]); vtktransform->PreMultiply(); break; } case OpSCALE: { mitk::PointOperation *pointOp = dynamic_cast(operation); if (pointOp == NULL) { //mitk::StatusBar::GetInstance()->DisplayText("received wrong type of operation!See mitkAffineInteractor.cpp", 10000); return; } mitk::Point3D newScale = pointOp->GetPoint(); ScalarType data[3]; /* calculate new scale: newscale = oldscale * (oldscale + scaletoadd)/oldscale */ data[0] = 1 + (newScale[0] / GetMatrixColumn(0).magnitude()); data[1] = 1 + (newScale[1] / GetMatrixColumn(1).magnitude()); data[2] = 1 + (newScale[2] / GetMatrixColumn(2).magnitude()); mitk::Point3D center = const_cast(m_BoundingBox.GetPointer())->GetCenter(); ScalarType pos[3]; vtktransform->GetPosition(pos); vtktransform->PostMultiply(); vtktransform->Translate(-pos[0], -pos[1], -pos[2]); vtktransform->Translate(-center[0], -center[1], -center[2]); vtktransform->PreMultiply(); vtktransform->Scale(data[0], data[1], data[2]); vtktransform->PostMultiply(); vtktransform->Translate(+center[0], +center[1], +center[2]); vtktransform->Translate(pos[0], pos[1], pos[2]); vtktransform->PreMultiply(); break; } case OpROTATE: { mitk::RotationOperation *rotateOp = dynamic_cast(operation); if (rotateOp == NULL) { //mitk::StatusBar::GetInstance()->DisplayText("received wrong type of operation!See mitkAffineInteractor.cpp", 10000); return; } Vector3D rotationVector = rotateOp->GetVectorOfRotation(); Point3D center = rotateOp->GetCenterOfRotation(); ScalarType angle = rotateOp->GetAngleOfRotation(); vtktransform->PostMultiply(); vtktransform->Translate(-center[0], -center[1], -center[2]); vtktransform->RotateWXYZ(angle, rotationVector[0], rotationVector[1], rotationVector[2]); vtktransform->Translate(center[0], center[1], center[2]); vtktransform->PreMultiply(); break; } case OpRESTOREPLANEPOSITION: { //Copy necessary to avoid vtk warning vtkMatrix4x4* matrix = vtkMatrix4x4::New(); TransferItkTransformToVtkMatrix(dynamic_cast(operation)->GetTransform().GetPointer(), matrix); vtktransform->SetMatrix(matrix); break; } case OpAPPLYTRANSFORMMATRIX: { ApplyTransformMatrixOperation *applyMatrixOp = dynamic_cast< ApplyTransformMatrixOperation* >( operation ); vtktransform->SetMatrix(applyMatrixOp->GetMatrix()); break; } default: vtktransform->Delete(); return; } m_VtkMatrix->DeepCopy(vtktransform->GetMatrix()); TransferVtkToItkTransform(); Modified(); vtktransform->Delete(); } mitk::VnlVector mitk::BaseGeometry::GetMatrixColumn(unsigned int direction) const { return m_IndexToWorldTransform->GetMatrix().GetVnlMatrix().get_column(direction); } mitk::BoundingBox::Pointer mitk::BaseGeometry::CalculateBoundingBoxRelativeToTransform(const mitk::AffineTransform3D* transform) const { mitk::BoundingBox::PointsContainer::Pointer pointscontainer=mitk::BoundingBox::PointsContainer::New(); mitk::BoundingBox::PointIdentifier pointid=0; unsigned char i; if(transform!=NULL) { mitk::AffineTransform3D::Pointer inverse = mitk::AffineTransform3D::New(); transform->GetInverse(inverse); for(i=0; i<8; ++i) pointscontainer->InsertElement( pointid++, inverse->TransformPoint( GetCornerPoint(i) )); } else { for(i=0; i<8; ++i) pointscontainer->InsertElement( pointid++, GetCornerPoint(i) ); } mitk::BoundingBox::Pointer result = mitk::BoundingBox::New(); result->SetPoints(pointscontainer); result->ComputeBoundingBox(); return result; } void mitk::BaseGeometry::SetTimeBounds(const TimeBounds& timebounds) { if(m_TimeBounds != timebounds) { m_TimeBounds = timebounds; Modified(); } + InternPostSetTimeBounds(timebounds); } +void mitk::BaseGeometry::InternPostSetTimeBounds(const TimeBounds& timebounds) +{} + const std::string mitk::BaseGeometry::GetTransformAsString( TransformType* transformType ) { std::ostringstream out; out << '['; for( int i=0; i<3; ++i ) { out << '['; for( int j=0; j<3; ++j ) out << transformType->GetMatrix().GetVnlMatrix().get(i, j) << ' '; out << ']'; } out << "]["; for( int i=0; i<3; ++i ) out << transformType->GetOffset()[i] << ' '; out << "]\0"; return out.str(); } void mitk::BaseGeometry::SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4* vtkmatrix) { m_VtkMatrix->DeepCopy(vtkmatrix); TransferVtkToItkTransform(); } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D & /*atPt3d_mm*/, const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { MITK_WARN<<"Warning! Call of the deprecated function BaseGeometry::WorldToIndex(point, vec, vec). Use BaseGeometry::WorldToIndex(vec, vec) instead!"; //BackTransform(atPt3d_mm, vec_mm, vec_units); this->WorldToIndex(vec_mm, vec_units); } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D &/*atPt3d_units*/, const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { MITK_WARN<<"Warning! Call of the deprecated function BaseGeometry::IndexToWorld(point, vec, vec). Use BaseGeometry::IndexToWorld(vec, vec) instead!"; //vec_mm = m_IndexToWorldTransform->TransformVector(vec_units); this->IndexToWorld(vec_units, vec_mm); } void mitk::BaseGeometry::BackTransform(const mitk::Point3D &/*at*/, const mitk::Vector3D &in, mitk::Vector3D& out) const { MITK_INFO<<"Warning! Call of the deprecated function BaseGeometry::BackTransform(point, vec, vec). Use BaseGeometry::BackTransform(vec, vec) instead!"; //// Get WorldToIndex transform //if (m_IndexToWorldTransformLastModified != m_IndexToWorldTransform->GetMTime()) //{ // m_InvertedTransform = TransformType::New(); // if (!m_IndexToWorldTransform->GetInverse( m_InvertedTransform.GetPointer() )) // { // itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed." ); // } // m_IndexToWorldTransformLastModified = m_IndexToWorldTransform->GetMTime(); //} //// Check for valid matrix inversion //const TransformType::MatrixType& inverse = m_InvertedTransform->GetMatrix(); //if(inverse.GetVnlMatrix().has_nans()) //{ // itkExceptionMacro( "Internal ITK matrix inversion error, cannot proceed. Matrix was: " << std::endl // << m_IndexToWorldTransform->GetMatrix() << "Suggested inverted matrix is:" << std::endl // << inverse ); //} //// Transform vector //for (unsigned int i = 0; i < 3; i++) //{ // out[i] = 0.0; // for (unsigned int j = 0; j < 3; j++) // { // out[i] += inverse[i][j]*in[j]; // } //} this->BackTransform(in, out); } diff --git a/Core/Code/DataManagement/mitkBaseGeometry.h b/Core/Code/DataManagement/mitkBaseGeometry.h index a30bdeef2d..77e8ad4883 100644 --- a/Core/Code/DataManagement/mitkBaseGeometry.h +++ b/Core/Code/DataManagement/mitkBaseGeometry.h @@ -1,575 +1,585 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef BaseGeometry_H_HEADER_INCLUDED #define BaseGeometry_H_HEADER_INCLUDED #include #include #include "mitkoperationactor.h" #include #include "mitkvector.h" #include #include #include "itkScalableAffineTransform.h" #include class vtkMatrix4x4; class vtkMatrixToLinearTransform; class vtkLinearTransform; namespace mitk { //##Documentation //## @brief Standard 3D-BoundingBox typedef //## //## Standard 3D-BoundingBox typedef to get rid of template arguments (3D, type). typedef itk::BoundingBox BoundingBox; //##Documentation //## @brief Standard typedef for time-bounds typedef itk::FixedArray TimeBounds; typedef itk::FixedArray FixedArrayType; //##Documentation //## @brief BaseGeometry xxxxxxxxxxxxxx //## //## xxxxxxxxxxx //## //## Rule: everything is in mm (ms) if not stated otherwise. //## @ingroup Geometry class MITK_CORE_EXPORT BaseGeometry : public itk::Object, public OperationActor { public: mitkClassMacro(BaseGeometry, itk::Object); //void testXYZ(){}; //xxx // ********************************** TypeDef ********************************** typedef itk::QuaternionRigidTransform< ScalarType > QuaternionTransformType; typedef QuaternionTransformType::VnlQuaternionType VnlQuaternionType; typedef itk::ScalableAffineTransform TransformType; typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::BoundsArrayType BoundsArrayType; typedef BoundingBoxType::Pointer BoundingBoxPointer; // ********************************** Origin, Spacing ********************************** //##Documentation //## @brief Get the origin, e.g. the upper-left corner of the plane const Point3D& GetOrigin() const; //##Documentation //## @brief Set the origin, i.e. the upper-left corner of the plane //## void SetOrigin(const Point3D& origin); //##Documentation //## @brief Get the spacing (size of a pixel). //## itkGetConstReferenceMacro(Spacing, mitk::Vector3D); //##Documentation //## @brief Get the spacing as a float[3] array. const float* GetFloatSpacing() const; //##Documentation //## @brief Set the spacing (m_Spacing) - virtual void SetSpacing(const mitk::Vector3D& aSpacing); + void SetSpacing(const mitk::Vector3D& aSpacing); //##Documentation //## @brief Get the origin as VnlVector //## //## \sa GetOrigin VnlVector GetOriginVnl() const; // ********************************** other functions ********************************** //##Documentation //## @brief Get the DICOM FrameOfReferenceID referring to the //## used world coordinate system itkGetConstMacro(FrameOfReferenceID, unsigned int); //##Documentation //## @brief Set the DICOM FrameOfReferenceID referring to the //## used world coordinate system itkSetMacro(FrameOfReferenceID, unsigned int); //##Documentation //## @brief Is this Geometry3D in a state that is valid? bool IsValid() const; // ********************************** Initialize ********************************** //##Documentation //## @brief Initialize the Geometry3D void Initialize(); void InitializeGeometry(Self * newGeometry) const; static void CopySpacingFromTransform(mitk::AffineTransform3D* transform, mitk::Vector3D& spacing, float floatSpacing[3]); // ********************************** Transformations Set/Get ********************************** // a bit of a misuse, but we want only doxygen to see the following: #ifdef DOXYGEN_SKIP //##Documentation //## @brief Get the transformation used to convert from index //## to world coordinates itkGetObjectMacro(IndexToWorldTransform, AffineTransform3D); #endif //## @brief Set the transformation used to convert from index //## to world coordinates - virtual void SetIndexToWorldTransform(mitk::AffineTransform3D* transform); + void SetIndexToWorldTransform(mitk::AffineTransform3D* transform); //##Documentation //## @brief Convenience method for setting the ITK transform //## (m_IndexToWorldTransform) via an vtkMatrix4x4 //## \sa SetIndexToWorldTransform virtual void SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4* vtkmatrix); /** Set/Get the IndexToWorldTransform */ itkGetConstObjectMacro(IndexToWorldTransform, AffineTransform3D); itkGetObjectMacro(IndexToWorldTransform, AffineTransform3D); //##Documentation //## @brief Get the m_IndexToWorldTransform as a vtkLinearTransform vtkLinearTransform* GetVtkTransform() const; //##Documentation //## @brief Set the transform to identity and origin to 0 //## virtual void SetIdentity(); // ********************************** Transformations ********************************** //##Documentation //## @brief Copy the ITK transform //## (m_IndexToWorldTransform) to the VTK transform //## \sa SetIndexToWorldTransform void TransferItkToVtkTransform(); //##Documentation //## @brief Copy the VTK transform //## to the ITK transform (m_IndexToWorldTransform) //## \sa SetIndexToWorldTransform void TransferVtkToItkTransform(); //##Documentation //## @brief Compose new IndexToWorldTransform with a given transform. //## //## This method composes m_IndexToWorldTransform with another transform, //## modifying self to be the composition of self and other. //## If the argument pre is true, then other is precomposed with self; //## that is, the resulting transformation consists of first applying //## other to the source, followed by self. If pre is false or omitted, //## then other is post-composed with self; that is the resulting //## transformation consists of first applying self to the source, //## followed by other. void Compose( const BaseGeometry::TransformType * other, bool pre = 0 ); //##Documentation //## @brief Compose new IndexToWorldTransform with a given vtkMatrix4x4. //## //## Converts the vtkMatrix4x4 into a itk-transform and calls the previous method. void Compose( const vtkMatrix4x4 * vtkmatrix, bool pre = 0 ); //##Documentation //## @brief Translate the origin by a vector //## void Translate(const Vector3D& vector); //##Documentation //##@brief executes affine operations (translate, rotate, scale) virtual void ExecuteOperation(Operation* operation); //##Documentation //## @brief Convert world coordinates (in mm) of a \em point to (continuous!) index coordinates //## \warning If you need (discrete) integer index coordinates (e.g., for iterating easily over an image), //## use WorldToIndex(const mitk::Point3D& pt_mm, itk::Index &index). //## For further information about coordinates types, please see the Geometry documentation void WorldToIndex(const mitk::Point3D& pt_mm, mitk::Point3D& pt_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## For further information about coordinates types, please see the Geometry documentation void WorldToIndex(const mitk::Vector3D& vec_mm, mitk::Vector3D& vec_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em point to (discrete!) index coordinates. //## This method rounds to integer indices! //## For further information about coordinates types, please see the Geometry documentation template void WorldToIndex(const mitk::Point3D& pt_mm, itk::Index &index) const { typedef itk::Index IndexType; mitk::Point3D pt_units; this->WorldToIndex(pt_mm, pt_units); int i, dim=index.GetIndexDimension(); if(dim>3) { index.Fill(0); dim=3; } for(i=0;i( pt_units[i] ); } } //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation void IndexToWorld(const mitk::Vector3D& vec_units, mitk::Vector3D& vec_mm) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em point to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation void IndexToWorld(const mitk::Point3D& pt_units, mitk::Point3D& pt_mm) const; //##Documentation //## @brief Convert (discrete) index coordinates of a \em point to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation template void IndexToWorld(const itk::Index &index, mitk::Point3D& pt_mm ) const { mitk::Point3D pt_units; pt_units.Fill(0); int i, dim=index.GetIndexDimension(); if(dim>3) { dim=3; } for(i=0;i void ItkPhysicalPointToWorld(const itk::Point& itkPhysicalPoint, mitk::Point3D& pt_mm) const { mitk::vtk2itk(itkPhysicalPoint, pt_mm); } //##Documentation //## @brief Deprecated for use with ITK version 3.10 or newer. //## Convert world coordinates (in mm) of a \em point to //## ITK physical coordinates (in mm, but without a possible rotation) //## //## This method is useful if you have want to access an mitk::Image //## via an itk::Image. ITK v3.8 and older did not support rotated (tilted) //## images, i.e., ITK images are always parallel to the coordinate axes. //## When accessing a (possibly rotated) mitk::Image via an itk::Image //## the rotational part of the transformation in the Geometry3D is //## simply discarded; in other word: only the origin and spacing is //## used by ITK, not the complete matrix available in MITK. //## With WorldToItkPhysicalPoint you can convert an MITK world //## coordinate (including the rotation) into a coordinate that //## can be used with the ITK image as a ITK physical coordinate //## (excluding the rotation). template void WorldToItkPhysicalPoint(const mitk::Point3D& pt_mm, itk::Point& itkPhysicalPoint) const { mitk::vtk2itk(pt_mm, itkPhysicalPoint); } // ********************************** BoundingBox ********************************** /** Get the bounding box */ itkGetConstObjectMacro(BoundingBox, BoundingBoxType); //##Documentation //## @brief Get the time bounds (in ms) itkGetConstReferenceMacro(TimeBounds, TimeBounds); // a bit of a misuse, but we want only doxygen to see the following: #ifdef DOXYGEN_SKIP //##Documentation //## @brief Get bounding box (in index/unit coordinates) itkGetConstObjectMacro(BoundingBox, BoundingBoxType); //##Documentation //## @brief Get bounding box (in index/unit coordinates) as a BoundsArrayType const BoundsArrayType GetBounds() const; #endif const BoundsArrayType GetBounds() const; //##Documentation //## \brief Set the bounding box (in index/unit coordinates) //## //## Only possible via the BoundsArray to make clear that a //## copy of the bounding-box is stored, not a reference to it. void SetBounds(const BoundsArrayType& bounds); //##Documentation //## @brief Set the bounding box (in index/unit coordinates) via a float array void SetFloatBounds(const float bounds[6]); //##Documentation //## @brief Set the bounding box (in index/unit coordinates) via a double array void SetFloatBounds(const double bounds[6]); //##Documentation //## @brief Get a VnlVector along bounding-box in the specified //## @a direction, length is spacing //## //## \sa GetAxisVector VnlVector GetMatrixColumn(unsigned int direction) const; //##Documentation //## @brief Calculates a bounding-box around the geometry relative //## to a coordinate system defined by a transform //## mitk::BoundingBox::Pointer CalculateBoundingBoxRelativeToTransform(const mitk::AffineTransform3D* transform) const; //##Documentation //## @brief Set the time bounds (in ms) - virtual void SetTimeBounds(const TimeBounds& timebounds); + void SetTimeBounds(const TimeBounds& timebounds); // ********************************** Geometry ********************************** #ifdef DOXYGEN_SKIP //##Documentation //## @brief Get the extent of the bounding box (in index/unit coordinates) //## //## To access the extent in mm use GetExtentInMM ScalarType GetExtent(unsigned int direction) const; #endif /** Get the extent of the bounding box */ ScalarType GetExtent(unsigned int direction) const; //##Documentation //## @brief Get the extent of the bounding-box in the specified @a direction in mm //## //## Equals length of GetAxisVector(direction). ScalarType GetExtentInMM(int direction) const; //##Documentation //## @brief Get vector along bounding-box in the specified @a direction in mm //## //## The length of the vector is the size of the bounding-box in the //## specified @a direction in mm //## \sa GetMatrixColumn Vector3D GetAxisVector(unsigned int direction) const; //##Documentation //## @brief Checks, if the given geometry can be converted to 2D without information loss //## e.g. when a 2D image is saved, the matrix is usually cropped to 2x2, and when you load it back to MITK //## it will be filled with standard values. This function checks, if information would be lost during this //## procedure virtual bool Is2DConvertable(); //##Documentation //## @brief Get the center of the bounding-box in mm //## Point3D GetCenter() const; //##Documentation //## @brief Get the squared length of the diagonal of the bounding-box in mm //## double GetDiagonalLength2() const; //##Documentation //## @brief Get the length of the diagonal of the bounding-box in mm //## double GetDiagonalLength() const; //##Documentation //## @brief Get the position of the corner number \a id (in world coordinates) //## //## See SetImageGeometry for how a corner is defined on images. virtual Point3D GetCornerPoint(int id) const; //##Documentation //## @brief Get the position of a corner (in world coordinates) //## //## See SetImageGeometry for how a corner is defined on images. virtual Point3D GetCornerPoint(bool xFront=true, bool yFront=true, bool zFront=true) const; //##Documentation //## @brief Set the extent of the bounding-box in the specified @a direction in mm //## //## @note This changes the matrix in the transform, @a not the bounds, which are given in units! - virtual void SetExtentInMM(int direction, ScalarType extentInMM); + void SetExtentInMM(int direction, ScalarType extentInMM); //##Documentation //## @brief Test whether the point \a p (world coordinates in mm) is //## inside the bounding box virtual bool IsInside(const mitk::Point3D& p) const; //##Documentation //## @brief Test whether the point \a p ((continous!)index coordinates in units) is //## inside the bounding box virtual bool IsIndexInside(const mitk::Point3D& index) const; //##Documentation //## @brief Convenience method for working with ITK indices template bool IsIndexInside(const itk::Index &index) const; protected: // ********************************** Constructor ********************************** BaseGeometry(); BaseGeometry(const BaseGeometry& other); virtual ~BaseGeometry(); itkGetConstMacro(IndexToWorldTransformLastModified, unsigned long); void BackTransform(const mitk::Point3D& in, mitk::Point3D& out) const; //Without redundant parameter Point3D void BackTransform(const mitk::Vector3D& in, mitk::Vector3D& out) const; //##Documentation //## @brief Deprecated void BackTransform(const mitk::Point3D& at, const mitk::Vector3D& in, mitk::Vector3D& out) const; static const std::string GetTransformAsString( TransformType* transformType ); //Internal Functions virtual bool InternPostIsValid() const; virtual void InternPostInitialize() {}; virtual void InternPostInitializeGeometry(Self * newGeometry) const{}; virtual void InternPreSetBounds(const BoundsArrayType& bounds); + virtual void InternPostSetExtentInMM(int direction, ScalarType extentInMM); + + virtual void InternPostSetTimeBounds(const TimeBounds& timebounds); + + virtual void InternPreSetIndexToWorldTransform(mitk::AffineTransform3D* transform); + virtual void InternPostSetIndexToWorldTransform(mitk::AffineTransform3D* transform); + + virtual void InternPreSetSpacing(const mitk::Vector3D& aSpacing); + void InternSetSpacing(const mitk::Vector3D& aSpacing); + // ********************************** Variables ********************************** AffineTransform3D::Pointer m_IndexToWorldTransform; vtkMatrixToLinearTransform* m_VtkIndexToWorldTransform; vtkMatrix4x4* m_VtkMatrix; bool m_Valid; unsigned int m_FrameOfReferenceID; mutable mitk::TimeBounds m_TimeBounds; mutable BoundingBoxPointer m_BoundingBox; //##Documentation //## @brief Origin, i.e. upper-left corner of the plane //## Point3D m_Origin; //##Documentation //## @brief Spacing of the data. Only significant if the geometry describes //## an Image (m_ImageGeometry==true). mitk::Vector3D m_Spacing; static const unsigned int NDimensions = 3; mutable TransformType::Pointer m_InvertedTransform; //this was private mutable unsigned long m_IndexToWorldTransformLastModified; //this was private float m_FloatSpacing[3]; //this was private // DEPRECATED(VnlQuaternionType m_RotationQuaternion); //this was private }; // ********************************** Equal Functions ********************************** // // Static compare functions mainly for testing // /** * @brief Equal A function comparing two bounding boxes (BoundingBoxType) for beeing identical. * * @ingroup MITKTestingAPI * * The function compares the bounds (elementwise). * * The parameter eps is a tolarence value for all methods which are internally used for comparion. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITK_CORE_EXPORT bool Equal( const mitk::BaseGeometry::BoundingBoxType *leftHandSide, const mitk::BaseGeometry::BoundingBoxType *rightHandSide, mitk::ScalarType eps, bool verbose); //ToDo // // Static compare functions mainly for testing // /** * @brief Equal A function comparing two geometries for beeing identical. * * @ingroup MITKTestingAPI * * The function compares the spacing, origin, axisvectors, extents, the matrix of the * IndexToWorldTransform (elementwise), the bounding (elementwise) and the ImageGeometry flag. * * The parameter eps is a tolarence value for all methods which are internally used for comparion. * If you want to use different tolarance values for different parts of the geometry, feel free to use * the other comparison methods and write your own implementation of Equal. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITK_CORE_EXPORT bool Equal(const mitk::BaseGeometry* leftHandSide, const mitk::BaseGeometry* rightHandSide, mitk::ScalarType eps, bool verbose); //ToDo /** * @brief Equal A function comparing two transforms (TransformType) for beeing identical. * * @ingroup MITKTestingAPI * * The function compares the IndexToWorldTransform (elementwise). * * The parameter eps is a tolarence value for all methods which are internally used for comparion. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITK_CORE_EXPORT bool Equal(const mitk::BaseGeometry::TransformType *leftHandSide, const mitk::BaseGeometry::TransformType *rightHandSide, mitk::ScalarType eps, bool verbose); //ToDo } // namespace mitk #endif /* BaseGeometry_H_HEADER_INCLUDED */ diff --git a/Core/Code/DataManagement/mitkGeometry2D.cpp b/Core/Code/DataManagement/mitkGeometry2D.cpp index 2418c54ce0..67d7668663 100644 --- a/Core/Code/DataManagement/mitkGeometry2D.cpp +++ b/Core/Code/DataManagement/mitkGeometry2D.cpp @@ -1,268 +1,264 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkGeometry2D.h" #include mitk::Geometry2D::Geometry2D() : m_ScaleFactorMMPerUnitX( 1.0 ), m_ScaleFactorMMPerUnitY( 1.0 ), m_ReferenceGeometry( NULL ) { } mitk::Geometry2D::Geometry2D(const Geometry2D& other) : Geometry3D(other), m_ScaleFactorMMPerUnitX( other.m_ScaleFactorMMPerUnitX), m_ScaleFactorMMPerUnitY( other.m_ScaleFactorMMPerUnitY), m_ReferenceGeometry( other.m_ReferenceGeometry ) { } mitk::Geometry2D::~Geometry2D() { } void - mitk::Geometry2D::SetIndexToWorldTransform( + mitk::Geometry2D::InternPostSetIndexToWorldTransform( mitk::AffineTransform3D* transform) { - Superclass::SetIndexToWorldTransform(transform); - m_ScaleFactorMMPerUnitX=GetExtentInMM(0)/GetExtent(0); m_ScaleFactorMMPerUnitY=GetExtentInMM(1)/GetExtent(1); assert(m_ScaleFactorMMPerUnitX(m_BoundingBox.GetPointer())->IsInside(pt3d_units); } void mitk::Geometry2D::Map(const mitk::Point2D &pt2d_mm, mitk::Point3D &pt3d_mm) const { Point3D pt3d_units; pt3d_units[0]=pt2d_mm[0]/m_ScaleFactorMMPerUnitX; pt3d_units[1]=pt2d_mm[1]/m_ScaleFactorMMPerUnitY; pt3d_units[2]=0; pt3d_mm = GetParametricTransform()->TransformPoint(pt3d_units); } void mitk::Geometry2D::IndexToWorld( const mitk::Point2D &/*pt_units*/, mitk::Point2D &/*pt_mm*/) const { itkExceptionMacro(<< "No general transform possible (only affine) ==> no general" \ " IndexToWorld(const mitk::Point2D &pt_mm, mitk::Point2D &pt_units)" \ " possible. Has to be implemented in sub-class."); } void mitk::Geometry2D::WorldToIndex( const mitk::Point2D &/*pt_mm*/, mitk::Point2D &/*pt_units*/) const { itkExceptionMacro(<< "No general back transform possible (only affine) ==> no general" \ " WorldToIndex(const mitk::Point2D &pt_mm, mitk::Point2D &pt_units)" \ " possible. Has to be implemented in sub-class."); } void mitk::Geometry2D::IndexToWorld(const mitk::Point2D &/*atPt2d_units*/, const mitk::Vector2D &/*vec_units*/, mitk::Vector2D &/*vec_mm*/) const { itkExceptionMacro(<< "No general transform possible (only affine) ==> no general" \ " IndexToWorld(const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units)" \ " possible. Has to be implemented in sub-class."); } void mitk::Geometry2D::WorldToIndex(const mitk::Point2D &/*atPt2d_mm*/, const mitk::Vector2D &/*vec_mm*/, mitk::Vector2D &/*vec_units*/) const { itkExceptionMacro(<< "No general back transform possible (only affine) ==> no general" \ " WorldToIndex(const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units)" \ " possible. Has to be implemented in sub-class."); } void mitk::Geometry2D::SetSizeInUnits(mitk::ScalarType width, mitk::ScalarType height) { ScalarType bounds[6]={0, width, 0, height, 0, 1}; ScalarType extent, newextentInMM; if(GetExtent(0)>0) { extent = GetExtent(0); if(width>extent) newextentInMM = GetExtentInMM(0)/width*extent; else newextentInMM = GetExtentInMM(0)*extent/width; SetExtentInMM(0, newextentInMM); } if(GetExtent(1)>0) { extent = GetExtent(1); if(width>extent) newextentInMM = GetExtentInMM(1)/height*extent; else newextentInMM = GetExtentInMM(1)*extent/height; SetExtentInMM(1, newextentInMM); } SetBounds(bounds); } bool mitk::Geometry2D::Project( const mitk::Point3D &pt3d_mm, mitk::Point3D &projectedPt3d_mm) const { assert(m_BoundingBox.IsNotNull()); Point3D pt3d_units; BackTransform(pt3d_mm, pt3d_units); pt3d_units[2] = 0; projectedPt3d_mm = GetParametricTransform()->TransformPoint(pt3d_units); return const_cast(m_BoundingBox.GetPointer())->IsInside(pt3d_units); } bool mitk::Geometry2D::Project(const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const { assert(m_BoundingBox.IsNotNull()); Vector3D vec3d_units; BackTransform(vec3d_mm, vec3d_units); vec3d_units[2] = 0; projectedVec3d_mm = GetParametricTransform()->TransformVector(vec3d_units); return true; } bool mitk::Geometry2D::Project(const mitk::Point3D & atPt3d_mm, const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const { MITK_WARN << "Deprecated function! Call Project(vec3D,vec3D) instead."; assert(m_BoundingBox.IsNotNull()); Vector3D vec3d_units; BackTransform(atPt3d_mm, vec3d_mm, vec3d_units); vec3d_units[2] = 0; projectedVec3d_mm = GetParametricTransform()->TransformVector(vec3d_units); Point3D pt3d_units; BackTransform(atPt3d_mm, pt3d_units); return const_cast(m_BoundingBox.GetPointer())->IsInside(pt3d_units); } bool mitk::Geometry2D::Map(const mitk::Point3D & atPt3d_mm, const mitk::Vector3D &vec3d_mm, mitk::Vector2D &vec2d_mm) const { Point2D pt2d_mm_start, pt2d_mm_end; Point3D pt3d_mm_end; bool inside=Map(atPt3d_mm, pt2d_mm_start); pt3d_mm_end = atPt3d_mm+vec3d_mm; inside&=Map(pt3d_mm_end, pt2d_mm_end); vec2d_mm=pt2d_mm_end-pt2d_mm_start; return inside; } void mitk::Geometry2D::Map(const mitk::Point2D &/*atPt2d_mm*/, const mitk::Vector2D &/*vec2d_mm*/, mitk::Vector3D &/*vec3d_mm*/) const { //@todo implement parallel to the other Map method! assert(false); } mitk::ScalarType mitk::Geometry2D::SignedDistance(const mitk::Point3D& pt3d_mm) const { Point3D projectedPoint; Project(pt3d_mm, projectedPoint); Vector3D direction = pt3d_mm-projectedPoint; ScalarType distance = direction.GetNorm(); if(IsAbove(pt3d_mm) == false) distance*=-1.0; return distance; } bool mitk::Geometry2D::IsAbove(const mitk::Point3D& pt3d_mm) const { Point3D pt3d_units; Geometry3D::WorldToIndex(pt3d_mm, pt3d_units); return (pt3d_units[2] > m_BoundingBox->GetBounds()[4]); } itk::LightObject::Pointer mitk::Geometry2D::InternalClone() const { Self::Pointer newGeometry = new Geometry2D(*this); newGeometry->UnRegister(); return newGeometry.GetPointer(); } void mitk::Geometry2D::PrintSelf(std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf(os,indent); os << indent << " ScaleFactorMMPerUnitX: " << m_ScaleFactorMMPerUnitX << std::endl; os << indent << " ScaleFactorMMPerUnitY: " << m_ScaleFactorMMPerUnitY << std::endl; } void mitk::Geometry2D::SetReferenceGeometry( mitk::Geometry3D *geometry ) { m_ReferenceGeometry = geometry; } mitk::Geometry3D * mitk::Geometry2D::GetReferenceGeometry() const { return m_ReferenceGeometry; } bool mitk::Geometry2D::HasReferenceGeometry() const { return ( m_ReferenceGeometry != NULL ); } diff --git a/Core/Code/DataManagement/mitkGeometry2D.h b/Core/Code/DataManagement/mitkGeometry2D.h index 10b62fcf89..bf7e55a24a 100644 --- a/Core/Code/DataManagement/mitkGeometry2D.h +++ b/Core/Code/DataManagement/mitkGeometry2D.h @@ -1,269 +1,269 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef GEOMETRY2D_H_HEADER_INCLUDED_C1F4D8E0 #define GEOMETRY2D_H_HEADER_INCLUDED_C1F4D8E0 #include #include "mitkGeometry3D.h" namespace mitk { -/** - * \brief Describes the geometry of a two-dimensional object - * - * Describes a two-dimensional manifold, i.e., to put it simply, - * an object that can be described using a 2D coordinate-system. - * - * Geometry2D can map points between 3D world coordinates - * (in mm) and the described 2D coordinate-system (in mm) by first projecting - * the 3D point onto the 2D manifold and then calculating the 2D-coordinates - * (in mm). These 2D-mm-coordinates can be further converted into - * 2D-unit-coordinates (e.g., pixels), giving a parameter representation of - * the object with parameter values inside a rectangle - * (e.g., [0,0]..[width, height]), which is the bounding box (bounding range - * in z-direction always [0]..[1]). - * - * A Geometry2D describes the 2D representation within a 3D object and is - * therefore itself a Geometry3D (derived from Geometry3D). For example, - * a single CT-image (slice) is 2D in the sense that you can access the - * pixels using 2D-coordinates, but is also 3D, as the pixels are really - * voxels, thus have an extension (thickness) in the 3rd dimension. - * - * Most often, instances of Geometry2D will be used to descibe a plane, - * which is represented by the sub-class PlaneGeometry, but curved - * surfaces are also possible. - * - * Optionally, a reference Geometry3D can be specified, which usually would - * be the geometry associated with the underlying dataset. This is currently - * used for calculating the intersection of inclined / rotated planes - * (represented as Geometry2D) with the bounding box of the associated - * Geometry3D. - * - * \warning The Geometry2Ds are not necessarily up-to-date and not even - * initialized. As described in the previous paragraph, one of the - * Generate-/Copy-/UpdateOutputInformation methods have to initialize it. - * mitk::BaseData::GetGeometry2D() makes sure, that the Geometry2D is - * up-to-date before returning it (by setting the update extent appropriately - * and calling UpdateOutputInformation). - * - * Rule: everything is in mm (or ms for temporal information) if not - * stated otherwise. - * \ingroup Geometry - */ -class MITK_CORE_EXPORT Geometry2D : public mitk::Geometry3D -{ -public: - mitkClassMacro(Geometry2D, mitk::Geometry3D); - itkNewMacro(Self); - - /** - * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D - * geometry. The result is a 2D point in mm (\a pt2d_mm). - * - * The result is a 2D point in mm (\a pt2d_mm) relative to the upper-left - * corner of the geometry. To convert this point into units (e.g., pixels - * in case of an image), use WorldToIndex. - * \return true projection was possible - * \sa Project(const mitk::Point3D &pt3d_mm, mitk::Point3D - * &projectedPt3d_mm) - */ - virtual bool Map(const mitk::Point3D &pt3d_mm, mitk::Point2D &pt2d_mm) const; - - /** - * \brief Converts a 2D point given in mm (\a pt2d_mm) relative to the - * upper-left corner of the geometry into the corresponding - * world-coordinate (a 3D point in mm, \a pt3d_mm). - * - * To convert a 2D point given in units (e.g., pixels in case of an - * image) into a 2D point given in mm (as required by this method), use - * IndexToWorld. - */ - virtual void Map(const mitk::Point2D &pt2d_mm, mitk::Point3D &pt3d_mm) const; - - /** - * \brief Convert a 2D point given in units (e.g., pixels in case of an - * image) into a 2D point given in mm - */ - virtual void IndexToWorld( - const mitk::Point2D &pt_units, mitk::Point2D &pt_mm) const; - - /** - * \brief Convert a 2D point given in mm into a 2D point given in mm - * (e.g., pixels in case of an image) - */ - virtual void WorldToIndex( - const mitk::Point2D &pt_mm, mitk::Point2D &pt_units) const; - - /** - * \brief Convert a 2D vector given in units (e.g., pixels in case of an - * image) into a 2D vector given in mm - * \warning strange: in contrast to vtkTransform the class itk::Transform - * does not have the parameter, \em where the vector that is to be - * transformed is located. This method here should also need this - * information for general transforms. - */ - virtual void IndexToWorld( - const mitk::Point2D &atPt2d_units, const mitk::Vector2D &vec_units, - mitk::Vector2D &vec_mm) const; - - /** - * \brief Convert a 2D vector given in mm into a 2D point vector in mm - * (e.g., pixels in case of an image) - * \warning strange: in contrast to vtkTransform the class itk::Transform - * does not have the parameter, \em where the vector that is to be - * transformed is located. This method here should also need this - * information for general transforms. - */ - virtual void WorldToIndex( - const mitk::Point2D &atPt2d_mm, const mitk::Vector2D &vec_mm, - mitk::Vector2D &vec_units) const; - /** - * \brief Set the width and height of this 2D-geometry in units by calling - * SetBounds. This does \a not change the extent in mm! - * - * For an image, this is the number of pixels in x-/y-direction. - * \note In contrast to calling SetBounds directly, this does \a not change - * the extent in mm! - */ - virtual void SetSizeInUnits(mitk::ScalarType width, mitk::ScalarType height); - - /** - * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D - * geometry. The result is a 3D point in mm (\a projectedPt3d_mm). - * - * \return true projection was possible - */ - virtual bool Project(const mitk::Point3D &pt3d_mm, - mitk::Point3D &projectedPt3d_mm) const; - - /** - * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D - * geometry. The result is a 2D vector in mm (\a vec2d_mm). - * - * The result is a 2D vector in mm (\a vec2d_mm) relative to the - * upper-left - * corner of the geometry. To convert this point into units (e.g., pixels - * in case of an image), use WorldToIndex. - * \return true projection was possible - * \sa Project(const mitk::Vector3D &vec3d_mm, mitk::Vector3D - * &projectedVec3d_mm) - */ - virtual bool Map(const mitk::Point3D & atPt3d_mm, - const mitk::Vector3D &vec3d_mm, mitk::Vector2D &vec2d_mm) const; - - /** - * \brief Converts a 2D vector given in mm (\a vec2d_mm) relative to the - * upper-left corner of the geometry into the corresponding - * world-coordinate (a 3D vector in mm, \a vec3d_mm). - * - * To convert a 2D vector given in units (e.g., pixels in case of an - * image) into a 2D vector given in mm (as required by this method), use - * IndexToWorld. - */ - virtual void Map(const mitk::Point2D & atPt2d_mm, - const mitk::Vector2D &vec2d_mm, mitk::Vector3D &vec3d_mm) const; - - /** - * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D - * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). - * - * DEPRECATED. Use Project(vector,vector) instead - * - * \return true projection was possible - */ - virtual bool Project(const mitk::Point3D & atPt3d_mm, - const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; - - /** - * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D - * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). + * \brief Describes the geometry of a two-dimensional object + * + * Describes a two-dimensional manifold, i.e., to put it simply, + * an object that can be described using a 2D coordinate-system. + * + * Geometry2D can map points between 3D world coordinates + * (in mm) and the described 2D coordinate-system (in mm) by first projecting + * the 3D point onto the 2D manifold and then calculating the 2D-coordinates + * (in mm). These 2D-mm-coordinates can be further converted into + * 2D-unit-coordinates (e.g., pixels), giving a parameter representation of + * the object with parameter values inside a rectangle + * (e.g., [0,0]..[width, height]), which is the bounding box (bounding range + * in z-direction always [0]..[1]). + * + * A Geometry2D describes the 2D representation within a 3D object and is + * therefore itself a Geometry3D (derived from Geometry3D). For example, + * a single CT-image (slice) is 2D in the sense that you can access the + * pixels using 2D-coordinates, but is also 3D, as the pixels are really + * voxels, thus have an extension (thickness) in the 3rd dimension. * - * \return true projection was possible + * Most often, instances of Geometry2D will be used to descibe a plane, + * which is represented by the sub-class PlaneGeometry, but curved + * surfaces are also possible. + * + * Optionally, a reference Geometry3D can be specified, which usually would + * be the geometry associated with the underlying dataset. This is currently + * used for calculating the intersection of inclined / rotated planes + * (represented as Geometry2D) with the bounding box of the associated + * Geometry3D. + * + * \warning The Geometry2Ds are not necessarily up-to-date and not even + * initialized. As described in the previous paragraph, one of the + * Generate-/Copy-/UpdateOutputInformation methods have to initialize it. + * mitk::BaseData::GetGeometry2D() makes sure, that the Geometry2D is + * up-to-date before returning it (by setting the update extent appropriately + * and calling UpdateOutputInformation). + * + * Rule: everything is in mm (or ms for temporal information) if not + * stated otherwise. + * \ingroup Geometry */ - virtual bool Project( const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; - - /** - * \brief Distance of the point from the geometry - * (bounding-box \em not considered) - * - */ - inline ScalarType Distance(const Point3D& pt3d_mm) const + class MITK_CORE_EXPORT Geometry2D : public mitk::Geometry3D { - return fabs(SignedDistance(pt3d_mm)); - } - - /** - * \brief Signed distance of the point from the geometry - * (bounding-box \em not considered) - * - */ - virtual ScalarType SignedDistance(const Point3D& pt3d_mm) const; - - /** - * \brief Test if the point is above the geometry - * (bounding-box \em not considered) - * - */ - virtual bool IsAbove(const Point3D& pt3d_mm) const; - - virtual void SetIndexToWorldTransform(mitk::AffineTransform3D* transform); - - virtual void SetExtentInMM(int direction, ScalarType extentInMM); - - virtual itk::LightObject::Pointer InternalClone() const; - - /** - * \brief Set the geometrical frame of reference in which this Geometry2D - * is placed. - * - * This would usually be the Geometry3D of the underlying dataset, but - * setting it is optional. - */ - void SetReferenceGeometry( mitk::Geometry3D *geometry ); - - /** - * \brief Get the geometrical frame of reference for this Geometry2D. - */ - Geometry3D *GetReferenceGeometry() const; - bool HasReferenceGeometry() const; - -protected: - Geometry2D(); - - Geometry2D(const Geometry2D& other); - - virtual ~Geometry2D(); - - virtual void PrintSelf(std::ostream& os, itk::Indent indent) const; - - /** - * \brief factor to convert x-coordinates from mm to units and vice versa - * - */ - mutable mitk::ScalarType m_ScaleFactorMMPerUnitX; - - /** - * \brief factor to convert y-coordinates from mm to units and vice versa - * - */ - mutable mitk::ScalarType m_ScaleFactorMMPerUnitY; - - mitk::Geometry3D *m_ReferenceGeometry; -}; + public: + mitkClassMacro(Geometry2D, mitk::Geometry3D); + itkNewMacro(Self); + + /** + * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D + * geometry. The result is a 2D point in mm (\a pt2d_mm). + * + * The result is a 2D point in mm (\a pt2d_mm) relative to the upper-left + * corner of the geometry. To convert this point into units (e.g., pixels + * in case of an image), use WorldToIndex. + * \return true projection was possible + * \sa Project(const mitk::Point3D &pt3d_mm, mitk::Point3D + * &projectedPt3d_mm) + */ + virtual bool Map(const mitk::Point3D &pt3d_mm, mitk::Point2D &pt2d_mm) const; + + /** + * \brief Converts a 2D point given in mm (\a pt2d_mm) relative to the + * upper-left corner of the geometry into the corresponding + * world-coordinate (a 3D point in mm, \a pt3d_mm). + * + * To convert a 2D point given in units (e.g., pixels in case of an + * image) into a 2D point given in mm (as required by this method), use + * IndexToWorld. + */ + virtual void Map(const mitk::Point2D &pt2d_mm, mitk::Point3D &pt3d_mm) const; + + /** + * \brief Convert a 2D point given in units (e.g., pixels in case of an + * image) into a 2D point given in mm + */ + virtual void IndexToWorld( + const mitk::Point2D &pt_units, mitk::Point2D &pt_mm) const; + + /** + * \brief Convert a 2D point given in mm into a 2D point given in mm + * (e.g., pixels in case of an image) + */ + virtual void WorldToIndex( + const mitk::Point2D &pt_mm, mitk::Point2D &pt_units) const; + + /** + * \brief Convert a 2D vector given in units (e.g., pixels in case of an + * image) into a 2D vector given in mm + * \warning strange: in contrast to vtkTransform the class itk::Transform + * does not have the parameter, \em where the vector that is to be + * transformed is located. This method here should also need this + * information for general transforms. + */ + virtual void IndexToWorld( + const mitk::Point2D &atPt2d_units, const mitk::Vector2D &vec_units, + mitk::Vector2D &vec_mm) const; + + /** + * \brief Convert a 2D vector given in mm into a 2D point vector in mm + * (e.g., pixels in case of an image) + * \warning strange: in contrast to vtkTransform the class itk::Transform + * does not have the parameter, \em where the vector that is to be + * transformed is located. This method here should also need this + * information for general transforms. + */ + virtual void WorldToIndex( + const mitk::Point2D &atPt2d_mm, const mitk::Vector2D &vec_mm, + mitk::Vector2D &vec_units) const; + + /** + * \brief Set the width and height of this 2D-geometry in units by calling + * SetBounds. This does \a not change the extent in mm! + * + * For an image, this is the number of pixels in x-/y-direction. + * \note In contrast to calling SetBounds directly, this does \a not change + * the extent in mm! + */ + virtual void SetSizeInUnits(mitk::ScalarType width, mitk::ScalarType height); + + /** + * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D + * geometry. The result is a 3D point in mm (\a projectedPt3d_mm). + * + * \return true projection was possible + */ + virtual bool Project(const mitk::Point3D &pt3d_mm, + mitk::Point3D &projectedPt3d_mm) const; + + /** + * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D + * geometry. The result is a 2D vector in mm (\a vec2d_mm). + * + * The result is a 2D vector in mm (\a vec2d_mm) relative to the + * upper-left + * corner of the geometry. To convert this point into units (e.g., pixels + * in case of an image), use WorldToIndex. + * \return true projection was possible + * \sa Project(const mitk::Vector3D &vec3d_mm, mitk::Vector3D + * &projectedVec3d_mm) + */ + virtual bool Map(const mitk::Point3D & atPt3d_mm, + const mitk::Vector3D &vec3d_mm, mitk::Vector2D &vec2d_mm) const; + + /** + * \brief Converts a 2D vector given in mm (\a vec2d_mm) relative to the + * upper-left corner of the geometry into the corresponding + * world-coordinate (a 3D vector in mm, \a vec3d_mm). + * + * To convert a 2D vector given in units (e.g., pixels in case of an + * image) into a 2D vector given in mm (as required by this method), use + * IndexToWorld. + */ + virtual void Map(const mitk::Point2D & atPt2d_mm, + const mitk::Vector2D &vec2d_mm, mitk::Vector3D &vec3d_mm) const; + + /** + * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D + * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). + * + * DEPRECATED. Use Project(vector,vector) instead + * + * \return true projection was possible + */ + virtual bool Project(const mitk::Point3D & atPt3d_mm, + const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; + + /** + * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D + * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). + * + * \return true projection was possible + */ + virtual bool Project( const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; + + /** + * \brief Distance of the point from the geometry + * (bounding-box \em not considered) + * + */ + inline ScalarType Distance(const Point3D& pt3d_mm) const + { + return fabs(SignedDistance(pt3d_mm)); + } + + /** + * \brief Signed distance of the point from the geometry + * (bounding-box \em not considered) + * + */ + virtual ScalarType SignedDistance(const Point3D& pt3d_mm) const; + + /** + * \brief Test if the point is above the geometry + * (bounding-box \em not considered) + * + */ + virtual bool IsAbove(const Point3D& pt3d_mm) const; + + virtual itk::LightObject::Pointer InternalClone() const; + + /** + * \brief Set the geometrical frame of reference in which this Geometry2D + * is placed. + * + * This would usually be the Geometry3D of the underlying dataset, but + * setting it is optional. + */ + void SetReferenceGeometry( mitk::Geometry3D *geometry ); + + /** + * \brief Get the geometrical frame of reference for this Geometry2D. + */ + Geometry3D *GetReferenceGeometry() const; + bool HasReferenceGeometry() const; + + protected: + Geometry2D(); + + Geometry2D(const Geometry2D& other); + + virtual ~Geometry2D(); + + virtual void PrintSelf(std::ostream& os, itk::Indent indent) const; + + virtual void InternPostSetExtentInMM(int direction, ScalarType extentInMM); + + virtual void InternPostSetIndexToWorldTransform(mitk::AffineTransform3D* transform); + + /** + * \brief factor to convert x-coordinates from mm to units and vice versa + * + */ + mutable mitk::ScalarType m_ScaleFactorMMPerUnitX; + + /** + * \brief factor to convert y-coordinates from mm to units and vice versa + * + */ + mutable mitk::ScalarType m_ScaleFactorMMPerUnitY; + + mitk::Geometry3D *m_ReferenceGeometry; + }; } // namespace mitk #endif /* GEOMETRY2D_H_HEADER_INCLUDED_C1F4D8E0 */ diff --git a/Core/Code/DataManagement/mitkPlaneGeometry.cpp b/Core/Code/DataManagement/mitkPlaneGeometry.cpp index 560b029def..c4caad38f1 100644 --- a/Core/Code/DataManagement/mitkPlaneGeometry.cpp +++ b/Core/Code/DataManagement/mitkPlaneGeometry.cpp @@ -1,735 +1,727 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlaneGeometry.h" #include "mitkPlaneOperation.h" #include "mitkInteractionConst.h" #include "mitkLine.h" #include #include namespace mitk { - mitk::PlaneGeometry::PlaneGeometry() + PlaneGeometry::PlaneGeometry() { Initialize(); } - mitk::PlaneGeometry::~PlaneGeometry() + PlaneGeometry::~PlaneGeometry() { } - void - PlaneGeometry::Initialize() - { - Superclass::Initialize(); - } - void PlaneGeometry::EnsurePerpendicularNormal(mitk::AffineTransform3D *transform) { //ensure row(2) of transform to be perpendicular to plane, keep length. VnlVector normal = vnl_cross_3d( transform->GetMatrix().GetVnlMatrix().get_column(0), transform->GetMatrix().GetVnlMatrix().get_column(1) ); normal.normalize(); ScalarType len = transform->GetMatrix() .GetVnlMatrix().get_column(2).two_norm(); if (len==0) len = 1; normal*=len; Matrix3D matrix = transform->GetMatrix(); matrix.GetVnlMatrix().set_column(2, normal); transform->SetMatrix(matrix); } void - PlaneGeometry::SetIndexToWorldTransform(mitk::AffineTransform3D *transform) + PlaneGeometry::InternPreSetIndexToWorldTransform(mitk::AffineTransform3D *transform) { EnsurePerpendicularNormal(transform); - - Superclass::SetIndexToWorldTransform(transform); } void PlaneGeometry::InternPreSetBounds(const BoundingBox::BoundsArrayType &bounds) { //currently the unit rectangle must be starting at the origin [0,0] assert(bounds[0]==0); assert(bounds[2]==0); //the unit rectangle must be two-dimensional assert(bounds[1]>0); assert(bounds[3]>0); } void PlaneGeometry::IndexToWorld( const Point2D &pt_units, Point2D &pt_mm ) const { pt_mm[0]=m_ScaleFactorMMPerUnitX*pt_units[0]; pt_mm[1]=m_ScaleFactorMMPerUnitY*pt_units[1]; } void PlaneGeometry::WorldToIndex( const Point2D &pt_mm, Point2D &pt_units ) const { pt_units[0]=pt_mm[0]*(1.0/m_ScaleFactorMMPerUnitX); pt_units[1]=pt_mm[1]*(1.0/m_ScaleFactorMMPerUnitY); } void PlaneGeometry::IndexToWorld( const Point2D & /*atPt2d_units*/, const Vector2D &vec_units, Vector2D &vec_mm) const { MITK_WARN<<"Warning! Call of the deprecated function PlaneGeometry::IndexToWorld(point, vec, vec). Use PlaneGeometry::IndexToWorld(vec, vec) instead!"; this->IndexToWorld(vec_units, vec_mm); } void PlaneGeometry::IndexToWorld(const Vector2D &vec_units, Vector2D &vec_mm) const { vec_mm[0] = m_ScaleFactorMMPerUnitX * vec_units[0]; vec_mm[1] = m_ScaleFactorMMPerUnitY * vec_units[1]; } void PlaneGeometry::WorldToIndex( const Point2D & /*atPt2d_mm*/, const Vector2D &vec_mm, Vector2D &vec_units) const { MITK_WARN<<"Warning! Call of the deprecated function PlaneGeometry::WorldToIndex(point, vec, vec). Use PlaneGeometry::WorldToIndex(vec, vec) instead!"; this->WorldToIndex(vec_mm, vec_units); } void PlaneGeometry::WorldToIndex( const Vector2D &vec_mm, Vector2D &vec_units) const { vec_units[0] = vec_mm[0] * ( 1.0 / m_ScaleFactorMMPerUnitX ); vec_units[1] = vec_mm[1] * ( 1.0 / m_ScaleFactorMMPerUnitY ); } void PlaneGeometry::InitializeStandardPlane( mitk::ScalarType width, ScalarType height, const Vector3D & spacing, PlaneGeometry::PlaneOrientation planeorientation, ScalarType zPosition, bool frontside, bool rotated ) { AffineTransform3D::Pointer transform; transform = AffineTransform3D::New(); AffineTransform3D::MatrixType matrix; AffineTransform3D::MatrixType::InternalMatrixType &vnlmatrix = matrix.GetVnlMatrix(); vnlmatrix.set_identity(); vnlmatrix(0,0) = spacing[0]; vnlmatrix(1,1) = spacing[1]; vnlmatrix(2,2) = spacing[2]; transform->SetIdentity(); transform->SetMatrix(matrix); InitializeStandardPlane(width, height, transform.GetPointer(), planeorientation, zPosition, frontside, rotated); } void PlaneGeometry::InitializeStandardPlane( mitk::ScalarType width, ScalarType height, const AffineTransform3D* transform, PlaneGeometry::PlaneOrientation planeorientation, ScalarType zPosition, bool frontside, bool rotated ) { Superclass::Initialize(); //construct standard view Point3D origin; VnlVector rightDV(3), bottomDV(3); origin.Fill(0); int normalDirection; switch(planeorientation) { case Axial: if(frontside) { if(rotated==false) { FillVector3D(origin, 0, 0, zPosition); FillVector3D(rightDV, 1, 0, 0); FillVector3D(bottomDV, 0, 1, 0); } else { FillVector3D(origin, width, height, zPosition); FillVector3D(rightDV, -1, 0, 0); FillVector3D(bottomDV, 0, -1, 0); } } else { if(rotated==false) { FillVector3D(origin, width, 0, zPosition); FillVector3D(rightDV, -1, 0, 0); FillVector3D(bottomDV, 0, 1, 0); } else { FillVector3D(origin, 0, height, zPosition); FillVector3D(rightDV, 1, 0, 0); FillVector3D(bottomDV, 0, -1, 0); } } normalDirection = 2; break; case Frontal: if(frontside) { if(rotated==false) { FillVector3D(origin, 0, zPosition, 0); FillVector3D(rightDV, 1, 0, 0); FillVector3D(bottomDV, 0, 0, 1); } else { FillVector3D(origin, width, zPosition, height); FillVector3D(rightDV, -1, 0, 0); FillVector3D(bottomDV, 0, 0, -1); } } else { if(rotated==false) { FillVector3D(origin, width, zPosition, 0); FillVector3D(rightDV, -1, 0, 0); FillVector3D(bottomDV, 0, 0, 1); } else { FillVector3D(origin, 0, zPosition, height); FillVector3D(rightDV, 1, 0, 0); FillVector3D(bottomDV, 0, 0, -1); } } normalDirection = 1; break; case Sagittal: if(frontside) { if(rotated==false) { FillVector3D(origin, zPosition, 0, 0); FillVector3D(rightDV, 0, 1, 0); FillVector3D(bottomDV, 0, 0, 1); } else { FillVector3D(origin, zPosition, width, height); FillVector3D(rightDV, 0, -1, 0); FillVector3D(bottomDV, 0, 0, -1); } } else { if(rotated==false) { FillVector3D(origin, zPosition, width, 0); FillVector3D(rightDV, 0, -1, 0); FillVector3D(bottomDV, 0, 0, 1); } else { FillVector3D(origin, zPosition, 0, height); FillVector3D(rightDV, 0, 1, 0); FillVector3D(bottomDV, 0, 0, -1); } } normalDirection = 0; break; default: itkExceptionMacro("unknown PlaneOrientation"); } if ( transform != NULL ) { origin = transform->TransformPoint( origin ); rightDV = transform->TransformVector( rightDV ); bottomDV = transform->TransformVector( bottomDV ); } ScalarType bounds[6]= { 0, width, 0, height, 0, 1 }; this->SetBounds( bounds ); if ( transform == NULL ) { this->SetMatrixByVectors( rightDV, bottomDV ); } else { this->SetMatrixByVectors( rightDV, bottomDV, transform->GetMatrix().GetVnlMatrix() .get_column(normalDirection).magnitude() ); } this->SetOrigin(origin); } void PlaneGeometry::InitializeStandardPlane( const Geometry3D *geometry3D, PlaneOrientation planeorientation, ScalarType zPosition, bool frontside, bool rotated ) { this->SetReferenceGeometry( const_cast< Geometry3D * >( geometry3D ) ); ScalarType width, height; const BoundingBox::BoundsArrayType& boundsarray = geometry3D->GetBoundingBox()->GetBounds(); Vector3D originVector; FillVector3D(originVector, boundsarray[0], boundsarray[2], boundsarray[4]); if(geometry3D->GetImageGeometry()) { FillVector3D( originVector, originVector[0] - 0.5, originVector[1] - 0.5, originVector[2] - 0.5 ); } switch(planeorientation) { case Axial: width = geometry3D->GetExtent(0); height = geometry3D->GetExtent(1); break; case Frontal: width = geometry3D->GetExtent(0); height = geometry3D->GetExtent(2); break; case Sagittal: width = geometry3D->GetExtent(1); height = geometry3D->GetExtent(2); break; default: itkExceptionMacro("unknown PlaneOrientation"); } InitializeStandardPlane( width, height, geometry3D->GetIndexToWorldTransform(), planeorientation, zPosition, frontside, rotated ); ScalarType bounds[6]= { 0, width, 0, height, 0, 1 }; this->SetBounds( bounds ); Point3D origin; originVector = geometry3D->GetIndexToWorldTransform() ->TransformVector( originVector ); origin = GetOrigin() + originVector; SetOrigin(origin); } void PlaneGeometry::InitializeStandardPlane( const Geometry3D *geometry3D, bool top, PlaneOrientation planeorientation, bool frontside, bool rotated ) { ScalarType zPosition; switch(planeorientation) { case Axial: zPosition = (top ? 0.5 : geometry3D->GetExtent(2)-1+0.5); break; case Frontal: zPosition = (top ? 0.5 : geometry3D->GetExtent(1)-1+0.5); break; case Sagittal: zPosition = (top ? 0.5 : geometry3D->GetExtent(0)-1+0.5); break; default: itkExceptionMacro("unknown PlaneOrientation"); } InitializeStandardPlane( geometry3D, planeorientation, zPosition, frontside, rotated ); } void PlaneGeometry::InitializeStandardPlane( const Vector3D &rightVector, const Vector3D &downVector, const Vector3D *spacing ) { InitializeStandardPlane( rightVector.GetVnlVector(), downVector.GetVnlVector(), spacing ); } void PlaneGeometry::InitializeStandardPlane( const VnlVector& rightVector, const VnlVector &downVector, const Vector3D *spacing ) { ScalarType width = rightVector.magnitude(); ScalarType height = downVector.magnitude(); InitializeStandardPlane( width, height, rightVector, downVector, spacing ); } void PlaneGeometry::InitializeStandardPlane( mitk::ScalarType width, ScalarType height, const Vector3D &rightVector, const Vector3D &downVector, const Vector3D *spacing ) { InitializeStandardPlane( width, height, rightVector.GetVnlVector(), downVector.GetVnlVector(), spacing ); } void PlaneGeometry::InitializeStandardPlane( mitk::ScalarType width, ScalarType height, const VnlVector &rightVector, const VnlVector &downVector, const Vector3D *spacing ) { assert(width > 0); assert(height > 0); VnlVector rightDV = rightVector; rightDV.normalize(); VnlVector downDV = downVector; downDV.normalize(); VnlVector normal = vnl_cross_3d(rightVector, downVector); normal.normalize(); if(spacing!=NULL) { rightDV *= (*spacing)[0]; downDV *= (*spacing)[1]; normal *= (*spacing)[2]; } AffineTransform3D::Pointer transform = AffineTransform3D::New(); Matrix3D matrix; matrix.GetVnlMatrix().set_column(0, rightDV); matrix.GetVnlMatrix().set_column(1, downDV); matrix.GetVnlMatrix().set_column(2, normal); transform->SetMatrix(matrix); transform->SetOffset(m_IndexToWorldTransform->GetOffset()); ScalarType bounds[6] = { 0, width, 0, height, 0, 1 }; this->SetBounds( bounds ); this->SetIndexToWorldTransform( transform ); } void PlaneGeometry::InitializePlane( const Point3D &origin, const Vector3D &normal ) { VnlVector rightVectorVnl(3), downVectorVnl; if( Equal( normal[1], 0.0f ) == false ) { FillVector3D( rightVectorVnl, 1.0f, -normal[0]/normal[1], 0.0f ); rightVectorVnl.normalize(); } else { FillVector3D( rightVectorVnl, 0.0f, 1.0f, 0.0f ); } downVectorVnl = vnl_cross_3d( normal.GetVnlVector(), rightVectorVnl ); downVectorVnl.normalize(); InitializeStandardPlane( rightVectorVnl, downVectorVnl ); SetOrigin(origin); } void PlaneGeometry::SetMatrixByVectors( const VnlVector &rightVector, const VnlVector &downVector, ScalarType thickness ) { VnlVector normal = vnl_cross_3d(rightVector, downVector); normal.normalize(); normal *= thickness; AffineTransform3D::Pointer transform = AffineTransform3D::New(); Matrix3D matrix; matrix.GetVnlMatrix().set_column(0, rightVector); matrix.GetVnlMatrix().set_column(1, downVector); matrix.GetVnlMatrix().set_column(2, normal); transform->SetMatrix(matrix); transform->SetOffset(m_IndexToWorldTransform->GetOffset()); SetIndexToWorldTransform(transform); } Vector3D PlaneGeometry::GetNormal() const { Vector3D frontToBack; frontToBack.SetVnlVector( m_IndexToWorldTransform ->GetMatrix().GetVnlMatrix().get_column(2) ); return frontToBack; } VnlVector PlaneGeometry::GetNormalVnl() const { return m_IndexToWorldTransform ->GetMatrix().GetVnlMatrix().get_column(2); } ScalarType PlaneGeometry::DistanceFromPlane( const Point3D &pt3d_mm ) const { return fabs(SignedDistance( pt3d_mm )); } ScalarType PlaneGeometry::SignedDistance( const Point3D &pt3d_mm ) const { return SignedDistanceFromPlane(pt3d_mm); } bool PlaneGeometry::IsAbove( const Point3D &pt3d_mm ) const { return SignedDistanceFromPlane(pt3d_mm) > 0; } bool PlaneGeometry::IntersectionLine( const PlaneGeometry* plane, Line3D& crossline ) const { Vector3D normal = this->GetNormal(); normal.Normalize(); Vector3D planeNormal = plane->GetNormal(); planeNormal.Normalize(); Vector3D direction = itk::CrossProduct( normal, planeNormal ); if ( direction.GetSquaredNorm() < eps ) return false; crossline.SetDirection( direction ); double N1dN2 = normal * planeNormal; double determinant = 1.0 - N1dN2 * N1dN2; Vector3D origin = this->GetOrigin().GetVectorFromOrigin(); Vector3D planeOrigin = plane->GetOrigin().GetVectorFromOrigin(); double d1 = normal * origin; double d2 = planeNormal * planeOrigin; double c1 = ( d1 - d2 * N1dN2 ) / determinant; double c2 = ( d2 - d1 * N1dN2 ) / determinant; Vector3D p = normal * c1 + planeNormal * c2; crossline.GetPoint().GetVnlVector() = p.GetVnlVector(); return true; } unsigned int PlaneGeometry::IntersectWithPlane2D( const PlaneGeometry* plane, Point2D& lineFrom, Point2D &lineTo ) const { Line3D crossline; if ( this->IntersectionLine( plane, crossline ) == false ) return 0; Point2D point2; Vector2D direction2; this->Map( crossline.GetPoint(), point2 ); this->Map( crossline.GetPoint(), crossline.GetDirection(), direction2 ); return Line3D::RectangleLineIntersection( 0, 0, GetExtentInMM(0), GetExtentInMM(1), point2, direction2, lineFrom, lineTo ); } double PlaneGeometry::Angle( const PlaneGeometry *plane ) const { return angle(plane->GetMatrixColumn(2), GetMatrixColumn(2)); } double PlaneGeometry::Angle( const Line3D &line ) const { return vnl_math::pi_over_2 - angle( line.GetDirection().GetVnlVector(), GetMatrixColumn(2) ); } bool PlaneGeometry::IntersectionPoint( const Line3D &line, Point3D &intersectionPoint ) const { Vector3D planeNormal = this->GetNormal(); planeNormal.Normalize(); Vector3D lineDirection = line.GetDirection(); lineDirection.Normalize(); double t = planeNormal * lineDirection; if ( fabs( t ) < eps ) { return false; } Vector3D diff; diff = this->GetOrigin() - line.GetPoint(); t = ( planeNormal * diff ) / t; intersectionPoint = line.GetPoint() + lineDirection * t; return true; } bool PlaneGeometry::IntersectionPointParam( const Line3D &line, double &t ) const { Vector3D planeNormal = this->GetNormal(); Vector3D lineDirection = line.GetDirection(); t = planeNormal * lineDirection; if ( fabs( t ) < eps ) { return false; } Vector3D diff; diff = this->GetOrigin() - line.GetPoint(); t = ( planeNormal * diff ) / t; return true; } bool PlaneGeometry::IsParallel( const PlaneGeometry *plane ) const { return ( (Angle(plane) < 10.0 * mitk::sqrteps ) || ( Angle(plane) > ( vnl_math::pi - 10.0 * sqrteps ) ) ) ; } bool PlaneGeometry::IsOnPlane( const Point3D &point ) const { return Distance(point) < eps; } bool PlaneGeometry::IsOnPlane( const Line3D &line ) const { return ( (Distance( line.GetPoint() ) < eps) && (Distance( line.GetPoint2() ) < eps) ); } bool PlaneGeometry::IsOnPlane( const PlaneGeometry *plane ) const { return ( IsParallel( plane ) && (Distance( plane->GetOrigin() ) < eps) ); } Point3D PlaneGeometry::ProjectPointOntoPlane( const Point3D& pt ) const { ScalarType len = this->GetNormalVnl().two_norm(); return pt - this->GetNormal() * this->SignedDistanceFromPlane( pt ) / len; } itk::LightObject::Pointer PlaneGeometry::InternalClone() const { Self::Pointer newGeometry = new PlaneGeometry(*this); newGeometry->UnRegister(); return newGeometry.GetPointer(); } void PlaneGeometry::ExecuteOperation( Operation *operation ) { vtkTransform *transform = vtkTransform::New(); transform->SetMatrix( m_VtkMatrix ); switch ( operation->GetOperationType() ) { case OpORIENT: { mitk::PlaneOperation *planeOp = dynamic_cast< mitk::PlaneOperation * >( operation ); if ( planeOp == NULL ) { return; } Point3D center = planeOp->GetPoint(); Vector3D orientationVector = planeOp->GetNormal(); Vector3D defaultVector; FillVector3D( defaultVector, 0.0, 0.0, 1.0 ); Vector3D rotationAxis = itk::CrossProduct( orientationVector, defaultVector ); //double rotationAngle = acos( orientationVector[2] / orientationVector.GetNorm() ); double rotationAngle = atan2( (double) rotationAxis.GetNorm(), (double) (orientationVector * defaultVector) ); rotationAngle *= 180.0 / vnl_math::pi; transform->PostMultiply(); transform->Identity(); transform->Translate( center[0], center[1], center[2] ); transform->RotateWXYZ( rotationAngle, rotationAxis[0], rotationAxis[1], rotationAxis[2] ); transform->Translate( -center[0], -center[1], -center[2] ); break; } case OpRESTOREPLANEPOSITION: { RestorePlanePositionOperation *op = dynamic_cast< mitk::RestorePlanePositionOperation* >(operation); if(op == NULL) { return; } AffineTransform3D::Pointer transform2 = AffineTransform3D::New(); Matrix3D matrix; matrix.GetVnlMatrix().set_column(0, op->GetTransform()->GetMatrix().GetVnlMatrix().get_column(0)); matrix.GetVnlMatrix().set_column(1, op->GetTransform()->GetMatrix().GetVnlMatrix().get_column(1)); matrix.GetVnlMatrix().set_column(2, op->GetTransform()->GetMatrix().GetVnlMatrix().get_column(2)); transform2->SetMatrix(matrix); Vector3D offset = op->GetTransform()->GetOffset(); transform2->SetOffset(offset); this->SetIndexToWorldTransform(transform2); ScalarType bounds[6] = {0, op->GetWidth(), 0, op->GetHeight(), 0 ,1 }; this->SetBounds(bounds); TransferItkToVtkTransform(); this->Modified(); transform->Delete(); return; } default: Superclass::ExecuteOperation( operation ); transform->Delete(); return; } m_VtkMatrix->DeepCopy(transform->GetMatrix()); this->TransferVtkToItkTransform(); this->Modified(); transform->Delete(); } void PlaneGeometry::PrintSelf( std::ostream& os, itk::Indent indent ) const { Superclass::PrintSelf(os,indent); os << indent << " Normal: " << GetNormal() << std::endl; } } // namespace diff --git a/Core/Code/DataManagement/mitkPlaneGeometry.h b/Core/Code/DataManagement/mitkPlaneGeometry.h index b95ec89c37..d59e308fc5 100644 --- a/Core/Code/DataManagement/mitkPlaneGeometry.h +++ b/Core/Code/DataManagement/mitkPlaneGeometry.h @@ -1,394 +1,392 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C #define PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C #include #include "mitkGeometry2D.h" #include "mitkRestorePlanePositionOperation.h" #include namespace mitk { template < class TCoordRep, unsigned int NPointDimension > class Line; typedef Line Line3D; /** * \brief Describes a two-dimensional, rectangular plane * * \ingroup Geometry */ class MITK_CORE_EXPORT PlaneGeometry : public Geometry2D { public: mitkClassMacro(PlaneGeometry,Geometry2D); /** Method for creation through the object factory. */ itkNewMacro(Self); enum PlaneOrientation { #ifdef _MSC_VER Transversal, // deprecated #endif Axial = 0, Sagittal, Frontal }; #ifdef __GNUC__ __attribute__ ((deprecated)) static const PlaneOrientation Transversal = PlaneOrientation(Axial); #endif virtual void IndexToWorld(const Point2D &pt_units, Point2D &pt_mm) const; virtual void WorldToIndex(const Point2D &pt_mm, Point2D &pt_units) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## @deprecated First parameter (Point2D) is not used. If possible, please use void IndexToWorld(const mitk::Vector2D& vec_units, mitk::Vector2D& vec_mm) const. //## For further information about coordinates types, please see the Geometry documentation virtual void IndexToWorld(const mitk::Point2D &atPt2d_untis, const mitk::Vector2D &vec_units, mitk::Vector2D &vec_mm) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation virtual void IndexToWorld(const mitk::Vector2D &vec_units, mitk::Vector2D &vec_mm) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## @deprecated First parameter (Point2D) is not used. If possible, please use void WorldToIndex(const mitk::Vector2D& vec_mm, mitk::Vector2D& vec_units) const. //## For further information about coordinates types, please see the Geometry documentation virtual void WorldToIndex(const mitk::Point2D &atPt2d_mm, const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## For further information about coordinates types, please see the Geometry documentation virtual void WorldToIndex(const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units) const; - virtual void Initialize(); - /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a geometry3D (default: identity). * Spacing also taken from \a geometry3D. * * \warning A former version of this method created a geometry with unit * spacing. For unit spacing use * * \code * // for in-plane unit spacing: * thisgeometry->SetSizeInUnits(thisgeometry->GetExtentInMM(0), * thisgeometry->GetExtentInMM(1)); * // additionally, for unit spacing in normal direction (former version * // did not do this): * thisgeometry->SetExtentInMM(2, 1.0); * \endcode */ virtual void InitializeStandardPlane( const Geometry3D* geometry3D, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside=true, bool rotated=false ); /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a geometry3D (default: identity). * Spacing also taken from \a geometry3D. * * \param top if \a true, create plane at top, otherwise at bottom * (for PlaneOrientation Axial, for other plane locations respectively) */ virtual void InitializeStandardPlane( const Geometry3D* geometry3D, bool top, PlaneOrientation planeorientation = Axial, bool frontside=true, bool rotated=false ); /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a transform (default: identity) * given width and height in units. * */ virtual void InitializeStandardPlane( ScalarType width, ScalarType height, const AffineTransform3D* transform = NULL, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside=true, bool rotated=false ); /** * \brief Initialize plane with orientation \a planeorientation * (default: axial) given width, height and spacing. * */ virtual void InitializeStandardPlane( ScalarType width, ScalarType height, const Vector3D & spacing, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside = true, bool rotated = false ); /** * \brief Initialize plane by width and height in pixels, right-/down-vector * (itk) to describe orientation in world-space (vectors will be normalized) * and spacing (default: 1.0 mm in all directions). * * The vectors are normalized and multiplied by the respective spacing before * they are set in the matrix. */ virtual void InitializeStandardPlane( ScalarType width, ScalarType height, const Vector3D& rightVector, const Vector3D& downVector, const Vector3D *spacing = NULL ); /** * \brief Initialize plane by width and height in pixels, * right-/down-vector (vnl) to describe orientation in world-space (vectors * will be normalized) and spacing (default: 1.0 mm in all directions). * * The vectors are normalized and multiplied by the respective spacing * before they are set in the matrix. */ virtual void InitializeStandardPlane( ScalarType width, ScalarType height, const VnlVector& rightVector, const VnlVector& downVector, const Vector3D * spacing = NULL ); /** * \brief Initialize plane by right-/down-vector (itk) and spacing * (default: 1.0 mm in all directions). * * The length of the right-/-down-vector is used as width/height in units, * respectively. Then, the vectors are normalized and multiplied by the * respective spacing before they are set in the matrix. */ virtual void InitializeStandardPlane( const Vector3D& rightVector, const Vector3D& downVector, const Vector3D * spacing = NULL ); /** * \brief Initialize plane by right-/down-vector (vnl) and spacing * (default: 1.0 mm in all directions). * * The length of the right-/-down-vector is used as width/height in units, * respectively. Then, the vectors are normalized and multiplied by the * respective spacing before they are set in the matrix. */ virtual void InitializeStandardPlane( const VnlVector& rightVector, const VnlVector& downVector, const Vector3D * spacing = NULL ); /** * \brief Initialize plane by origin and normal (size is 1.0 mm in * all directions, direction of right-/down-vector valid but * undefined). * */ virtual void InitializePlane( const Point3D& origin, const Vector3D& normal); /** * \brief Initialize plane by right-/down-vector. * * \warning The vectors are set into the matrix as they are, * \em without normalization! */ void SetMatrixByVectors( const VnlVector& rightVector, const VnlVector& downVector, ScalarType thickness=1.0 ); /** * \brief Change \a transform so that the third column of the * transform-martix is perpendicular to the first two columns * */ static void EnsurePerpendicularNormal( AffineTransform3D* transform ); /** * \brief Normal of the plane * */ Vector3D GetNormal() const; /** * \brief Normal of the plane as VnlVector * */ VnlVector GetNormalVnl() const; virtual ScalarType SignedDistance( const Point3D& pt3d_mm ) const; virtual bool IsAbove( const Point3D& pt3d_mm ) const; /** * \brief Distance of the point from the plane * (bounding-box \em not considered) * */ ScalarType DistanceFromPlane( const Point3D& pt3d_mm ) const ; /** * \brief Signed distance of the point from the plane * (bounding-box \em not considered) * * > 0 : point is in the direction of the direction vector. */ inline ScalarType SignedDistanceFromPlane( const Point3D& pt3d_mm ) const { ScalarType len = GetNormalVnl().two_norm(); if( len == 0 ) return 0; return (pt3d_mm-GetOrigin())*GetNormal() / len; } /** * \brief Distance of the plane from another plane * (bounding-box \em not considered) * * Result is 0 if planes are not parallel. */ ScalarType DistanceFromPlane(const PlaneGeometry* plane) const { return fabs(SignedDistanceFromPlane(plane)); } /** * \brief Signed distance of the plane from another plane * (bounding-box \em not considered) * * Result is 0 if planes are not parallel. */ inline ScalarType SignedDistanceFromPlane( const PlaneGeometry *plane ) const { if(IsParallel(plane)) { return SignedDistance(plane->GetOrigin()); } return 0; } /** * \brief Calculate the intersecting line of two planes * * \return \a true planes are intersecting * \return \a false planes do not intersect */ bool IntersectionLine( const PlaneGeometry *plane, Line3D &crossline ) const; /** * \brief Calculate two points where another plane intersects the border of this plane * * \return number of intersection points (0..2). First interection point (if existing) * is returned in \a lineFrom, second in \a lineTo. */ unsigned int IntersectWithPlane2D(const PlaneGeometry *plane, Point2D &lineFrom, Point2D &lineTo ) const ; /** * \brief Calculate the angle between two planes * * \return angle in radiants */ double Angle( const PlaneGeometry *plane ) const; /** * \brief Calculate the angle between the plane and a line * * \return angle in radiants */ double Angle( const Line3D &line ) const; /** * \brief Calculate intersection point between the plane and a line * * \param intersectionPoint intersection point * \return \a true if \em unique intersection exists, i.e., if line * is \em not on or parallel to the plane */ bool IntersectionPoint( const Line3D &line, Point3D &intersectionPoint ) const; /** * \brief Calculate line parameter of intersection point between the * plane and a line * * \param t parameter of line: intersection point is * line.GetPoint()+t*line.GetDirection() * \return \a true if \em unique intersection exists, i.e., if line * is \em not on or parallel to the plane */ bool IntersectionPointParam( const Line3D &line, double &t ) const; /** * \brief Returns whether the plane is parallel to another plane * * @return true iff the normal vectors both point to the same or exactly oposit direction */ bool IsParallel( const PlaneGeometry *plane ) const; /** * \brief Returns whether the point is on the plane * (bounding-box \em not considered) */ bool IsOnPlane( const Point3D &point ) const; /** * \brief Returns whether the line is on the plane * (bounding-box \em not considered) */ bool IsOnPlane( const Line3D &line ) const; /** * \brief Returns whether the plane is on the plane * (bounding-box \em not considered) * * @return true iff the normal vector of the planes point to the same or the exactly oposit direction and * the distance of the planes is < eps * */ bool IsOnPlane( const PlaneGeometry *plane ) const; /** * \brief Returns the lot from the point to the plane */ Point3D ProjectPointOntoPlane( const Point3D &pt ) const; - virtual void SetIndexToWorldTransform( AffineTransform3D *transform); - virtual itk::LightObject::Pointer InternalClone() const; /** Implements operation to re-orient the plane */ virtual void ExecuteOperation( Operation *operation ); protected: PlaneGeometry(); virtual ~PlaneGeometry(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; virtual void InternPreSetBounds( const BoundingBox::BoundsArrayType &bounds ); + virtual void InternPreSetIndexToWorldTransform( AffineTransform3D *transform); + private: /** * \brief Compares plane with another plane: \a true if IsOnPlane * (bounding-box \em not considered) */ virtual bool operator==( const PlaneGeometry * ) const { return false; }; /** * \brief Compares plane with another plane: \a false if IsOnPlane * (bounding-box \em not considered) */ virtual bool operator!=( const PlaneGeometry * ) const { return false; }; }; } // namespace mitk #endif /* PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C */ diff --git a/Core/Code/DataManagement/mitkSlicedGeometry3D.cpp b/Core/Code/DataManagement/mitkSlicedGeometry3D.cpp index 4da37ac5b0..30d20fb3ef 100644 --- a/Core/Code/DataManagement/mitkSlicedGeometry3D.cpp +++ b/Core/Code/DataManagement/mitkSlicedGeometry3D.cpp @@ -1,1035 +1,1033 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSlicedGeometry3D.h" #include "mitkPlaneGeometry.h" #include "mitkRotationOperation.h" #include "mitkPlaneOperation.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkApplyTransformMatrixOperation.h" #include "mitkInteractionConst.h" #include "mitkSliceNavigationController.h" const mitk::ScalarType PI = 3.14159265359; mitk::SlicedGeometry3D::SlicedGeometry3D() : m_EvenlySpaced( true ), m_Slices( 0 ), m_ReferenceGeometry( NULL ), m_SliceNavigationController( NULL ) { m_DirectionVector.Fill(0); this->InitializeSlicedGeometry( m_Slices ); } mitk::SlicedGeometry3D::SlicedGeometry3D(const SlicedGeometry3D& other) : Superclass(other), m_EvenlySpaced( other.m_EvenlySpaced ), m_Slices( other.m_Slices ), m_ReferenceGeometry( other.m_ReferenceGeometry ), m_SliceNavigationController( other.m_SliceNavigationController ) { m_DirectionVector.Fill(0); SetSpacing( other.GetSpacing() ); SetDirectionVector( other.GetDirectionVector() ); if ( m_EvenlySpaced ) { Geometry2D::Pointer geometry = other.m_Geometry2Ds[0]->Clone(); Geometry2D* geometry2D = dynamic_cast(geometry.GetPointer()); assert(geometry2D!=NULL); SetGeometry2D(geometry2D, 0); } else { unsigned int s; for ( s = 0; s < other.m_Slices; ++s ) { if ( other.m_Geometry2Ds[s].IsNull() ) { assert(other.m_EvenlySpaced); m_Geometry2Ds[s] = NULL; } else { Geometry2D* geometry2D = other.m_Geometry2Ds[s]->Clone(); assert(geometry2D!=NULL); SetGeometry2D(geometry2D, s); } } } } mitk::SlicedGeometry3D::~SlicedGeometry3D() { } mitk::Geometry2D * mitk::SlicedGeometry3D::GetGeometry2D( int s ) const { mitk::Geometry2D::Pointer geometry2D = NULL; if ( this->IsValidSlice(s) ) { geometry2D = m_Geometry2Ds[s]; // If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored // for the requested slice, and (c) the first slice (s=0) // is a PlaneGeometry instance, then we calculate the geometry of the // requested as the plane of the first slice shifted by m_Spacing[2]*s // in the direction of m_DirectionVector. if ( (m_EvenlySpaced) && (geometry2D.IsNull()) ) { PlaneGeometry *firstSlice = dynamic_cast< PlaneGeometry * > ( m_Geometry2Ds[0].GetPointer() ); if ( firstSlice != NULL ) { if ( (m_DirectionVector[0] == 0.0) && (m_DirectionVector[1] == 0.0) && (m_DirectionVector[2] == 0.0) ) { m_DirectionVector = firstSlice->GetNormal(); m_DirectionVector.Normalize(); } Vector3D direction; direction = m_DirectionVector * m_Spacing[2]; mitk::PlaneGeometry::Pointer requestedslice; requestedslice = static_cast< mitk::PlaneGeometry * >( firstSlice->Clone().GetPointer() ); requestedslice->SetOrigin( requestedslice->GetOrigin() + direction * s ); geometry2D = requestedslice; m_Geometry2Ds[s] = geometry2D; } } return geometry2D; } else { return NULL; } } const mitk::BoundingBox * mitk::SlicedGeometry3D::GetBoundingBox() const { assert(m_BoundingBox.IsNotNull()); return m_BoundingBox.GetPointer(); } bool mitk::SlicedGeometry3D::SetGeometry2D( mitk::Geometry2D *geometry2D, int s ) { if ( this->IsValidSlice(s) ) { m_Geometry2Ds[s] = geometry2D; m_Geometry2Ds[s]->SetReferenceGeometry( m_ReferenceGeometry ); return true; } return false; } void mitk::SlicedGeometry3D::InitializeSlicedGeometry( unsigned int slices ) { Superclass::Initialize(); m_Slices = slices; Geometry2D::Pointer gnull = NULL; m_Geometry2Ds.assign( m_Slices, gnull ); Vector3D spacing; spacing.Fill( 1.0 ); this->SetSpacing( spacing ); m_DirectionVector.Fill( 0 ); } void mitk::SlicedGeometry3D::InitializeEvenlySpaced( mitk::Geometry2D* geometry2D, unsigned int slices, bool flipped ) { assert( geometry2D != NULL ); this->InitializeEvenlySpaced( geometry2D, geometry2D->GetExtentInMM(2)/geometry2D->GetExtent(2), slices, flipped ); } void mitk::SlicedGeometry3D::InitializeEvenlySpaced( mitk::Geometry2D* geometry2D, mitk::ScalarType zSpacing, unsigned int slices, bool flipped ) { assert( geometry2D != NULL ); assert( geometry2D->GetExtent(0) > 0 ); assert( geometry2D->GetExtent(1) > 0 ); geometry2D->Register(); Superclass::Initialize(); m_Slices = slices; BoundingBox::BoundsArrayType bounds = geometry2D->GetBounds(); bounds[4] = 0; bounds[5] = slices; // clear and reserve Geometry2D::Pointer gnull = NULL; m_Geometry2Ds.assign( m_Slices, gnull ); Vector3D directionVector = geometry2D->GetAxisVector(2); directionVector.Normalize(); directionVector *= zSpacing; if ( flipped == false ) { // Normally we should use the following four lines to create a copy of // the transform contrained in geometry2D, because it may not be changed // by us. But we know that SetSpacing creates a new transform without // changing the old (coming from geometry2D), so we can use the fifth // line instead. We check this at (**). // // AffineTransform3D::Pointer transform = AffineTransform3D::New(); // transform->SetMatrix(geometry2D->GetIndexToWorldTransform()->GetMatrix()); // transform->SetOffset(geometry2D->GetIndexToWorldTransform()->GetOffset()); // SetIndexToWorldTransform(transform); m_IndexToWorldTransform = const_cast< AffineTransform3D * >( geometry2D->GetIndexToWorldTransform() ); } else { directionVector *= -1.0; m_IndexToWorldTransform = AffineTransform3D::New(); m_IndexToWorldTransform->SetMatrix( geometry2D->GetIndexToWorldTransform()->GetMatrix() ); AffineTransform3D::OutputVectorType scaleVector; FillVector3D(scaleVector, 1.0, 1.0, -1.0); m_IndexToWorldTransform->Scale(scaleVector, true); m_IndexToWorldTransform->SetOffset( geometry2D->GetIndexToWorldTransform()->GetOffset() ); } mitk::Vector3D spacing; FillVector3D( spacing, geometry2D->GetExtentInMM(0) / bounds[1], geometry2D->GetExtentInMM(1) / bounds[3], zSpacing ); // Ensure that spacing differs from m_Spacing to make SetSpacing change the // matrix. m_Spacing[2] = zSpacing - 1; this->SetDirectionVector( directionVector ); this->SetBounds( bounds ); this->SetGeometry2D( geometry2D, 0 ); this->SetSpacing( spacing ); this->SetEvenlySpaced(); this->SetTimeBounds( geometry2D->GetTimeBounds() ); assert(m_IndexToWorldTransform.GetPointer() != geometry2D->GetIndexToWorldTransform()); // (**) see above. this->SetFrameOfReferenceID( geometry2D->GetFrameOfReferenceID() ); this->SetImageGeometry( geometry2D->GetImageGeometry() ); geometry2D->UnRegister(); } void mitk::SlicedGeometry3D::InitializePlanes( const mitk::Geometry3D *geometry3D, mitk::PlaneGeometry::PlaneOrientation planeorientation, bool top, bool frontside, bool rotated ) { m_ReferenceGeometry = const_cast< Geometry3D * >( geometry3D ); PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->InitializeStandardPlane( geometry3D, top, planeorientation, frontside, rotated ); ScalarType viewSpacing = 1; unsigned int slices = 1; switch ( planeorientation ) { case PlaneGeometry::Axial: viewSpacing = geometry3D->GetSpacing()[2]; slices = (unsigned int) geometry3D->GetExtent( 2 ); break; case PlaneGeometry::Frontal: viewSpacing = geometry3D->GetSpacing()[1]; slices = (unsigned int) geometry3D->GetExtent( 1 ); break; case PlaneGeometry::Sagittal: viewSpacing = geometry3D->GetSpacing()[0]; slices = (unsigned int) geometry3D->GetExtent( 0 ); break; default: itkExceptionMacro("unknown PlaneOrientation"); } mitk::Vector3D normal = this->AdjustNormal( planeGeometry->GetNormal() ); ScalarType directedExtent = std::abs( m_ReferenceGeometry->GetExtentInMM( 0 ) * normal[0] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 1 ) * normal[1] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 2 ) * normal[2] ); if ( directedExtent >= viewSpacing ) { slices = static_cast< int >(directedExtent / viewSpacing + 0.5); } else { slices = 1; } bool flipped = (top == false); if ( frontside == false ) { flipped = !flipped; } if ( planeorientation == PlaneGeometry::Frontal ) { flipped = !flipped; } this->InitializeEvenlySpaced( planeGeometry, viewSpacing, slices, flipped ); } void mitk::SlicedGeometry3D ::ReinitializePlanes( const Point3D ¢er, const Point3D &referencePoint ) { // Need a reference frame to align the rotated planes if ( !m_ReferenceGeometry ) { return; } // Get first plane of plane stack PlaneGeometry *firstPlane = dynamic_cast< PlaneGeometry * >( m_Geometry2Ds[0].GetPointer() ); // If plane stack is empty, exit if ( firstPlane == NULL ) { return; } // Calculate the "directed" spacing when taking the plane (defined by its axes // vectors and normal) as the reference coordinate frame. // // This is done by calculating the radius of the ellipsoid defined by the // original volume spacing axes, in the direction of the respective axis of the // reference frame. mitk::Vector3D axis0 = firstPlane->GetAxisVector(0); mitk::Vector3D axis1 = firstPlane->GetAxisVector(1); mitk::Vector3D normal = firstPlane->GetNormal(); normal.Normalize(); Vector3D spacing; spacing[0] = this->CalculateSpacing( axis0 ); spacing[1] = this->CalculateSpacing( axis1 ); spacing[2] = this->CalculateSpacing( normal ); Superclass::SetSpacing( spacing ); // Now we need to calculate the number of slices in the plane's normal // direction, so that the entire volume is covered. This is done by first // calculating the dot product between the volume diagonal (the maximum // distance inside the volume) and the normal, and dividing this value by // the directed spacing calculated above. ScalarType directedExtent = std::abs( m_ReferenceGeometry->GetExtentInMM( 0 ) * normal[0] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 1 ) * normal[1] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 2 ) * normal[2] ); if ( directedExtent >= spacing[2] ) { m_Slices = static_cast< unsigned int >(directedExtent / spacing[2] + 0.5); } else { m_Slices = 1; } // The origin of our "first plane" needs to be adapted to this new extent. // To achieve this, we first calculate the current distance to the volume's // center, and then shift the origin in the direction of the normal by the // difference between this distance and half of the new extent. double centerOfRotationDistance = firstPlane->SignedDistanceFromPlane( center ); if ( centerOfRotationDistance > 0 ) { firstPlane->SetOrigin( firstPlane->GetOrigin() + normal * (centerOfRotationDistance - directedExtent / 2.0) ); m_DirectionVector = normal; } else { firstPlane->SetOrigin( firstPlane->GetOrigin() + normal * (directedExtent / 2.0 + centerOfRotationDistance) ); m_DirectionVector = -normal; } // Now we adjust this distance according with respect to the given reference // point: we need to make sure that the point is touched by one slice of the // new slice stack. double referencePointDistance = firstPlane->SignedDistanceFromPlane( referencePoint ); int referencePointSlice = static_cast< int >( referencePointDistance / spacing[2]); double alignmentValue = referencePointDistance / spacing[2] - referencePointSlice; firstPlane->SetOrigin( firstPlane->GetOrigin() + normal * alignmentValue * spacing[2] ); // Finally, we can clear the previous geometry stack and initialize it with // our re-initialized "first plane". m_Geometry2Ds.assign( m_Slices, Geometry2D::Pointer( NULL ) ); if ( m_Slices > 0 ) { m_Geometry2Ds[0] = firstPlane; } // Reinitialize SNC with new number of slices m_SliceNavigationController->GetSlice()->SetSteps( m_Slices ); this->Modified(); } double mitk::SlicedGeometry3D::CalculateSpacing( const mitk::Vector3D &d ) const { // Need the spacing of the underlying dataset / geometry if ( !m_ReferenceGeometry ) { return 1.0; } const mitk::Vector3D &spacing = m_ReferenceGeometry->GetSpacing(); return SlicedGeometry3D::CalculateSpacing( spacing, d ); } double mitk::SlicedGeometry3D::CalculateSpacing( const mitk::Vector3D spacing, const mitk::Vector3D &d ) { // The following can be derived from the ellipsoid equation // // 1 = x^2/a^2 + y^2/b^2 + z^2/c^2 // // where (a,b,c) = spacing of original volume (ellipsoid radii) // and (x,y,z) = scaled coordinates of vector d (according to ellipsoid) // double scaling = d[0]*d[0] / (spacing[0] * spacing[0]) + d[1]*d[1] / (spacing[1] * spacing[1]) + d[2]*d[2] / (spacing[2] * spacing[2]); scaling = sqrt( scaling ); return ( sqrt( d[0]*d[0] + d[1]*d[1] + d[2]*d[2] ) / scaling ); } mitk::Vector3D mitk::SlicedGeometry3D::AdjustNormal( const mitk::Vector3D &normal ) const { TransformType::Pointer inverse = TransformType::New(); m_ReferenceGeometry->GetIndexToWorldTransform()->GetInverse( inverse ); Vector3D transformedNormal = inverse->TransformVector( normal ); transformedNormal.Normalize(); return transformedNormal; } void mitk::SlicedGeometry3D::SetImageGeometry( const bool isAnImageGeometry ) { Superclass::SetImageGeometry( isAnImageGeometry ); mitk::Geometry3D* geometry; unsigned int s; for ( s = 0; s < m_Slices; ++s ) { geometry = m_Geometry2Ds[s]; if ( geometry!=NULL ) { geometry->SetImageGeometry( isAnImageGeometry ); } } } void mitk::SlicedGeometry3D::ChangeImageGeometryConsideringOriginOffset( const bool isAnImageGeometry ) { mitk::Geometry3D* geometry; unsigned int s; for ( s = 0; s < m_Slices; ++s ) { geometry = m_Geometry2Ds[s]; if ( geometry!=NULL ) { geometry->ChangeImageGeometryConsideringOriginOffset( isAnImageGeometry ); } } Superclass::ChangeImageGeometryConsideringOriginOffset( isAnImageGeometry ); } bool mitk::SlicedGeometry3D::IsValidSlice( int s ) const { return ((s >= 0) && (s < (int)m_Slices)); } void mitk::SlicedGeometry3D::SetReferenceGeometry( Geometry3D *referenceGeometry ) { m_ReferenceGeometry = referenceGeometry; std::vector::iterator it; for ( it = m_Geometry2Ds.begin(); it != m_Geometry2Ds.end(); ++it ) { (*it)->SetReferenceGeometry( referenceGeometry ); } } void - mitk::SlicedGeometry3D::SetSpacing( const mitk::Vector3D &aSpacing ) + mitk::SlicedGeometry3D::InternPreSetSpacing( const mitk::Vector3D &aSpacing ) { bool hasEvenlySpacedPlaneGeometry = false; mitk::Point3D origin; mitk::Vector3D rightDV, bottomDV; BoundingBox::BoundsArrayType bounds; assert(aSpacing[0]>0 && aSpacing[1]>0 && aSpacing[2]>0); // In case of evenly-spaced data: re-initialize instances of Geometry2D, // since the spacing influences them if ((m_EvenlySpaced) && (m_Geometry2Ds.size() > 0)) { mitk::Geometry2D::ConstPointer firstGeometry = m_Geometry2Ds[0].GetPointer(); const PlaneGeometry *planeGeometry = dynamic_cast< const PlaneGeometry * >( firstGeometry.GetPointer() ); if (planeGeometry != NULL ) { this->WorldToIndex( planeGeometry->GetOrigin(), origin ); this->WorldToIndex( planeGeometry->GetAxisVector(0), rightDV ); this->WorldToIndex( planeGeometry->GetAxisVector(1), bottomDV ); bounds = planeGeometry->GetBounds(); hasEvenlySpacedPlaneGeometry = true; } } - Superclass::SetSpacing(aSpacing); + InternSetSpacing(aSpacing); mitk::Geometry2D::Pointer firstGeometry; // In case of evenly-spaced data: re-initialize instances of Geometry2D, // since the spacing influences them if ( hasEvenlySpacedPlaneGeometry ) { //create planeGeometry according to new spacing this->IndexToWorld( origin, origin ); this->IndexToWorld( rightDV, rightDV ); this->IndexToWorld( bottomDV, bottomDV ); mitk::PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->SetImageGeometry( this->GetImageGeometry() ); planeGeometry->SetReferenceGeometry( m_ReferenceGeometry ); planeGeometry->InitializeStandardPlane( rightDV.GetVnlVector(), bottomDV.GetVnlVector(), &m_Spacing ); planeGeometry->SetOrigin(origin); planeGeometry->SetBounds(bounds); firstGeometry = planeGeometry; } else if ( (m_EvenlySpaced) && (m_Geometry2Ds.size() > 0) ) { firstGeometry = m_Geometry2Ds[0].GetPointer(); } //clear and reserve Geometry2D::Pointer gnull=NULL; m_Geometry2Ds.assign(m_Slices, gnull); if ( m_Slices > 0 ) { m_Geometry2Ds[0] = firstGeometry; } this->Modified(); } void mitk::SlicedGeometry3D ::SetSliceNavigationController( SliceNavigationController *snc ) { m_SliceNavigationController = snc; } mitk::SliceNavigationController * mitk::SlicedGeometry3D::GetSliceNavigationController() { return m_SliceNavigationController; } void mitk::SlicedGeometry3D::SetEvenlySpaced(bool on) { if(m_EvenlySpaced!=on) { m_EvenlySpaced=on; this->Modified(); } } void mitk::SlicedGeometry3D ::SetDirectionVector( const mitk::Vector3D& directionVector ) { Vector3D newDir = directionVector; newDir.Normalize(); if ( newDir != m_DirectionVector ) { m_DirectionVector = newDir; this->Modified(); } } void - mitk::SlicedGeometry3D::SetTimeBounds( const mitk::TimeBounds& timebounds ) + mitk::SlicedGeometry3D::InternPostSetTimeBounds( const mitk::TimeBounds& timebounds ) { - Superclass::SetTimeBounds( timebounds ); - unsigned int s; for ( s = 0; s < m_Slices; ++s ) { if(m_Geometry2Ds[s].IsNotNull()) { m_Geometry2Ds[s]->SetTimeBounds( timebounds ); } } m_TimeBounds = timebounds; } itk::LightObject::Pointer mitk::SlicedGeometry3D::InternalClone() const { Self::Pointer newGeometry = new SlicedGeometry3D(*this); newGeometry->UnRegister(); return newGeometry.GetPointer(); } void mitk::SlicedGeometry3D::PrintSelf( std::ostream& os, itk::Indent indent ) const { Superclass::PrintSelf(os,indent); os << indent << " EvenlySpaced: " << m_EvenlySpaced << std::endl; if ( m_EvenlySpaced ) { os << indent << " DirectionVector: " << m_DirectionVector << std::endl; } os << indent << " Slices: " << m_Slices << std::endl; os << std::endl; os << indent << " GetGeometry2D(0): "; if ( this->GetGeometry2D(0) == NULL ) { os << "NULL" << std::endl; } else { this->GetGeometry2D(0)->Print(os, indent); } } void mitk::SlicedGeometry3D::ExecuteOperation(Operation* operation) { switch ( operation->GetOperationType() ) { case OpNOTHING: break; case OpROTATE: if ( m_EvenlySpaced ) { // Need a reference frame to align the rotation if ( m_ReferenceGeometry ) { // Clear all generated geometries and then rotate only the first slice. // The other slices will be re-generated on demand // Save first slice Geometry2D::Pointer geometry2D = m_Geometry2Ds[0]; RotationOperation *rotOp = dynamic_cast< RotationOperation * >( operation ); // Generate a RotationOperation using the dataset center instead of // the supplied rotation center. This is necessary so that the rotated // zero-plane does not shift away. The supplied center is instead used // to adjust the slice stack afterwards. Point3D center = m_ReferenceGeometry->GetCenter(); RotationOperation centeredRotation( rotOp->GetOperationType(), center, rotOp->GetVectorOfRotation(), rotOp->GetAngleOfRotation() ); // Rotate first slice geometry2D->ExecuteOperation( ¢eredRotation ); // Clear the slice stack and adjust it according to the center of // the dataset and the supplied rotation center (see documentation of // ReinitializePlanes) this->ReinitializePlanes( center, rotOp->GetCenterOfRotation() ); geometry2D->SetSpacing(this->GetSpacing()); if ( m_SliceNavigationController ) { m_SliceNavigationController->SelectSliceByPoint( rotOp->GetCenterOfRotation() ); m_SliceNavigationController->AdjustSliceStepperRange(); } Geometry3D::ExecuteOperation( ¢eredRotation ); } else { // we also have to consider the case, that there is no reference geometry available. if ( m_Geometry2Ds.size() > 0 ) { // Reach through to all slices in my container for (std::vector::iterator iter = m_Geometry2Ds.begin(); iter != m_Geometry2Ds.end(); ++iter) { // Test for empty slices, which can happen if evenly spaced geometry if ((*iter).IsNotNull()) { (*iter)->ExecuteOperation(operation); } } // rotate overall geometry RotationOperation *rotOp = dynamic_cast< RotationOperation * >( operation ); Geometry3D::ExecuteOperation( rotOp); } } } else { // Reach through to all slices for (std::vector::iterator iter = m_Geometry2Ds.begin(); iter != m_Geometry2Ds.end(); ++iter) { (*iter)->ExecuteOperation(operation); } } break; case OpORIENT: if ( m_EvenlySpaced ) { // get operation data PlaneOperation *planeOp = dynamic_cast< PlaneOperation * >( operation ); // Get first slice Geometry2D::Pointer geometry2D = m_Geometry2Ds[0]; PlaneGeometry *planeGeometry = dynamic_cast< PlaneGeometry * >( geometry2D.GetPointer() ); // Need a PlaneGeometry, a PlaneOperation and a reference frame to // carry out the re-orientation. If not all avaialble, stop here if ( !m_ReferenceGeometry || !planeGeometry || !planeOp ) { break; } // General Behavior: // Clear all generated geometries and then rotate only the first slice. // The other slices will be re-generated on demand // // 1st Step: Reorient Normal Vector of first plane // Point3D center = planeOp->GetPoint(); //m_ReferenceGeometry->GetCenter(); mitk::Vector3D currentNormal = planeGeometry->GetNormal(); mitk::Vector3D newNormal; if (planeOp->AreAxisDefined()) { // If planeOp was defined by one centerpoint and two axis vectors newNormal = CrossProduct(planeOp->GetAxisVec0(), planeOp->GetAxisVec1()); } else { // If planeOp was defined by one centerpoint and one normal vector newNormal = planeOp->GetNormal(); } // Get Rotation axis und angle currentNormal.Normalize(); newNormal.Normalize(); ScalarType rotationAngle = angle(currentNormal.GetVnlVector(),newNormal.GetVnlVector()); rotationAngle *= 180.0 / vnl_math::pi; // from rad to deg Vector3D rotationAxis = itk::CrossProduct( currentNormal, newNormal ); if (std::abs(rotationAngle-180) < mitk::eps ) { // current Normal and desired normal are not linear independent!!(e.g 1,0,0 and -1,0,0). // Rotation Axis should be ANY vector that is 90� to current Normal mitk::Vector3D helpNormal; helpNormal = currentNormal; helpNormal[0] += 1; helpNormal[1] -= 1; helpNormal[2] += 1; helpNormal.Normalize(); rotationAxis = itk::CrossProduct( helpNormal, currentNormal ); } RotationOperation centeredRotation( mitk::OpROTATE, center, rotationAxis, rotationAngle ); // Rotate first slice geometry2D->ExecuteOperation( ¢eredRotation ); // Reinitialize planes and select slice, if my rotations are all done. if (!planeOp->AreAxisDefined()) { // Clear the slice stack and adjust it according to the center of // rotation and plane position (see documentation of ReinitializePlanes) this->ReinitializePlanes( center, planeOp->GetPoint() ); if ( m_SliceNavigationController ) { m_SliceNavigationController->SelectSliceByPoint( planeOp->GetPoint() ); m_SliceNavigationController->AdjustSliceStepperRange(); } } // Also apply rotation on the slicedGeometry - Geometry3D (Bounding geometry) Geometry3D::ExecuteOperation( ¢eredRotation ); // // 2nd step. If axis vectors were defined, rotate the plane around its normal to fit these // if (planeOp->AreAxisDefined()) { mitk::Vector3D vecAxixNew = planeOp->GetAxisVec0(); vecAxixNew.Normalize(); mitk::Vector3D VecAxisCurr = geometry2D->GetAxisVector(0); VecAxisCurr.Normalize(); ScalarType rotationAngle = angle(VecAxisCurr.GetVnlVector(),vecAxixNew.GetVnlVector()); rotationAngle = rotationAngle * 180 / PI; // Rad to Deg // we rotate around the normal of the plane, but we do not know, if we need to rotate clockwise // or anti-clockwise. So we rotate around the crossproduct of old and new Axisvector. // Since both axis vectors lie in the plane, the crossproduct is the planes normal or the negative planes normal rotationAxis = itk::CrossProduct( VecAxisCurr, vecAxixNew ); if (std::abs(rotationAngle-180) < mitk::eps ) { // current axisVec and desired axisVec are not linear independent!!(e.g 1,0,0 and -1,0,0). // Rotation Axis can be just plane Normal. (have to rotate by 180�) rotationAxis = newNormal; } // Perfom Rotation mitk::RotationOperation op(mitk::OpROTATE, center, rotationAxis, rotationAngle); geometry2D->ExecuteOperation( &op ); // Apply changes on first slice to whole slice stack this->ReinitializePlanes( center, planeOp->GetPoint() ); if ( m_SliceNavigationController ) { m_SliceNavigationController->SelectSliceByPoint( planeOp->GetPoint() ); m_SliceNavigationController->AdjustSliceStepperRange(); } // Also apply rotation on the slicedGeometry - Geometry3D (Bounding geometry) Geometry3D::ExecuteOperation( &op ); } } else { // Reach through to all slices for (std::vector::iterator iter = m_Geometry2Ds.begin(); iter != m_Geometry2Ds.end(); ++iter) { (*iter)->ExecuteOperation(operation); } } break; case OpRESTOREPLANEPOSITION: if ( m_EvenlySpaced ) { // Save first slice Geometry2D::Pointer geometry2D = m_Geometry2Ds[0]; PlaneGeometry* planeGeometry = dynamic_cast< PlaneGeometry * >( geometry2D.GetPointer() ); RestorePlanePositionOperation *restorePlaneOp = dynamic_cast< RestorePlanePositionOperation* >( operation ); // Need a PlaneGeometry, a PlaneOperation and a reference frame to // carry out the re-orientation if ( m_ReferenceGeometry && planeGeometry && restorePlaneOp ) { // Clear all generated geometries and then rotate only the first slice. // The other slices will be re-generated on demand // Rotate first slice geometry2D->ExecuteOperation( restorePlaneOp ); m_DirectionVector = restorePlaneOp->GetDirectionVector(); double centerOfRotationDistance = planeGeometry->SignedDistanceFromPlane( m_ReferenceGeometry->GetCenter() ); if ( centerOfRotationDistance > 0 ) { m_DirectionVector = m_DirectionVector; } else { m_DirectionVector = -m_DirectionVector; } Vector3D spacing = restorePlaneOp->GetSpacing(); Superclass::SetSpacing( spacing ); // /*Now we need to calculate the number of slices in the plane's normal // direction, so that the entire volume is covered. This is done by first // calculating the dot product between the volume diagonal (the maximum // distance inside the volume) and the normal, and dividing this value by // the directed spacing calculated above.*/ ScalarType directedExtent = std::abs( m_ReferenceGeometry->GetExtentInMM( 0 ) * m_DirectionVector[0] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 1 ) * m_DirectionVector[1] ) + std::abs( m_ReferenceGeometry->GetExtentInMM( 2 ) * m_DirectionVector[2] ); if ( directedExtent >= spacing[2] ) { m_Slices = static_cast< unsigned int >(directedExtent / spacing[2] + 0.5); } else { m_Slices = 1; } m_Geometry2Ds.assign( m_Slices, Geometry2D::Pointer( NULL ) ); if ( m_Slices > 0 ) { m_Geometry2Ds[0] = geometry2D; } m_SliceNavigationController->GetSlice()->SetSteps( m_Slices ); this->Modified(); //End Reinitialization if ( m_SliceNavigationController ) { m_SliceNavigationController->GetSlice()->SetPos( restorePlaneOp->GetPos() ); m_SliceNavigationController->AdjustSliceStepperRange(); } Geometry3D::ExecuteOperation(restorePlaneOp); } } else { // Reach through to all slices for (std::vector::iterator iter = m_Geometry2Ds.begin(); iter != m_Geometry2Ds.end(); ++iter) { (*iter)->ExecuteOperation(operation); } } break; case OpAPPLYTRANSFORMMATRIX: // Clear all generated geometries and then transform only the first slice. // The other slices will be re-generated on demand // Save first slice Geometry2D::Pointer geometry2D = m_Geometry2Ds[0]; ApplyTransformMatrixOperation *applyMatrixOp = dynamic_cast< ApplyTransformMatrixOperation* >( operation ); // Apply transformation to first plane geometry2D->ExecuteOperation( applyMatrixOp ); // Generate a ApplyTransformMatrixOperation using the dataset center instead of // the supplied rotation center. The supplied center is instead used to adjust the // slice stack afterwards (see OpROTATE). Point3D center = m_ReferenceGeometry->GetCenter(); // Clear the slice stack and adjust it according to the center of // the dataset and the supplied rotation center (see documentation of // ReinitializePlanes) this->ReinitializePlanes( center, applyMatrixOp->GetReferencePoint() ); Geometry3D::ExecuteOperation( applyMatrixOp ); break; } this->Modified(); } diff --git a/Core/Code/DataManagement/mitkSlicedGeometry3D.h b/Core/Code/DataManagement/mitkSlicedGeometry3D.h index 80fdc978cf..c9cd056dce 100644 --- a/Core/Code/DataManagement/mitkSlicedGeometry3D.h +++ b/Core/Code/DataManagement/mitkSlicedGeometry3D.h @@ -1,324 +1,311 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ - #ifndef MITKSLICEDGEOMETRY3D_H_HEADER_INCLUDED_C1EBD0AD #define MITKSLICEDGEOMETRY3D_H_HEADER_INCLUDED_C1EBD0AD #include "mitkGeometry3D.h" #include "mitkPlaneGeometry.h" namespace mitk { - -class SliceNavigationController; -class NavigationController; - -/** \brief Describes the geometry of a data object consisting of slices. - * - * A Geometry2D can be requested for each slice. In the case of - * \em evenly-spaced, \em plane geometries (m_EvenlySpaced==true), - * only the 2D-geometry of the first slice has to be set (to an instance of - * PlaneGeometry). The 2D geometries of the other slices are calculated - * by shifting the first slice in the direction m_DirectionVector by - * m_Spacing.z * sliceNumber. The m_Spacing member (which is only - * relevant in the case m_EvenlySpaced==true) descibes the size of a voxel - * (in mm), i.e., m_Spacing.x is the voxel width in the x-direction of the - * plane. It is derived from the reference geometry of this SlicedGeometry3D, - * which usually would be the global geometry describing how datasets are to - * be resliced. - * - * By default, slices are oriented in the direction of one of the main axes - * (x, y, z). However, by means of rotation, it is possible to realign the - * slices in any possible direction. In case of an inclined plane, the spacing - * is derived as a product of the (regular) geometry spacing and the direction - * vector of the plane. - * - * SlicedGeometry3D and the associated Geometry2Ds have to be initialized in - * the method GenerateOutputInformation() of BaseProcess (or CopyInformation / - * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing pic - * tags in Image) subclasses. See also - * - * \sa itk::ProcessObject::GenerateOutputInformation(), - * \sa itk::DataObject::CopyInformation() and - * \a itk::DataObject::UpdateOutputInformation(). - * - * Rule: everything is in mm (or ms for temporal information) if not - * stated otherwise. - * - * \warning The hull (i.e., transform, bounding-box and - * time-bounds) is only guaranteed to be up-to-date after calling - * UpdateInformation(). - * - * \ingroup Geometry - */ -class MITK_CORE_EXPORT SlicedGeometry3D : public mitk::Geometry3D -{ -public: - mitkClassMacro(SlicedGeometry3D, Geometry3D); - - /** Method for creation through the object factory. */ - itkNewMacro(Self); - - /** - * \brief Returns the Geometry2D of the slice (\a s). - * - * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored - * for the requested slice, and (c) the first slice (s=0) - * is a PlaneGeometry instance, then we calculate the geometry of the - * requested as the plane of the first slice shifted by m_Spacing[3]*s - * in the direction of m_DirectionVector. - * - * \warning The Geometry2Ds are not necessarily up-to-date and not even - * initialized. - * - * The Geometry2Ds have to be initialized in the method - * GenerateOutputInformation() of BaseProcess (or CopyInformation / - * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing - * pic tags in Image) subclasses. See also - * - * \sa itk::ProcessObject::GenerateOutputInformation(), - * \sa itk::DataObject::CopyInformation() and - * \sa itk::DataObject::UpdateOutputInformation(). - */ - virtual mitk::Geometry2D* GetGeometry2D( int s ) const; - - - /** - * \brief Set Geometry2D of slice \a s. - */ - virtual bool SetGeometry2D( mitk::Geometry2D *geometry2D, int s ); - - //##Documentation - //## @brief When switching from an Image Geometry to a normal Geometry (and the other way around), you have to change the origin as well (See Geometry Documentation)! This function will change the "isImageGeometry" bool flag and changes the origin respectively. - virtual void ChangeImageGeometryConsideringOriginOffset( const bool isAnImageGeometry ); - - virtual void SetTimeBounds( const mitk::TimeBounds& timebounds ); - - - virtual const mitk::BoundingBox* GetBoundingBox() const; - - /** - * \brief Get the number of slices - */ - itkGetConstMacro( Slices, unsigned int ); - - - /** - * \brief Check whether a slice exists - */ - virtual bool IsValidSlice( int s = 0 ) const; - - virtual void SetReferenceGeometry( Geometry3D *referenceGeometry ); - - /** - * \brief Set the spacing (m_Spacing), in direction of the plane normal. - * - * INTERNAL METHOD. - */ - virtual void SetSpacing( const mitk::Vector3D &aSpacing ); - - /** - * \brief Set the SliceNavigationController corresponding to this sliced - * geometry. - * - * The SNC needs to be informed when the number of slices in the geometry - * changes, which can occur whenthe slices are re-oriented by rotation. - */ - virtual void SetSliceNavigationController( - mitk::SliceNavigationController *snc ); - mitk::SliceNavigationController *GetSliceNavigationController(); - - /** - * \brief Set/Get whether the SlicedGeometry3D is evenly-spaced - * (m_EvenlySpaced) - * - * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored for - * the requested slice, and (c) the first slice (s=0) is a PlaneGeometry - * instance, then we calculate the geometry of the requested as the plane - * of the first slice shifted by m_Spacing.z * s in the direction of - * m_DirectionVector. - * - * \sa GetGeometry2D - */ - itkGetConstMacro(EvenlySpaced, bool); - - virtual void SetEvenlySpaced(bool on = true); - - /** - * \brief Set/Get the vector between slices for the evenly-spaced case - * (m_EvenlySpaced==true). - * - * If the direction-vector is (0,0,0) (the default) and the first - * 2D geometry is a PlaneGeometry, then the direction-vector will be - * calculated from the plane normal. - * - * \sa m_DirectionVector - */ - virtual void SetDirectionVector(const mitk::Vector3D& directionVector); - itkGetConstMacro(DirectionVector, const mitk::Vector3D&); - - virtual itk::LightObject::Pointer InternalClone() const; - - static const std::string SLICES; - const static std::string DIRECTION_VECTOR; - const static std::string EVENLY_SPACED; - - /** - * \brief Tell this instance how many Geometry2Ds it shall manage. Bounding - * box and the Geometry2Ds must be set additionally by calling the respective - * methods! - * - * \warning Bounding box and the 2D-geometries must be set additionally: use - * SetBounds(), SetGeometry(). - */ - virtual void InitializeSlicedGeometry( unsigned int slices ); - - /** - * \brief Completely initialize this instance as evenly-spaced with slices - * parallel to the provided Geometry2D that is used as the first slice and - * for spacing calculation. - * - * Initializes the bounding box according to the width/height of the - * Geometry2D and \a slices. The spacing is calculated from the Geometry2D. - */ - virtual void InitializeEvenlySpaced( mitk::Geometry2D *geometry2D, - unsigned int slices, bool flipped=false ); - - /** - * \brief Completely initialize this instance as evenly-spaced with slices - * parallel to the provided Geometry2D that is used as the first slice and - * for spacing calculation (except z-spacing). - * - * Initializes the bounding box according to the width/height of the - * Geometry2D and \a slices. The x-/y-spacing is calculated from the - * Geometry2D. - */ - virtual void InitializeEvenlySpaced( mitk::Geometry2D *geometry2D, - mitk::ScalarType zSpacing, unsigned int slices, bool flipped=false ); - - /** - * \brief Completely initialize this instance as evenly-spaced plane slices - * parallel to a side of the provided Geometry3D and using its spacing - * information. - * - * Initializes the bounding box according to the width/height of the - * Geometry3D and the number of slices according to - * Geometry3D::GetExtent(2). - * - * \param planeorientation side parallel to which the slices will be oriented - * \param top if \a true, create plane at top, otherwise at bottom - * (for PlaneOrientation Axial, for other plane locations respectively) - * \param frontside defines the side of the plane (the definition of - * front/back is somewhat arbitrary) - * - * \param rotate rotates the plane by 180 degree around its normal (the - * definition of rotated vs not rotated is somewhat arbitrary) - */ - virtual void InitializePlanes( const mitk::Geometry3D *geometry3D, - mitk::PlaneGeometry::PlaneOrientation planeorientation, bool top=true, - bool frontside=true, bool rotated=false ); - - - virtual void SetImageGeometry(const bool isAnImageGeometry); - - virtual void ExecuteOperation(Operation* operation); - - static double CalculateSpacing( const mitk::Vector3D spacing, const mitk::Vector3D &d ); - -protected: - SlicedGeometry3D(); - - SlicedGeometry3D(const SlicedGeometry3D& other); - - virtual ~SlicedGeometry3D(); - - - /** - * Reinitialize plane stack after rotation. More precisely, the first plane - * of the stack needs to spatially aligned, in two respects: - * - * 1. Re-alignment with respect to the dataset center; this is necessary - * since the distance from the first plane to the center could otherwise - * continuously decrease or increase. - * 2. Re-alignment with respect to a given reference point; the reference - * point is a location which the user wants to be exactly touched by one - * plane of the plane stack. The first plane is minimally shifted to - * ensure this touching. Usually, the reference point would be the - * point around which the geometry is rotated. - */ - virtual void ReinitializePlanes( const Point3D ¢er, - const Point3D &referencePoint ); - - - ScalarType GetLargestExtent( const Geometry3D *geometry ); - - void PrintSelf(std::ostream& os, itk::Indent indent) const; - - /** Calculate "directed spacing", i.e. the spacing in directions - * non-orthogonal to the coordinate axes. This is done via the - * ellipsoid equation. - */ - double CalculateSpacing( const mitk::Vector3D &direction ) const; - - - - /** The extent of the slice stack, i.e. the number of slices, depends on the - * plane normal. For rotated geometries, the geometry's transform needs to - * be accounted in this calculation. - */ - mitk::Vector3D AdjustNormal( const mitk::Vector3D &normal ) const; - - - /** - * Container for the 2D-geometries contained within this SliceGeometry3D. - */ - mutable std::vector m_Geometry2Ds; - - - /** - * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored - * for the requested slice, and (c) the first slice (s=0) - * is a PlaneGeometry instance, then we calculate the geometry of the - * requested as the plane of the first slice shifted by m_Spacing.z*s - * in the direction of m_DirectionVector. - * - * \sa GetGeometry2D - */ - bool m_EvenlySpaced; - - /** - * Vector between slices for the evenly-spaced case (m_EvenlySpaced==true). - * If the direction-vector is (0,0,0) (the default) and the first - * 2D geometry is a PlaneGeometry, then the direction-vector will be - * calculated from the plane normal. - */ - mutable mitk::Vector3D m_DirectionVector; - - /** Number of slices this SliceGeometry3D is descibing. */ - unsigned int m_Slices; - - /** Underlying Geometry3D for this SlicedGeometry */ - mitk::Geometry3D *m_ReferenceGeometry; - - /** SNC correcsponding to this geometry; used to reflect changes in the - * number of slices due to rotation. */ - //mitk::NavigationController *m_NavigationController; - mitk::SliceNavigationController *m_SliceNavigationController; -}; - + class SliceNavigationController; + class NavigationController; + + /** \brief Describes the geometry of a data object consisting of slices. + * + * A Geometry2D can be requested for each slice. In the case of + * \em evenly-spaced, \em plane geometries (m_EvenlySpaced==true), + * only the 2D-geometry of the first slice has to be set (to an instance of + * PlaneGeometry). The 2D geometries of the other slices are calculated + * by shifting the first slice in the direction m_DirectionVector by + * m_Spacing.z * sliceNumber. The m_Spacing member (which is only + * relevant in the case m_EvenlySpaced==true) descibes the size of a voxel + * (in mm), i.e., m_Spacing.x is the voxel width in the x-direction of the + * plane. It is derived from the reference geometry of this SlicedGeometry3D, + * which usually would be the global geometry describing how datasets are to + * be resliced. + * + * By default, slices are oriented in the direction of one of the main axes + * (x, y, z). However, by means of rotation, it is possible to realign the + * slices in any possible direction. In case of an inclined plane, the spacing + * is derived as a product of the (regular) geometry spacing and the direction + * vector of the plane. + * + * SlicedGeometry3D and the associated Geometry2Ds have to be initialized in + * the method GenerateOutputInformation() of BaseProcess (or CopyInformation / + * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing pic + * tags in Image) subclasses. See also + * + * \sa itk::ProcessObject::GenerateOutputInformation(), + * \sa itk::DataObject::CopyInformation() and + * \a itk::DataObject::UpdateOutputInformation(). + * + * Rule: everything is in mm (or ms for temporal information) if not + * stated otherwise. + * + * \warning The hull (i.e., transform, bounding-box and + * time-bounds) is only guaranteed to be up-to-date after calling + * UpdateInformation(). + * + * \ingroup Geometry + */ + class MITK_CORE_EXPORT SlicedGeometry3D : public mitk::Geometry3D + { + public: + mitkClassMacro(SlicedGeometry3D, Geometry3D); + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** + * \brief Returns the Geometry2D of the slice (\a s). + * + * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored + * for the requested slice, and (c) the first slice (s=0) + * is a PlaneGeometry instance, then we calculate the geometry of the + * requested as the plane of the first slice shifted by m_Spacing[3]*s + * in the direction of m_DirectionVector. + * + * \warning The Geometry2Ds are not necessarily up-to-date and not even + * initialized. + * + * The Geometry2Ds have to be initialized in the method + * GenerateOutputInformation() of BaseProcess (or CopyInformation / + * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing + * pic tags in Image) subclasses. See also + * + * \sa itk::ProcessObject::GenerateOutputInformation(), + * \sa itk::DataObject::CopyInformation() and + * \sa itk::DataObject::UpdateOutputInformation(). + */ + virtual mitk::Geometry2D* GetGeometry2D( int s ) const; + + /** + * \brief Set Geometry2D of slice \a s. + */ + virtual bool SetGeometry2D( mitk::Geometry2D *geometry2D, int s ); + + //##Documentation + //## @brief When switching from an Image Geometry to a normal Geometry (and the other way around), you have to change the origin as well (See Geometry Documentation)! This function will change the "isImageGeometry" bool flag and changes the origin respectively. + virtual void ChangeImageGeometryConsideringOriginOffset( const bool isAnImageGeometry ); + + virtual const mitk::BoundingBox* GetBoundingBox() const; + + /** + * \brief Get the number of slices + */ + itkGetConstMacro( Slices, unsigned int ); + + /** + * \brief Check whether a slice exists + */ + virtual bool IsValidSlice( int s = 0 ) const; + + virtual void SetReferenceGeometry( Geometry3D *referenceGeometry ); + + /** + * \brief Set the SliceNavigationController corresponding to this sliced + * geometry. + * + * The SNC needs to be informed when the number of slices in the geometry + * changes, which can occur whenthe slices are re-oriented by rotation. + */ + virtual void SetSliceNavigationController( + mitk::SliceNavigationController *snc ); + mitk::SliceNavigationController *GetSliceNavigationController(); + + /** + * \brief Set/Get whether the SlicedGeometry3D is evenly-spaced + * (m_EvenlySpaced) + * + * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored for + * the requested slice, and (c) the first slice (s=0) is a PlaneGeometry + * instance, then we calculate the geometry of the requested as the plane + * of the first slice shifted by m_Spacing.z * s in the direction of + * m_DirectionVector. + * + * \sa GetGeometry2D + */ + itkGetConstMacro(EvenlySpaced, bool); + + virtual void SetEvenlySpaced(bool on = true); + + /** + * \brief Set/Get the vector between slices for the evenly-spaced case + * (m_EvenlySpaced==true). + * + * If the direction-vector is (0,0,0) (the default) and the first + * 2D geometry is a PlaneGeometry, then the direction-vector will be + * calculated from the plane normal. + * + * \sa m_DirectionVector + */ + virtual void SetDirectionVector(const mitk::Vector3D& directionVector); + itkGetConstMacro(DirectionVector, const mitk::Vector3D&); + + virtual itk::LightObject::Pointer InternalClone() const; + + static const std::string SLICES; + const static std::string DIRECTION_VECTOR; + const static std::string EVENLY_SPACED; + + /** + * \brief Tell this instance how many Geometry2Ds it shall manage. Bounding + * box and the Geometry2Ds must be set additionally by calling the respective + * methods! + * + * \warning Bounding box and the 2D-geometries must be set additionally: use + * SetBounds(), SetGeometry(). + */ + virtual void InitializeSlicedGeometry( unsigned int slices ); + + /** + * \brief Completely initialize this instance as evenly-spaced with slices + * parallel to the provided Geometry2D that is used as the first slice and + * for spacing calculation. + * + * Initializes the bounding box according to the width/height of the + * Geometry2D and \a slices. The spacing is calculated from the Geometry2D. + */ + virtual void InitializeEvenlySpaced( mitk::Geometry2D *geometry2D, + unsigned int slices, bool flipped=false ); + + /** + * \brief Completely initialize this instance as evenly-spaced with slices + * parallel to the provided Geometry2D that is used as the first slice and + * for spacing calculation (except z-spacing). + * + * Initializes the bounding box according to the width/height of the + * Geometry2D and \a slices. The x-/y-spacing is calculated from the + * Geometry2D. + */ + virtual void InitializeEvenlySpaced( mitk::Geometry2D *geometry2D, + mitk::ScalarType zSpacing, unsigned int slices, bool flipped=false ); + + /** + * \brief Completely initialize this instance as evenly-spaced plane slices + * parallel to a side of the provided Geometry3D and using its spacing + * information. + * + * Initializes the bounding box according to the width/height of the + * Geometry3D and the number of slices according to + * Geometry3D::GetExtent(2). + * + * \param planeorientation side parallel to which the slices will be oriented + * \param top if \a true, create plane at top, otherwise at bottom + * (for PlaneOrientation Axial, for other plane locations respectively) + * \param frontside defines the side of the plane (the definition of + * front/back is somewhat arbitrary) + * + * \param rotate rotates the plane by 180 degree around its normal (the + * definition of rotated vs not rotated is somewhat arbitrary) + */ + virtual void InitializePlanes( const mitk::Geometry3D *geometry3D, + mitk::PlaneGeometry::PlaneOrientation planeorientation, bool top=true, + bool frontside=true, bool rotated=false ); + + virtual void SetImageGeometry(const bool isAnImageGeometry); + + virtual void ExecuteOperation(Operation* operation); + + static double CalculateSpacing( const mitk::Vector3D spacing, const mitk::Vector3D &d ); + + protected: + SlicedGeometry3D(); + + SlicedGeometry3D(const SlicedGeometry3D& other); + + virtual ~SlicedGeometry3D(); + + virtual void InternPostSetTimeBounds( const mitk::TimeBounds& timebounds ); + + /** + * \brief Set the spacing (m_Spacing), in direction of the plane normal. + * + * INTERNAL METHOD. + */ + virtual void InternPreSetSpacing( const mitk::Vector3D &aSpacing ); + + /** + * Reinitialize plane stack after rotation. More precisely, the first plane + * of the stack needs to spatially aligned, in two respects: + * + * 1. Re-alignment with respect to the dataset center; this is necessary + * since the distance from the first plane to the center could otherwise + * continuously decrease or increase. + * 2. Re-alignment with respect to a given reference point; the reference + * point is a location which the user wants to be exactly touched by one + * plane of the plane stack. The first plane is minimally shifted to + * ensure this touching. Usually, the reference point would be the + * point around which the geometry is rotated. + */ + virtual void ReinitializePlanes( const Point3D ¢er, + const Point3D &referencePoint ); + + ScalarType GetLargestExtent( const Geometry3D *geometry ); + + void PrintSelf(std::ostream& os, itk::Indent indent) const; + + /** Calculate "directed spacing", i.e. the spacing in directions + * non-orthogonal to the coordinate axes. This is done via the + * ellipsoid equation. + */ + double CalculateSpacing( const mitk::Vector3D &direction ) const; + + /** The extent of the slice stack, i.e. the number of slices, depends on the + * plane normal. For rotated geometries, the geometry's transform needs to + * be accounted in this calculation. + */ + mitk::Vector3D AdjustNormal( const mitk::Vector3D &normal ) const; + + /** + * Container for the 2D-geometries contained within this SliceGeometry3D. + */ + mutable std::vector m_Geometry2Ds; + + /** + * If (a) m_EvenlySpaced==true, (b) we don't have a Geometry2D stored + * for the requested slice, and (c) the first slice (s=0) + * is a PlaneGeometry instance, then we calculate the geometry of the + * requested as the plane of the first slice shifted by m_Spacing.z*s + * in the direction of m_DirectionVector. + * + * \sa GetGeometry2D + */ + bool m_EvenlySpaced; + + /** + * Vector between slices for the evenly-spaced case (m_EvenlySpaced==true). + * If the direction-vector is (0,0,0) (the default) and the first + * 2D geometry is a PlaneGeometry, then the direction-vector will be + * calculated from the plane normal. + */ + mutable mitk::Vector3D m_DirectionVector; + + /** Number of slices this SliceGeometry3D is descibing. */ + unsigned int m_Slices; + + /** Underlying Geometry3D for this SlicedGeometry */ + mitk::Geometry3D *m_ReferenceGeometry; + + /** SNC correcsponding to this geometry; used to reflect changes in the + * number of slices due to rotation. */ + //mitk::NavigationController *m_NavigationController; + mitk::SliceNavigationController *m_SliceNavigationController; + }; } // namespace mitk #endif /* MITKSLICEDGEOMETRY3D_H_HEADER_INCLUDED_C1EBD0AD */ diff --git a/Core/Code/Testing/mitkBaseGeometryTest.cpp b/Core/Code/Testing/mitkBaseGeometryTest.cpp index ab66cda916..322b2a5723 100644 --- a/Core/Code/Testing/mitkBaseGeometryTest.cpp +++ b/Core/Code/Testing/mitkBaseGeometryTest.cpp @@ -1,279 +1,513 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestingMacros.h" #include #include #include #include #include #include "mitkoperationactor.h" #include #include "mitkvector.h" #include #include #include "itkScalableAffineTransform.h" #include #include #include class vtkMatrix4x4; class vtkMatrixToLinearTransform; class vtkLinearTransform; typedef itk::BoundingBox BoundingBox; typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::BoundsArrayType BoundsArrayType; typedef BoundingBoxType::Pointer BoundingBoxPointer; // Dummy instance of abstract base class class DummyTestClass : public mitk::BaseGeometry { public: DummyTestClass(){}; DummyTestClass(const DummyTestClass& other) : BaseGeometry(other){}; ~DummyTestClass(){}; mitkClassMacro(DummyTestClass, mitk::BaseGeometry); itkNewMacro(Self); mitkNewMacro1Param(Self,Self); itkGetConstMacro(IndexToWorldTransformLastModified, unsigned long); itkGetConstMacro(VtkMatrix, vtkMatrix4x4*); void DummyTestClass::ExecuteOperation(mitk::Operation* operation){}; + itk::LightObject::Pointer DummyTestClass::InternalClone() const + { + Self::Pointer newGeometry = new Self(*this); + newGeometry->UnRegister(); + return newGeometry.GetPointer(); + } }; class mitkBaseGeometryTestSuite : public mitk::TestFixture { // List of Tests CPPUNIT_TEST_SUITE(mitkBaseGeometryTestSuite); + + //Constructor MITK_TEST(TestConstructors); + + //Set MITK_TEST(TestSetOrigin); MITK_TEST(TestSetBounds); MITK_TEST(TestSetFloatBounds); MITK_TEST(TestSetFloatBoundsDouble); MITK_TEST(TestSetFrameOfReferenceID); MITK_TEST(TestSetIndexToWorldTransform); MITK_TEST(TestSetSpacing); MITK_TEST(TestTransferItkToVtkTransform); + MITK_TEST(TestSetIndexToWorldTransformByVtkMatrix); + MITK_TEST(TestSetIdentity); + //Equal + MITK_TEST(Equal_CloneAndOriginal_ReturnsTrue); + MITK_TEST(Equal_DifferentOrigin_ReturnsFalse); + MITK_TEST(Equal_DifferentIndexToWorldTransform_ReturnsFalse); + MITK_TEST(Equal_DifferentSpacing_ReturnsFalse); + MITK_TEST(Equal_InputIsNull_ReturnsFalse); + MITK_TEST(Equal_DifferentBoundingBox_ReturnsFalse); + MITK_TEST(TestComposeTransform); + MITK_TEST(TestComposeVtkMatrix); + CPPUNIT_TEST_SUITE_END(); // Used Variables private: mitk::Point3D aPoint; float aFloatSpacing[3]; mitk::Vector3D aSpacing; mitk::AffineTransform3D::Pointer aTransform; BoundingBoxPointer aBoundingBox; mitk::AffineTransform3D::MatrixType aMatrix; mitk::Point3D anotherPoint; mitk::Vector3D anotherSpacing; BoundingBoxPointer anotherBoundingBox; + BoundingBoxPointer aThirdBoundingBox; mitk::AffineTransform3D::Pointer anotherTransform; + mitk::AffineTransform3D::Pointer aThirdTransform; mitk::AffineTransform3D::MatrixType anotherMatrix; + mitk::AffineTransform3D::MatrixType aThirdMatrix; + + DummyTestClass::Pointer aDummyGeometry; + DummyTestClass::Pointer anotherDummyGeometry; public: // Set up for variables void setUp() { mitk::FillVector3D(aFloatSpacing, 1,1,1); mitk::FillVector3D(aSpacing, 1,1,1); mitk::FillVector3D(aPoint, 0,0,0); //Transform aTransform = mitk::AffineTransform3D::New(); aTransform->SetIdentity(); - anotherTransform = mitk::AffineTransform3D::New(); - aMatrix.SetIdentity(); + anotherTransform = mitk::AffineTransform3D::New(); + anotherMatrix.SetIdentity(); anotherMatrix(1,1) = 2; anotherTransform->SetMatrix( anotherMatrix ); + aThirdTransform = mitk::AffineTransform3D::New(); + + aThirdMatrix.SetIdentity(); + aThirdMatrix(1,1) = 7; + aThirdTransform->SetMatrix( aThirdMatrix ); + //Bounding Box float bounds[6] = {0,1,0,1,0,1}; mitk::BoundingBox::BoundsArrayType b; const float *input = bounds; int j=0; for(mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); j < 6 ;++j) *it++ = (mitk::ScalarType)*input++; aBoundingBox = BoundingBoxType::New(); BoundingBoxType::PointsContainer::Pointer pointscontainer = BoundingBoxType::PointsContainer::New(); BoundingBoxType::PointType p; BoundingBoxType::PointIdentifier pointid; for(pointid=0; pointid<2;++pointid) { unsigned int i; for(i=0; i<3; ++i) { p[i] = bounds[2*i+pointid]; } pointscontainer->InsertElement(pointid, p); } aBoundingBox->SetPoints(pointscontainer); aBoundingBox->ComputeBoundingBox(); anotherBoundingBox = BoundingBoxType::New(); p[0]=11; p[1]=12; p[2]=13; pointscontainer->InsertElement(1, p); anotherBoundingBox->SetPoints(pointscontainer); anotherBoundingBox->ComputeBoundingBox(); + aThirdBoundingBox = BoundingBoxType::New(); + p[0]=22; + p[1]=23; + p[2]=24; + pointscontainer->InsertElement(1, p); + aThirdBoundingBox->SetPoints(pointscontainer); + aThirdBoundingBox->ComputeBoundingBox(); + mitk::FillVector3D(anotherPoint, 2,3,4); mitk::FillVector3D(anotherSpacing, 5,6,7); + + aDummyGeometry = DummyTestClass::New(); + aDummyGeometry->Initialize(); + anotherDummyGeometry = aDummyGeometry->Clone(); + } + + void tearDown() + { + aDummyGeometry = NULL; + anotherDummyGeometry = NULL; } // Test functions void TestSetOrigin() { DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetOrigin(anotherPoint); CPPUNIT_ASSERT(anotherPoint==dummy->GetOrigin()); //undo changes, new and changed object need to be the same! dummy->SetOrigin(aPoint); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestSetFloatBounds(){ float bounds[6] = {0,11,0,12,0,13}; DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetFloatBounds(bounds); CPPUNIT_ASSERT(mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, true)); + //Wrong bounds, test needs to fail + bounds[1]=7; + dummy->SetFloatBounds(bounds); + CPPUNIT_ASSERT((mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, false))==false); + //undo changes, new and changed object need to be the same! float originalBounds[6] = {0,1,0,1,0,1}; dummy->SetFloatBounds(originalBounds); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestSetBounds(){ DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetBounds(anotherBoundingBox->GetBounds()); CPPUNIT_ASSERT(mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, true)); + //Test needs to fail now + dummy->SetBounds(aThirdBoundingBox->GetBounds()); + CPPUNIT_ASSERT(mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, false)==false); + //undo changes, new and changed object need to be the same! dummy->SetBounds(aBoundingBox->GetBounds()); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestSetFloatBoundsDouble(){ double bounds[6] = {0,11,0,12,0,13}; DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetFloatBounds(bounds); CPPUNIT_ASSERT(mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, true)); + //Test needs to fail now + bounds[3]=7; + dummy->SetFloatBounds(bounds); + CPPUNIT_ASSERT(mitk::Equal( dummy->GetBoundingBox(), anotherBoundingBox, mitk::eps, false)==false); + //undo changes, new and changed object need to be the same! double originalBounds[6] = {0,1,0,1,0,1}; dummy->SetFloatBounds(originalBounds); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestSetFrameOfReferenceID() { DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetFrameOfReferenceID(5); CPPUNIT_ASSERT(dummy->GetFrameOfReferenceID()==5); //undo changes, new and changed object need to be the same! dummy->SetFrameOfReferenceID(0); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestSetIndexToWorldTransform() { DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetIndexToWorldTransform(anotherTransform); CPPUNIT_ASSERT(mitk::Equal(anotherTransform,dummy->GetIndexToWorldTransform(),mitk::eps,true)); + //Test needs to fail now + dummy->SetIndexToWorldTransform(aThirdTransform); + CPPUNIT_ASSERT(mitk::Equal(anotherTransform,dummy->GetIndexToWorldTransform(),mitk::eps,false)==false); + //undo changes, new and changed object need to be the same! dummy->SetIndexToWorldTransform(aTransform); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } + void TestSetIndexToWorldTransformByVtkMatrix() + { + vtkMatrix4x4* vtkmatrix; + vtkmatrix = vtkMatrix4x4::New(); + vtkmatrix->Identity(); + vtkmatrix->SetElement(1,1,2); + DummyTestClass::Pointer dummy = DummyTestClass::New(); + dummy->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); + CPPUNIT_ASSERT(mitk::Equal(anotherTransform,dummy->GetIndexToWorldTransform(),mitk::eps,true)); + + //test needs to fail now + vtkmatrix->SetElement(1,1,7); + dummy->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); + CPPUNIT_ASSERT(mitk::Equal(anotherTransform,dummy->GetIndexToWorldTransform(),mitk::eps,false)==false); + + //undo changes, new and changed object need to be the same! + vtkmatrix->SetElement(1,1,1); + dummy->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); + DummyTestClass::Pointer newDummy = DummyTestClass::New(); + CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); + } + + void TestSetIdentity() + { + DummyTestClass::Pointer dummy = DummyTestClass::New(); + //Change IndextoWorldTransform and Origin + dummy->SetIndexToWorldTransform(anotherTransform); + dummy->SetOrigin(anotherPoint); + + //Set Identity should reset ITWT and Origin + dummy->SetIdentity(); + + CPPUNIT_ASSERT(mitk::Equal(aTransform,dummy->GetIndexToWorldTransform(),mitk::eps,true)); + CPPUNIT_ASSERT(aPoint==dummy->GetOrigin()); + CPPUNIT_ASSERT(aSpacing==dummy->GetSpacing()); + + //new and changed object need to be the same! + DummyTestClass::Pointer newDummy = DummyTestClass::New(); + CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); + } + void TestSetSpacing() { DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetSpacing(anotherSpacing); CPPUNIT_ASSERT(anotherSpacing==dummy->GetSpacing()); //undo changes, new and changed object need to be the same! dummy->SetSpacing(aSpacing); DummyTestClass::Pointer newDummy = DummyTestClass::New(); CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); } void TestTransferItkToVtkTransform() { DummyTestClass::Pointer dummy = DummyTestClass::New(); dummy->SetIndexToWorldTransform(anotherTransform); //calls TransferItkToVtkTransform mitk::AffineTransform3D::Pointer dummyTransform = dummy->GetIndexToWorldTransform(); CPPUNIT_ASSERT(mitk::MatrixEqualElementWise( anotherMatrix, dummyTransform->GetMatrix() )); } void TestConstructors() { //test standard constructor DummyTestClass::Pointer dummy1 = DummyTestClass::New(); bool test = dummy1->IsValid(); CPPUNIT_ASSERT(test == true); CPPUNIT_ASSERT(dummy1->GetFrameOfReferenceID() == 0); CPPUNIT_ASSERT(dummy1->GetIndexToWorldTransformLastModified() == 0); const float *dummy1FloatSpacing = dummy1->GetFloatSpacing(); CPPUNIT_ASSERT(dummy1FloatSpacing[0]==aFloatSpacing[0]); CPPUNIT_ASSERT(dummy1FloatSpacing[1]==aFloatSpacing[1]); CPPUNIT_ASSERT(dummy1FloatSpacing[2]==aFloatSpacing[2]); CPPUNIT_ASSERT(dummy1->GetSpacing() == aSpacing); CPPUNIT_ASSERT(dummy1->GetOrigin()==aPoint); CPPUNIT_ASSERT(mitk::Equal( dummy1->GetIndexToWorldTransform(), aTransform, mitk::eps, true)); CPPUNIT_ASSERT(mitk::Equal( dummy1->GetBoundingBox(), aBoundingBox, mitk::eps, true)); DummyTestClass::Pointer dummy2 = DummyTestClass::New(); dummy2->SetOrigin(anotherPoint); float bounds[6] = {0,11,0,12,0,13}; dummy2->SetFloatBounds(bounds); dummy2->SetIndexToWorldTransform(anotherTransform); dummy2->SetSpacing(anotherSpacing); DummyTestClass::Pointer dummy3; dummy3 = DummyTestClass::New(*dummy2); CPPUNIT_ASSERT(mitk::Equal(dummy3,dummy2,mitk::eps,true)); } -}; + + //Equal Tests + + void Equal_CloneAndOriginal_ReturnsTrue() + { + CPPUNIT_ASSERT( mitk::Equal(aDummyGeometry, anotherDummyGeometry, mitk::eps,true)); + } + + void Equal_DifferentOrigin_ReturnsFalse() + { + anotherDummyGeometry->SetOrigin(anotherPoint); + + CPPUNIT_ASSERT( mitk::Equal(aDummyGeometry, anotherDummyGeometry, mitk::eps,false)==false); + } + + void Equal_DifferentIndexToWorldTransform_ReturnsFalse() + { + anotherDummyGeometry->SetIndexToWorldTransform(anotherTransform); + + CPPUNIT_ASSERT( mitk::Equal(aDummyGeometry, anotherDummyGeometry, mitk::eps,false)==false); + } + + void Equal_DifferentSpacing_ReturnsFalse() + { + anotherDummyGeometry->SetSpacing(anotherSpacing); + + CPPUNIT_ASSERT( mitk::Equal(aDummyGeometry, anotherDummyGeometry, mitk::eps,false)==false); + } + + void Equal_InputIsNull_ReturnsFalse() + { + DummyTestClass::Pointer geometryNull = NULL; + CPPUNIT_ASSERT( mitk::Equal(geometryNull, anotherDummyGeometry, mitk::eps,false)==false); + } + + void Equal_DifferentBoundingBox_ReturnsFalse() + { + //create different bounds to make the comparison false + mitk::ScalarType bounds[ ] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + anotherDummyGeometry->SetBounds(bounds); + + CPPUNIT_ASSERT( mitk::Equal(aDummyGeometry, anotherDummyGeometry, mitk::eps,false)==false); + } + + void TestComposeTransform(){ + //Create Transformations to set and compare + mitk::AffineTransform3D::Pointer transform1; + transform1 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix1; + matrix1.SetIdentity(); + matrix1(1,1) = 2; + transform1->SetMatrix( matrix1 ); //Spacing = 2 + + mitk::AffineTransform3D::Pointer transform2; + transform2 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix2; + matrix2.SetIdentity(); + matrix2(1,1) = 2; + transform2->SetMatrix( matrix2 ); //Spacing = 2 + + mitk::AffineTransform3D::Pointer transform3; + transform3 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix3; + matrix3.SetIdentity(); + matrix3(1,1) = 4; + transform3->SetMatrix( matrix3 ); //Spacing = 4 + + mitk::AffineTransform3D::Pointer transform4; + transform4 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix4; + matrix4.SetIdentity(); + matrix4(1,1) = 0.25; + transform4->SetMatrix( matrix4 ); //Spacing = 0.25 + + DummyTestClass::Pointer dummy = DummyTestClass::New(); + dummy->SetIndexToWorldTransform(transform1); //Spacing = 2 + dummy->Compose(transform2); //Spacing = 4 + + CPPUNIT_ASSERT(mitk::Equal(transform3,dummy->GetIndexToWorldTransform(),mitk::eps,true)); // 4=4 + + //undo changes, new and changed object need to be the same! + dummy->Compose(transform4); //Spacing = 1 + DummyTestClass::Pointer newDummy = DummyTestClass::New(); + CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); // 1=1 + } + + void TestComposeVtkMatrix(){ + //Create Transformations to set and compare + mitk::AffineTransform3D::Pointer transform1; + transform1 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix1; + matrix1.SetIdentity(); + matrix1(1,1) = 2; + transform1->SetMatrix( matrix1 ); //Spacing = 2 + + vtkMatrix4x4* vtkmatrix2; + vtkmatrix2 = vtkMatrix4x4::New(); + vtkmatrix2->Identity(); + vtkmatrix2->SetElement(1,1,2); //Spacing = 2 + + mitk::AffineTransform3D::Pointer transform3; + transform3 = mitk::AffineTransform3D::New(); + mitk::AffineTransform3D::MatrixType matrix3; + matrix3.SetIdentity(); + matrix3(1,1) = 4; + transform3->SetMatrix( matrix3 ); //Spacing = 4 + + vtkMatrix4x4* vtkmatrix4; + vtkmatrix4 = vtkMatrix4x4::New(); + vtkmatrix4->Identity(); + vtkmatrix4->SetElement(1,1,0.25); //Spacing = 0.25 + + DummyTestClass::Pointer dummy = DummyTestClass::New(); + dummy->SetIndexToWorldTransform(transform1); //Spacing = 2 + dummy->Compose(vtkmatrix2); //Spacing = 4 + + CPPUNIT_ASSERT(mitk::Equal(transform3,dummy->GetIndexToWorldTransform(),mitk::eps,true)); // 4=4 + + //undo changes, new and changed object need to be the same! + dummy->Compose(vtkmatrix4); //Spacing = 1 + DummyTestClass::Pointer newDummy = DummyTestClass::New(); + CPPUNIT_ASSERT(mitk::Equal(dummy,newDummy,mitk::eps,true)); // 1=1 + } +};//end class mitkBaseGeometryTestSuite MITK_TEST_SUITE_REGISTRATION(mitkBaseGeometry)