Medical Imaging Interaction Toolkit  2018.4.99-389bf124
Medical Imaging Interaction Toolkit
mitkCovarianceMatrixCalculator.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
14 #include <mitkExceptionMacro.h>
15 #include <mitkSurface.h>
16 #include <vtkCell.h>
17 #include <vtkCellLinks.h>
18 #include <vtkPlane.h>
19 #include <vtkPointData.h>
20 #include <vtkPolyDataNormals.h>
21 #include <vtkSmartPointer.h>
22 
23 // forward declarations of private functions
24 static vtkIdList *GetNeighboursOfPoint(unsigned int index, vtkPolyData *polydata);
25 
26 static vtkIdList *CalculatePCAonPointNeighboursForNormalVector(int index,
27  double normal[3],
28  itk::Matrix<double, 3, 3> &mat,
29  double curVertex[3],
30  std::vector<mitk::Point3D> &pointList,
31  vtkPolyData *polyData);
32 
33 static itk::Matrix<double, 3, 3> ComputeCovarianceMatrix(itk::Matrix<double, 3, 3> &axes,
34  double sigma[3],
35  double normalizationValue);
36 namespace mitk
37 {
39  struct CovarianceMatrixCalculatorData
40  {
41  vtkPolyDataNormals *m_PolyDataNormals;
42  vtkPolyData *m_PolyData;
43  Surface *m_Input;
44  double m_VoronoiScalingFactor;
45  bool m_EnableNormalization;
46  double m_MeanVariance;
47 
48  CovarianceMatrixCalculatorData()
49  : m_PolyDataNormals(vtkPolyDataNormals::New()),
50  m_PolyData(nullptr),
51  m_Input(nullptr),
52  m_VoronoiScalingFactor(1.0),
53  m_EnableNormalization(false),
54  m_MeanVariance(0.0)
55  {
56  m_PolyDataNormals->SplittingOff();
57  }
58 
59  ~CovarianceMatrixCalculatorData()
60  {
61  if (m_PolyDataNormals)
62  m_PolyDataNormals->Delete();
63  }
64  };
65 }
66 
67 mitk::CovarianceMatrixCalculator::CovarianceMatrixCalculator() : d(new CovarianceMatrixCalculatorData())
68 {
69 }
70 
72 {
73  delete d;
74 }
75 
77 {
78  d->m_VoronoiScalingFactor = factor;
79 }
80 
82 {
83  d->m_EnableNormalization = state;
84 }
85 
87 {
88  return d->m_MeanVariance;
89 }
90 
92  const
93 {
95 }
96 
98 {
99  d->m_Input = input;
100 }
101 
103 {
104  double normalizationValue = -1.0;
105  vtkDataArray *normals = nullptr;
106  d->m_MeanVariance = 0.0;
107 
108  if (!d->m_Input)
109  mitkThrow() << "No input surface was set in mitk::CovarianceMatrixCalculator";
110 
111  d->m_PolyData = d->m_Input->GetVtkPolyData();
112 
113  // Optional normal calculation can be disabled to use the normals
114  // of the surface:
115  // normals = d->m_PolyData->GetPointData()->GetNormals();
117  // if ( normals == nullptr )
118  //{
119  d->m_PolyDataNormals->SetInputData(d->m_PolyData);
120  d->m_PolyDataNormals->Update();
121  normals = d->m_PolyDataNormals->GetOutput()->GetPointData()->GetNormals();
122  //}
123 
124  if (d->m_EnableNormalization)
125  normalizationValue = 1.5;
126 
127  // clear the matrixlist
128  m_CovarianceMatrixList.clear();
129  // allocate memory if required
130  if (d->m_PolyData->GetNumberOfPoints() > (vtkIdType)m_CovarianceMatrixList.capacity())
131  m_CovarianceMatrixList.reserve(d->m_PolyData->GetNumberOfPoints());
132 
133  for (vtkIdType i = 0; i < d->m_PolyData->GetNumberOfPoints(); ++i)
134  {
135  Vertex normal;
136  Vertex currentVertex;
137  Vertex variances = {0.0, 0.0, 0.0};
138  CovarianceMatrix mat;
139  mat.Fill(0.0);
140 
141  normals->GetTuple(i, normal);
142  d->m_PolyData->GetPoint(i, currentVertex);
143 
144  ComputeOrthonormalCoordinateSystem(i, normal, mat, variances, currentVertex);
145 
146  // use prefactor for sigma along surface
147  variances[0] = (d->m_VoronoiScalingFactor * variances[0]);
148  variances[1] = (d->m_VoronoiScalingFactor * variances[1]);
149  variances[2] = (d->m_VoronoiScalingFactor * variances[2]);
150 
151  d->m_MeanVariance += (variances[0] + variances[1] + variances[2]);
152  // compute the covariance matrix and save it
153  const CovarianceMatrix covarianceMatrix = ComputeCovarianceMatrix(mat, variances, normalizationValue);
154  m_CovarianceMatrixList.push_back(covarianceMatrix);
155  }
156 
157  if (d->m_EnableNormalization)
158  d->m_MeanVariance = normalizationValue / 3.0;
159  else
160  d->m_MeanVariance /= (3.0 * (double)d->m_PolyData->GetNumberOfPoints());
161 
162  // reset input
163  d->m_PolyData = nullptr;
164  d->m_Input = nullptr;
165 }
166 
167 // Get a list with the id's of all surrounding conected vertices
168 // to the current vertex at the given index in the polydata
169 vtkIdList *GetNeighboursOfPoint(unsigned int index, vtkPolyData *polydata)
170 {
171  vtkIdList *cellIds = vtkIdList::New();
172  vtkIdList *result = vtkIdList::New();
173  polydata->GetPointCells(index, cellIds);
174 
175  for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); j++)
176  {
177  vtkIdList *newPoints = polydata->GetCell(cellIds->GetId(j))->GetPointIds();
178  for (vtkIdType k = 0; k < newPoints->GetNumberOfIds(); k++)
179  {
180  // if point has not yet been inserted add id
181  if (result->IsId(newPoints->GetId(k)) == -1)
182  {
183  result->InsertNextId(newPoints->GetId(k));
184  }
185  }
186  }
187  cellIds->Delete();
188  return result;
189 }
190 
191 // Computes a primary component analysis of the surounding vertices
192 // of the verex at the current index.
194  double normal[3],
195  itk::Matrix<double, 3, 3> &mat,
196  double curVertex[3],
197  std::vector<mitk::Point3D> &pointList,
198  vtkPolyData *polyData)
199 {
200  typedef std::vector<mitk::Point3D> VectorType;
201  typedef VectorType::const_iterator ConstPointIterator;
202  typedef double Vertex[3];
203 
204  Vertex mean = {0.0, 0.0, 0.0};
205  Vertex tmp = {0.0, 0.0, 0.0};
206  vtkIdList *neighbourPoints = GetNeighboursOfPoint(index, polyData);
207  const vtkIdType size = neighbourPoints->GetNumberOfIds();
208 
209  // reserve memory for all neighbours
210  pointList.reserve(size);
211 
212  // project neighbours on plane given by normal
213  // and compute mean
214  for (vtkIdType i = 0; i < size; ++i)
215  {
216  mitk::Point3D p;
217  Vertex resultPoint;
218 
219  polyData->GetPoint((neighbourPoints->GetId(i)), tmp);
220 
221  vtkPlane::GeneralizedProjectPoint(tmp, curVertex, normal, resultPoint);
222 
223  p[0] = resultPoint[0];
224  p[1] = resultPoint[1];
225  p[2] = resultPoint[2];
226 
227  mean[0] += p[0];
228  mean[1] += p[1];
229  mean[2] += p[2];
230 
231  pointList.push_back(p);
232  }
233 
234  mean[0] /= (double)size;
235  mean[1] /= (double)size;
236  mean[2] /= (double)size;
237 
238  // compute the covariances with matrix multiplication
239  for (ConstPointIterator it = pointList.begin(); it != pointList.end(); ++it)
240  {
241  tmp[0] = ((*it)[0] - mean[0]);
242  tmp[1] = ((*it)[1] - mean[1]);
243  tmp[2] = ((*it)[2] - mean[2]);
244 
245  // on diagonal elements
246  mat[0][0] += tmp[0] * tmp[0];
247  mat[1][1] += tmp[1] * tmp[1];
248  mat[2][2] += tmp[2] * tmp[2];
249 
250  // of diagonal elements
251  mat[1][0] += tmp[0] * tmp[1];
252  mat[2][0] += tmp[0] * tmp[2];
253  mat[2][1] += tmp[1] * tmp[2];
254  }
255 
256  // copy upper triangle to lower triangle,
257  // we got a symetric matrix
258  mat[0][1] = mat[1][0];
259  mat[0][2] = mat[2][0];
260  mat[1][2] = mat[2][1];
261 
262  // variance
263  mat /= (size - 1);
264 
265  vnl_svd<double> svd(mat.GetVnlMatrix());
266 
267  for (int i = 0; i < 3; ++i)
268  for (int j = 0; j < 3; ++j)
269  mat[i][j] = svd.U()[j][i];
270 
271  return neighbourPoints;
272 }
273 
274 // Computes an orthonormal system for a vertex with it's surrounding neighbours.
276  const int index, Vertex normal, CovarianceMatrix &axes, Vertex variances, Vertex curVertex)
277 {
278  typedef std::vector<mitk::Point3D> VectorType;
279  typedef VectorType::const_iterator ConstPointIterator;
280  VectorType projectedPoints;
281 
282  Vertex meanValues = {0.0, 0.0, 0.0};
283  // project neighbours to new coordinate system and get principal axes
284  vtkIdList *neighbourPoints =
285  CalculatePCAonPointNeighboursForNormalVector(index, normal, axes, curVertex, projectedPoints, d->m_PolyData);
286 
287  // Set the normal as the third principal axis
288  axes[2][0] = normal[0];
289  axes[2][1] = normal[1];
290  axes[2][2] = normal[2];
291 
292  for (vtkIdType i = 0; i < neighbourPoints->GetNumberOfIds(); ++i)
293  {
294  mitk::Point3D projectedPoint;
295  Vertex curNeighbour;
296  d->m_PolyData->GetPoint(neighbourPoints->GetId(i), curNeighbour);
297 
298  curNeighbour[0] = curNeighbour[0] - curVertex[0];
299  curNeighbour[1] = curNeighbour[1] - curVertex[1];
300  curNeighbour[2] = curNeighbour[2] - curVertex[2];
301 
302  for (int k = 0; k < 3; ++k)
303  {
304  projectedPoint[k] = axes[k][0] * curNeighbour[0] + axes[k][1] * curNeighbour[1] + axes[k][2] * curNeighbour[2];
305 
306  meanValues[k] += projectedPoint[k];
307  }
308  // reuse the allocated vector from the PCA on the point neighbours
309  projectedPoints[i] = projectedPoint;
310  }
311 
312  meanValues[0] /= (double)projectedPoints.size();
313  meanValues[1] /= (double)projectedPoints.size();
314  meanValues[2] /= (double)projectedPoints.size();
315 
316  // compute variances along new axes
317  for (ConstPointIterator it = projectedPoints.begin(); it != projectedPoints.end(); ++it)
318  {
319  const mitk::Point3D &p = *it;
320 
321  variances[0] += (p[0] - meanValues[0]) * (p[0] - meanValues[0]);
322  variances[1] += (p[1] - meanValues[1]) * (p[1] - meanValues[1]);
323  variances[2] += (p[2] - meanValues[2]) * (p[2] - meanValues[2]);
324  }
325 
326  variances[0] /= (double)(projectedPoints.size() - 1);
327  variances[1] /= (double)(projectedPoints.size() - 1);
328  variances[2] /= (double)(projectedPoints.size() - 1);
329 
330  // clean up
331  neighbourPoints->Delete();
332 }
333 
334 // Sorts the axes of the computed orthonormal system based on
335 // the eigenvalues in a descending order
336 itk::Matrix<double, 3, 3> ComputeCovarianceMatrix(itk::Matrix<double, 3, 3> &axes,
337  double sigma[3],
338  double normalizationValue)
339 {
340  unsigned int idxMax, idxMin, idxBetween;
341  itk::Matrix<double, 3, 3> returnValue;
342  itk::Matrix<double, 3, 3> V;
343  itk::Matrix<double, 3, 3> diagMatrix;
344  diagMatrix.Fill(0.0);
345 
346  if (sigma[0] >= sigma[1] && sigma[0] >= sigma[2])
347  {
348  idxMax = 0;
349  if (sigma[1] >= sigma[2])
350  {
351  idxBetween = 1;
352  idxMin = 2;
353  }
354  else
355  {
356  idxBetween = 2;
357  idxMin = 1;
358  }
359  }
360  else if (sigma[1] >= sigma[0] && sigma[1] >= sigma[2])
361  {
362  idxMax = 1;
363  if (sigma[0] >= sigma[2])
364  {
365  idxBetween = 0;
366  idxMin = 2;
367  }
368  else
369  {
370  idxBetween = 2;
371  idxMin = 0;
372  }
373  }
374  else // index 2 corresponds to largest sigma
375  {
376  idxMax = 2;
377  if (sigma[0] >= sigma[1])
378  {
379  idxBetween = 0;
380  idxMin = 1;
381  }
382  else
383  {
384  idxBetween = 1;
385  idxMin = 0;
386  }
387  }
388 
389  V[0][0] = axes[idxMax][0];
390  V[1][0] = axes[idxMax][1];
391  V[2][0] = axes[idxMax][2];
392  V[0][1] = axes[idxBetween][0];
393  V[1][1] = axes[idxBetween][1];
394  V[2][1] = axes[idxBetween][2];
395  V[0][2] = axes[idxMin][0];
396  V[1][2] = axes[idxMin][1];
397  V[2][2] = axes[idxMin][2];
398 
399  diagMatrix[0][0] = sigma[idxMax];
400  diagMatrix[1][1] = sigma[idxBetween];
401  diagMatrix[2][2] = sigma[idxMin];
402 
403  returnValue = V * diagMatrix * V.GetTranspose();
404 
405  if (normalizationValue > 0.0)
406  {
407  double trace = returnValue[0][0] + returnValue[1][1] + returnValue[2][2];
408  returnValue *= (normalizationValue / trace);
409  }
410 
411  return returnValue;
412 }
Class for storing surfaces (vtkPolyData).
Definition: mitkSurface.h:28
float k(1.0)
const CovarianceMatrixList & GetCovarianceMatrices() const
std::vector< CovarianceMatrix > CovarianceMatrixList
DataCollection - Class to facilitate loading/accessing structured data.
static vtkIdList * CalculatePCAonPointNeighboursForNormalVector(int index, double normal[3], itk::Matrix< double, 3, 3 > &mat, double curVertex[3], std::vector< mitk::Point3D > &pointList, vtkPolyData *polyData)
static vtkIdList * GetNeighboursOfPoint(unsigned int index, vtkPolyData *polydata)
void ComputeOrthonormalCoordinateSystem(const int index, Vertex normal, CovarianceMatrix &principalComponents, Vertex variances, Vertex curVertex)
#define mitkThrow()
static itk::Matrix< double, 3, 3 > ComputeCovarianceMatrix(itk::Matrix< double, 3, 3 > &axes, double sigma[3], double normalizationValue)