Cloned library of VTK-5.0.0 with extra build files for internal package management.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

391 lines
12 KiB

2 years ago
/*=========================================================================
Program: Visualization Toolkit
Module: $RCSfile: vtkProcrustesAlignmentFilter.cxx,v $
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkProcrustesAlignmentFilter.h"
#include "vtkExecutive.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkLandmarkTransform.h"
#include "vtkTransformPolyDataFilter.h"
#include "vtkPolyData.h"
#include "vtkMath.h"
vtkCxxRevisionMacro(vtkProcrustesAlignmentFilter, "$Revision: 1.20 $");
vtkStandardNewMacro(vtkProcrustesAlignmentFilter);
//----------------------------------------------------------------------------
// protected
vtkProcrustesAlignmentFilter::vtkProcrustesAlignmentFilter()
{
this->LandmarkTransform = vtkLandmarkTransform::New();
this->MeanPoints = vtkPoints::New();
}
//----------------------------------------------------------------------------
// protected
vtkProcrustesAlignmentFilter::~vtkProcrustesAlignmentFilter()
{
if(this->LandmarkTransform)
{
this->LandmarkTransform->Delete();
}
if(this->MeanPoints)
{
this->MeanPoints->Delete();
}
}
//----------------------------------------------------------------------------
// Calculate the centroid of a point cloud
static inline void Centroid(vtkPoints* pd, double *cp)
{
// Center point
cp[0] = 0; cp[1] = 0; cp[2] = 0;
int np = pd->GetNumberOfPoints();
// Calculate center of shape
for (int i = 0; i < np; i++)
{
double p[3];
pd->GetPoint(i, p);
cp[0] += p[0]; cp[1] += p[1]; cp[2] += p[2];
}
cp[0] /= np; cp[1] /= np; cp[2] /= np;
}
//----------------------------------------------------------------------------
// Calculate the centroid size of a point cloud
static inline double CentroidSize(vtkPoints* pd, double *cp)
{
Centroid(pd, cp);
double S = 0;
for (int i = 0; i < pd->GetNumberOfPoints(); i++)
{
double p[3];
pd->GetPoint(i, p);
S += vtkMath::Distance2BetweenPoints(p,cp);
}
return sqrt(S);
}
//----------------------------------------------------------------------------
// Translation of point cloud. Could be done using transformations
static inline void TranslateShape(vtkPoints* pd, double *tp)
{
for (int i = 0; i < pd->GetNumberOfPoints(); i++)
{
double p[3];
pd->GetPoint(i, p);
pd->SetPoint(i, p[0]+tp[0], p[1]+tp[1], p[2]+tp[2]);
}
}
//----------------------------------------------------------------------------
// Scaling of point cloud. Could be done using transformations
static inline void ScaleShape(vtkPoints* pd, double S)
{
for (int i = 0; i < pd->GetNumberOfPoints(); i++)
{
double p[3];
pd->GetPoint(i, p);
pd->SetPoint(i, p[0]*S, p[1]*S, p[2]*S);
}
}
//----------------------------------------------------------------------------
// Normalise a point cloud to have centroid (0,0,0) and centroid size 1
static inline int NormaliseShape(vtkPoints* pd)
{
double cp[3];
double S = CentroidSize(pd, cp);
if (S == 0)
return 0;
double tp[3];
tp[0] = -cp[0]; tp[1] = -cp[1]; tp[2] = -cp[2];
TranslateShape(pd, tp);
ScaleShape(pd, 1/S);
return 1;
}
//----------------------------------------------------------------------------
// protected
int vtkProcrustesAlignmentFilter::RequestData(
vtkInformation *vtkNotUsed(request),
vtkInformationVector **inputVector,
vtkInformationVector *outputVector)
{
// get the info objects
vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
vtkInformation *outInfo = outputVector->GetInformationObject(0);
// get the input and ouptut
vtkPointSet *input = vtkPointSet::SafeDownCast(
inInfo->Get(vtkDataObject::DATA_OBJECT()));
vtkPointSet *output = vtkPointSet::SafeDownCast(
outInfo->Get(vtkDataObject::DATA_OBJECT()));
vtkDebugMacro(<<"Execute()");
int i,v;
const int N_SETS = this->GetNumberOfInputConnections(0);
// copy the inputs across
// (really actually only the points need to be deep copied since the rest stays the same)
vtkInformation *tmpInfo;
vtkPointSet *tmpInput;
output->DeepCopy(input);
for(i=1;i<N_SETS;i++)
{
tmpInfo = inputVector[0]->GetInformationObject(i);
tmpInput = 0;
if (tmpInfo)
{
tmpInput =
vtkPointSet::SafeDownCast(tmpInfo->Get(vtkDataObject::DATA_OBJECT()));
}
this->GetOutput(i)->DeepCopy(tmpInput);
}
// the number of points is determined by the first input (they must all be the same)
const int N_POINTS = input->GetNumberOfPoints();
vtkDebugMacro(<<"N_POINTS is " <<N_POINTS);
if(N_POINTS == 0)
{
vtkErrorMacro(<<"No points!");
return 1;
}
// all the inputs must have the same number of points to consider executing
for(i=1;i<N_SETS;i++)
{
tmpInfo = inputVector[0]->GetInformationObject(i);
tmpInput = 0;
if (tmpInfo)
{
tmpInput =
vtkPointSet::SafeDownCast(tmpInfo->Get(vtkDataObject::DATA_OBJECT()));
}
else
{
continue;
}
if(tmpInput->GetNumberOfPoints() != N_POINTS)
{
vtkErrorMacro(<<"The inputs have different numbers of points!");
return 1;
}
}
// vtkPoints *mean_points = vtkPoints::New();
MeanPoints->DeepCopy(input->GetPoints());
// our initial estimate of the mean comes from the first example in the set
// we keep a record of the first mean to fix the orientation and scale
// (which are otherwise undefined and the loop will not converge)
vtkPoints *first_mean = vtkPoints::New();
first_mean->DeepCopy(MeanPoints);
// If the similarity transform is used, the mean shape must be normalised
// to avoid shrinking
if (this->LandmarkTransform->GetMode() == VTK_LANDMARK_SIMILARITY)
{
if (!NormaliseShape(MeanPoints)) {
vtkErrorMacro(<<"Centroid size zero");
return 1;
}
if (!NormaliseShape(first_mean)) {
vtkErrorMacro(<<"Centroid size zero");
return 1;
}
}
// storage for the new mean that is being calculated
vtkPoints *new_mean = vtkPoints::New();
new_mean->SetNumberOfPoints(N_POINTS);
// compute mean and align all the shapes to it, until convergence
int converged=0; // bool converged=false
int iterations=0;
const int MAX_ITERATIONS=5;
double difference;
double point[3],p[3],p2[3];
double outPoint[3];
do { // (while not converged)
// align each pointset with the mean
for(i=0;i<N_SETS;i++)
{
this->LandmarkTransform->SetSourceLandmarks(this->GetOutput(i)->GetPoints());
this->LandmarkTransform->SetTargetLandmarks(MeanPoints);
this->LandmarkTransform->Update();
for(v=0;v<N_POINTS;v++)
{
this->LandmarkTransform->InternalTransformPoint(
this->GetOutput(i)->GetPoint(v), outPoint);
this->GetOutput(i)->GetPoints()->SetPoint(v, outPoint);
}
}
// compute the new mean (just average the point locations)
for(v=0;v<N_POINTS;v++)
{
point[0]=0.0F;
point[1]=0.0F;
point[2]=0.0F;
for(i=0;i<N_SETS;i++)
{
this->GetOutput(i)->GetPoint(v, p);
point[0]+=p[0];
point[1]+=p[1];
point[2]+=p[2];
}
p[0] = point[0]/(double)N_SETS;
p[1] = point[1]/(double)N_SETS;
p[2] = point[2]/(double)N_SETS;
new_mean->SetPoint(v, p);
}
// align the new mean with the fixed mean if the transform
// is similarity or rigidbody. It is not yet decided what to do with affine
if (this->LandmarkTransform->GetMode() == VTK_LANDMARK_SIMILARITY ||
this->LandmarkTransform->GetMode() == VTK_LANDMARK_RIGIDBODY){
this->LandmarkTransform->SetSourceLandmarks(new_mean);
this->LandmarkTransform->SetTargetLandmarks(first_mean);
this->LandmarkTransform->Update();
for(v=0;v<N_POINTS;v++)
{
this->LandmarkTransform->InternalTransformPoint(
new_mean->GetPoint(v), outPoint);
new_mean->SetPoint(v, outPoint);
}
}
// If the similarity transform is used, the mean shape must be normalised
// to avoid shrinking
if (this->LandmarkTransform->GetMode() == VTK_LANDMARK_SIMILARITY) {
if (!NormaliseShape(new_mean)) {
vtkErrorMacro(<<"Centroid size zero");
return 1;
}
}
// the new mean becomes our mean
// compute the difference between the two
difference = 0.0F;
for(v=0;v<N_POINTS;v++)
{
new_mean->GetPoint(v, p);
MeanPoints->GetPoint(v, p2);
difference += vtkMath::Distance2BetweenPoints(p,p2);
MeanPoints->SetPoint(v, p);
}
// test for convergence
iterations++;
vtkDebugMacro( << "Difference after " << iterations << " iteration(s) is: " << difference);
if(difference<1e-6 || iterations>=MAX_ITERATIONS) {
converged=1; // true
}
// The convergence test is that the sum of the distances between the
// points on mean(t) and mean(t-1) is less than a very small number.
// Procrustes shouldn't need more than 2 or 3 iterations but things could go wrong
// so we impose an iteration limit to avoid getting stuck in an infinite loop.
} while(!converged);
if(iterations>=MAX_ITERATIONS) {
vtkDebugMacro( << "Procrustes did not converge in " << MAX_ITERATIONS << " iterations! Objects may not be aligned. Difference = " <<
difference);
// we don't throw an Error here since the shapes most probably *are* aligned, but the
// numerical precision is worse than our convergence test anticipated.
}
else {
vtkDebugMacro( << "Procrustes required " << iterations << " iterations to converge to " <<
difference);
}
// clean up
first_mean->Delete();
new_mean->Delete();
return 1;
}
//----------------------------------------------------------------------------
// public
void vtkProcrustesAlignmentFilter::SetNumberOfInputs(int n)
{
this->SetNumberOfInputConnections(0, n);
this->SetNumberOfOutputPorts(n);
// initialise the outputs
for(int i=0;i<n;i++)
{
vtkPoints *points = vtkPoints::New();
vtkPolyData *ps = vtkPolyData::New();
ps->SetPoints(points);
points->Delete();
this->GetExecutive()->SetOutputData(i,ps);
ps->Delete();
}
// is this the right thing to be doing here? if we don't initialise the outputs here
// then the filter crashes but vtkPolyData may not be the type of the inputs
}
//----------------------------------------------------------------------------
void vtkProcrustesAlignmentFilter::SetInput(int idx, vtkPointSet *p)
{
this->SetNthInputConnection(0, idx, p ? p->GetProducerPort() : 0);
}
//----------------------------------------------------------------------------
vtkPointSet* vtkProcrustesAlignmentFilter::GetInput(int idx)
{
return vtkPointSet::SafeDownCast(
this->GetExecutive()->GetInputData(0, idx));
}
//----------------------------------------------------------------------------
int vtkProcrustesAlignmentFilter::FillInputPortInformation(
int port, vtkInformation *info)
{
int retval = this->Superclass::FillInputPortInformation(port, info);
info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 1);
return retval;
}
//----------------------------------------------------------------------------
// public
void vtkProcrustesAlignmentFilter::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os,indent);
this->LandmarkTransform->PrintSelf(os,indent.GetNextIndent());
this->MeanPoints->PrintSelf(os, indent.GetNextIndent());
}