SpectralDecomposition.h
1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2013-2015 Imperial College London
5  * Copyright 2013-2015 Andreas Schuh
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #ifndef MIRTK_SpectralDecomposition_H
21 #define MIRTK_SpectralDecomposition_H
22 
23 #include "mirtk/Pair.h"
24 #include "mirtk/Array.h"
25 #include "mirtk/PointSet.h"
26 #include "mirtk/Vector.h"
27 #include "mirtk/Matrix.h"
28 #include "mirtk/SparseMatrix.h"
29 #include "mirtk/Parallel.h"
30 
31 #include "mirtk/PointSetExport.h"
32 
33 #include "vtkSmartPointer.h"
34 #include "vtkPolyData.h"
35 #include "vtkPointData.h"
36 #include "vtkDataArray.h"
37 #include "vtkIdList.h"
38 #include "vtkAbstractPointLocator.h"
39 
40 
41 namespace mirtk {
42 namespace SpectralDecomposition {
43 
44 
45 // =============================================================================
46 // Types and constants
47 // =============================================================================
48 
49 typedef GenericSparseMatrix<double> SparseMatrix;
50 typedef Array<Pair<int, double> > FeatureWeights;
51 
52 const double EPSILON = 1e-6;
53 
54 // =============================================================================
55 // Bipartite graph matching / Optimal assignment problem
56 // =============================================================================
57 
58 /// Find optimal assignment given cost matrix using the Hungarian method
59 Array<int> WeightedMatching(const Matrix &cost);
60 
61 // =============================================================================
62 // Find closest points
63 // =============================================================================
64 
65 // -----------------------------------------------------------------------------
66 /// For each point in the first set, find closest point in the second set
68 {
69  // Attention: vtkKdTree/vtkKdTreePointLocator is not thread-safe
70  // (cf. http://www.vtk.org/Bug/view.php?id=15206 )!
71  const PointSet &_P1;
72  vtkAbstractPointLocator *_P2;
73  Array<int> &_Idx;
74 
75  FindClosestPoints(const PointSet &, vtkAbstractPointLocator *, Array<int> &);
76 
77 public:
78 
79  /// Internal function executed by worker threads
80  void operator ()(const blocked_range<int> &re) const
81  {
82  for (int i = re.begin(); i != re.end(); ++i) {
83  const Point &p = _P1(i);
84  _Idx[i] = static_cast<int>(_P2->FindClosestPoint(p._x, p._y, p._z));
85  }
86  }
87 
88  /// For each point in the first set, find closest point in the second set
89  static Array<int> Run(const PointSet &p1, const PointSet &p2);
90 };
91 
92 // -----------------------------------------------------------------------------
93 /// For each point in the first set, find closest point in the second set
95 {
96  // Attention: vtkKdTree/vtkKdTreePointLocator is not thread-safe
97  // (cf. http://www.vtk.org/Bug/view.php?id=15206 )!
98  const PointSet &_P1;
99  vtkAbstractPointLocator *_P2;
100  Array<Array<int> > &_Idx;
101  int _Num;
102 
103  FindClosestNPoints(const PointSet &, vtkAbstractPointLocator *, Array<Array<int> > &, int);
104 
105 public:
106 
107  /// Internal function executed by worker threads
108  void operator ()(const blocked_range<int> &re) const
109  {
110  double p[3];
111  vtkIdList *ids = vtkIdList::New();
112  for (int i = re.begin(); i != re.end(); ++i) {
113  _P1.GetPoint(i, p);
114  _P2->FindClosestNPoints(_Num, p, ids);
115  for (vtkIdType j = 0; j < ids->GetNumberOfIds(); ++j) {
116  _Idx[i][j] = static_cast<int>(ids->GetId(j));
117  }
118  }
119  ids->Delete();
120  }
121 
122  /// For each point in the first set, find closest point in the second set
123  static Array<Array<int> > Run(const PointSet &p1, const PointSet &p2, int);
124 };
125 
126 // =============================================================================
127 // Node/edge feature weights
128 // =============================================================================
129 
130 /// Get absolute edge weights
131 ///
132 /// \param[in] dataset Surface mesh.
133 /// \param[in] names Names of feature arrays in \p dataset, e.g., "curvature",
134 /// "sulcal depth", "cortical thickness", ..., to use.
135 /// \param[in] weights Relative weights of edge features. If \c NULL, the
136 /// default weight is used for all features. If only one
137 /// weight value given, it is used for all features.
138 /// Otherwise, a separate weight must be given for each
139 /// named feature array.
140 ///
141 /// \returns Pairs of feature array indices and corresponding absolute weights.
142 FeatureWeights EdgeWeights(vtkPolyData *dataset,
143  const Array<string> &names,
144  const Array<double> *weights = NULL);
145 
146 /// Get absolute node weights
147 ///
148 /// \param[in] dataset Surface mesh.
149 /// \param[in] names Names of feature arrays in \p dataset, e.g., "curvature",
150 /// "sulcal depth", "cortical thickness", ..., to use.
151 /// \param[in] weights Relative weights of node features. If \c NULL, the
152 /// default weight is used for all features. If only one
153 /// weight value given, it is used for all features.
154 /// Otherwise, a separate weight must be given for each
155 /// named feature array.
156 ///
157 /// \returns Pairs of feature array indices and corresponding absolute weights.
158 FeatureWeights NodeWeights(vtkPolyData *dataset,
159  const Array<string> &names,
160  const Array<double> *weights = NULL);
161 
162 // -----------------------------------------------------------------------------
163 /// Size of node feature vectors
164 inline int NumberOfFeatures(vtkPolyData *dataset, const FeatureWeights *weights = NULL)
165 {
166  int d = 3;
167  if (weights) {
168  vtkPointData * const data = dataset->GetPointData();
169  FeatureWeights::const_iterator weight = weights->begin();
170  while (weight != weights->end()) {
171  vtkDataArray * const feature = data->GetArray(weight->first);
172  d += feature->GetNumberOfComponents();
173  ++weight;
174  }
175  }
176  return d;
177 }
178 
179 // -----------------------------------------------------------------------------
180 /// Get node features
181 inline void GetFeatures(vtkPolyData *dataset, vtkIdType i, double *p, const FeatureWeights *weights = NULL)
182 {
183  dataset->GetPoint(i, p);
184  p += 3;
185  if (weights) {
186  vtkPointData * const data = dataset->GetPointData();
187  FeatureWeights::const_iterator weight = weights->begin();
188  while (weight != weights->end()) {
189  vtkDataArray * const feature = data->GetArray(weight->first);
190  feature->GetTuple(i, p);
191  for (int c = 0; c < feature->GetNumberOfComponents(); ++c) {
192  p[c] *= weight->second;
193  }
194  p += feature->GetNumberOfComponents();
195  ++weight;
196  }
197  }
198 }
199 
200 // -----------------------------------------------------------------------------
201 /// Calculate squared distance between 3-dimensional points
202 inline double Distance2BetweenPoints(const double *p1, const double *p2)
203 {
204  double dx, dist2;
205  dx = p2[0] - p1[0], dist2 = dx * dx;
206  dx = p2[1] - p1[1], dist2 += dx * dx;
207  dx = p2[2] - p1[2], dist2 += dx * dx;
208  return dist2;
209 }
210 
211 // -----------------------------------------------------------------------------
212 /// Calculate squared distance between d-dimensional points
213 inline double Distance2BetweenPoints(const double *p1, const double *p2, int d)
214 {
215  double dx, dist2 = .0;
216  for (int i = 0; i < d; ++i) {
217  dx = p2[i] - p1[i];
218  dist2 += dx * dx;
219  }
220  return dist2;
221 }
222 
223 // =============================================================================
224 // Graph connectivity
225 // =============================================================================
226 
227 /// Calculate intra-mesh affinity weights
228 void AdjacencyMatrix(SparseMatrix::Entries adjw[],
230  int r1, int c1, vtkPolyData *dataset,
231  FeatureWeights weights = FeatureWeights());
232 
233 /// Calculate intra-mesh affinity weights
234 void AdjacencyMatrix(SparseMatrix &adjw,
235  vtkPolyData *dataset,
236  FeatureWeights weights = FeatureWeights());
237 
238 /// Calculate intra-mesh affinity weights
239 SparseMatrix AdjacencyMatrix(vtkPolyData *dataset,
240  SparseMatrix::StorageLayout layout = SparseMatrix::CCS,
241  FeatureWeights weights = FeatureWeights());
242 
243 /// Calculate inter-mesh affinity weights (with links at subsampled points only)
244 template <typename TCorWeight>
245 void ConnectivityMatrix(SparseMatrix::Entries lnkw[],
247  int r1,
248  int c1,
249  vtkPolyData *target,
250  const Array<int> *target_sample,
251  vtkPolyData *source,
252  const Array<int> *source_sample,
253  const GenericSparseMatrix<TCorWeight> *weight,
254  bool weight_transpose = false);
255 
256 /// Calculate inter-mesh affinity weights (with links at subsampled points only)
257 template <typename TCorWeight>
258 void ConnectivityMatrix(SparseMatrix &lnkw,
259  vtkPolyData *target,
260  const Array<int> *target_sample,
261  vtkPolyData *source,
262  const Array<int> *source_sample,
263  const GenericSparseMatrix<TCorWeight> *weight,
264  bool transpose = false);
265 
266 /// Calculate inter-mesh affinity weights (with links at subsampled points only)
267 template <typename TCorWeight>
268 SparseMatrix ConnectivityMatrix(vtkPolyData *target,
269  const Array<int> *target_sample,
270  vtkPolyData *source,
271  const Array<int> *source_sample,
272  const GenericSparseMatrix<TCorWeight> *weight,
273  bool transpose = false,
274  SparseMatrix::StorageLayout layout = SparseMatrix::CCS);
275 
276 /// Compute node degrees
277 void Degree(Vector &D, const SparseMatrix &A);
278 
279 /// Compute node degrees
280 Vector Degree(const SparseMatrix &A);
281 
282 /// Compute degree matrix
283 void DegreeMatrix(SparseMatrix &D, const SparseMatrix &A);
284 
285 /// Compute degree matrix
286 SparseMatrix DegreeMatrix(const SparseMatrix &A);
287 
288 /// Compute normalized Laplacian
289 ///
290 /// \param[out] L Normalized Laplacian matrix.
291 /// \param[in] A (Weighted) Adjacency matrix.
292 /// \param[in] G Optional node weights. If \c NULL, G = Id.
293 /// \param[in] D Main diagonal of (precomputed) degree matrix.
294 ///
295 /// \returns L = G D^-1 (D - A) = G - D^-1 A
296 void NormalizedLaplacian(SparseMatrix &L, const SparseMatrix &A,
297  const Vector *G = NULL, const Vector *D = NULL);
298 
299 /// Compute general graph Laplacian
300 ///
301 /// \param[out] L General graph Laplacian matrix.
302 /// \param[in] dataset Surface mesh or contour with optional extra
303 /// feature arrays used as edge and/or node weights.
304 /// \param[in] edge_weights Weights of edge features if any (cf. EdgeWeights).
305 /// \param[in] node_weights Weights of node features if any (cf. NodeWeights).
306 void Laplacian(SparseMatrix &L, vtkPolyData *dataset,
307  FeatureWeights edge_weights = FeatureWeights(),
308  FeatureWeights node_weights = FeatureWeights());
309 
310 // =============================================================================
311 // Spectral decomposition
312 // =============================================================================
313 
314 /// Compute spectral components
315 ///
316 /// This function performs a spectral analysis of the given graph Laplacian matrix.
317 ///
318 /// \param[in] L General graph Laplacian matrix.
319 /// \param[in] k Number of spectral components.
320 /// \param[out] m Eigenmodes of \c d, i.e., spectral coordinates.
321 /// \param[out] v Eigenvalues of \c d, i.e., resonance frequencies.
322 ///
323 /// \returns Actual number of spectral components found.
324 int ComputeEigenmodes(const SparseMatrix &L, int k, Matrix &m, Vector &v);
325 
326 /// Compute spectral components
327 ///
328 /// This function performs a spectral analysis of the general graph Laplacian
329 /// matrix of the specified surface mesh.
330 ///
331 /// \param[in] d Surface mesh or contour with optional extra
332 /// features used as edge and/or node weights.
333 /// \param[in] k Number of spectral components.
334 /// \param[out] m Eigenmodes of \c d, i.e., spectral coordinates.
335 /// \param[out] v Eigenvalues of \c d, i.e., resonance frequencies.
336 /// \param[in] ew Weights of edge features if any (cf. EdgeWeights).
337 /// \param[in] nw Weights of node features if any (cf. NodeWeights).
338 ///
339 /// \returns Actual number of spectral components found.
340 int ComputeEigenmodes(vtkPolyData *d, int k, Matrix &m, Vector &v,
341  FeatureWeights ew = FeatureWeights(),
342  FeatureWeights nw = FeatureWeights());
343 
344 /// Compute spectral components
345 ///
346 /// This function performs a spectral analysis of the general graph Laplacian
347 /// matrix of the specified surface mesh. The components of the resulting \p k
348 /// eigenmodes are stored as point data of the input mesh in the array named
349 /// \"eigenmodes\".
350 ///
351 /// \param[in,out] d Surface mesh or contour with optional extra
352 /// features used as edge and/or node weights.
353 /// \param[in] k Number of spectral components.
354 /// \param[in] ew Weights of edge features if any (cf. EdgeWeights).
355 /// \param[in] nw Weights of node features if any (cf. NodeWeights).
356 ///
357 /// \returns Actual number of spectral components found.
358 int ComputeEigenmodes(vtkPolyData *d, int k,
359  FeatureWeights ew = FeatureWeights(),
360  FeatureWeights nw = FeatureWeights());
361 
362 /// Reorder eigenmodes to correct for sign ambiguity and multiplicity
363 ///
364 /// \param[in] p1 First set of spatial coordinates.
365 /// \param[in] m1 First set of original eigenmodes.
366 /// \param[in] v1 Eigenvalues corresponding to first set of eigenmodes.
367 /// \param[in] p2 Second set of spatial coordinates.
368 /// \param[in,out] m2 Second set of original eigenmodes which are reordered.
369 /// \param[in] v2 Eigenvalues corresponding to second set of eigenmodes.
370 /// \param[in] ratio Downsample ratio, e.g., 2 to use only half of the points.
371 /// \param[in] knn Number of spatially nearest neighbors to consider.
372 ///
373 /// \returns Confidence/cost of each match.
374 Vector MatchEigenmodes(const PointSet &p1, const Matrix &m1, const Vector &v1,
375  const PointSet &p2, Matrix &m2, Vector &v2,
376  int ratio = 10, int knn = 1);
377 
378 /// Reorder eigenmodes to correct for sign ambiguity and multiplicity
379 ///
380 /// \param[in] p1 First set of spatial coordinates.
381 /// \param[in] m1 First set of original eigenmodes.
382 /// \param[in] v1 Eigenvalues corresponding to first set of eigenmodes.
383 /// \param[in] p2 Second set of spatial coordinates.
384 /// \param[in,out] m2 Second set of original eigenmodes which are reordered.
385 /// \param[in] v2 Eigenvalues corresponding to second set of eigenmodes.
386 /// \param[in] ratio Downsample ratio, e.g., 2 to use only half of the points.
387 /// \param[in] knn Number of spatially nearest neighbors to consider.
388 ///
389 /// \returns Confidence/cost of each match.
390 Vector MatchEigenmodes(vtkPoints *p1, const Matrix &m1, const Vector &v1,
391  vtkPoints *p2, Matrix &m2, Vector &v2,
392  int ratio = 10, int knn = 1);
393 
394 /// Compute weights of eigenmodes given the match confidence
395 ///
396 /// Reduce importance of eigenmode by lowering weight for
397 /// - Eigenmodes corresponding to high frequencies.
398 /// - Pairs of eigenmodes with low confidence in the match.
399 ///
400 /// \param[in] m1 First set of eigenmodes.
401 /// \param[in] v1 Eigenvalues corresponding to first set of eigenmodes.
402 /// \param[in] m2 Second set of eigenmodes.
403 /// \param[in] v2 Eigenvalues corresponding to second set of eigenmodes.
404 /// \param[in,out] w Input: Match confidence/cost. Output: Weights of eigenmodes.
405 void WeightEigenmodes(const Matrix &m1, const Vector &v1,
406  const Matrix &m2, const Vector &v2, Vector &w);
407 
408 /// Scale eigenmodes given their individual weights
409 ///
410 /// \param[in,out] m (Joint) Eigenmodes.
411 /// \param[in] w Weights of eigenmodes.
412 void ScaleEigenmodes(Matrix &m, const Vector &w);
413 
414 /// Normalize eigenmodes individually to the range [-1, 1]
415 void NormalizeEigenmodes(Matrix &m);
416 
417 /// Scale eigenmodes given their individual weights
418 ///
419 /// \param[in,out] m1 First set of eigenmodes.
420 /// \param[in,out] m2 Second set of eigenmodes.
421 /// \param[in] w Weights of eigenmodes (cf. WeightEigenmodes).
422 void ScaleEigenmodes(Matrix &m1, Matrix &m2, const Vector &w);
423 
424 /// Convert eigenmodes to dataset point data array
425 vtkSmartPointer<vtkDataArray>
426 ToPointDataArray(const Matrix &m, int r1 = 0,
427  int n = -1, int k = -1, const char *name = "eigenmodes");
428 
429 /// Set spectral components as point data of vtkPolyData instance
430 ///
431 /// \returns Index of eigenmodes point data array.
432 int SetEigenmodes(vtkPolyData *d, const Matrix &m, const char *name = "eigenmodes");
433 
434 /// Set spectral components as point data of vtkPolyData instance
435 ///
436 /// \returns Index of eigenmodes point data array.
437 int SetEigenmodes(vtkPolyData *d, const Matrix &m,
438  int r1, int k = -1, const char *name = "eigenmodes");
439 
440 /// Get spectral components from point data of vtkPolyData instance
441 Matrix GetEigenmodes(vtkPolyData *d, const char *name = "eigenmodes");
442 
443 /// Compute spectral components and account for sign ambiguity and multiplicity
444 ///
445 /// The signs of the computed eigenmodes of the second dataset are flipped
446 /// and the eigenmodes reordered in order to minimize the bipartite assignment
447 /// cost between the eigenmodes of two datasets. This accounts for the sign
448 /// ambiguity and multiplicity of the eigenvalue problem. The assignment costs
449 /// are then used to scale the eigenmodes. The resulting spectral components
450 /// are added to the point data of the respective vtkPolyData instance with
451 /// name "eigenmodes". An existing point data array with this name will be
452 /// overwritten.
453 ///
454 /// \param[in,out] d1 Surface mesh or contour with optional extra
455 /// features used as edge and/or node weights.
456 /// \param[in,out] d2 Surface mesh or contour with optional extra
457 /// features used as edge and/or node weights.
458 /// \param[in] k Number of desired spectral components.
459 /// \param[in] ew Weights of edge features if any (cf. EdgeWeights).
460 /// \param[in] nw Weights of node features if any (cf. NodeWeights).
461 ///
462 /// \returns Actual number of spectral components found for both datasets.
463 int ComputeEigenmodes(vtkPolyData *d1, vtkPolyData *d2, int k,
464  FeatureWeights ew = FeatureWeights(),
465  FeatureWeights nw = FeatureWeights());
466 
467 
468 } } // namespace mirtk::SpectralDecomposition
469 
470 #endif // MIRTK_SpectralDecomposition_H
For each point in the first set, find closest point in the second set.
double _x
x coordinate of Point
Definition: Point.h:46
One-dimensional range.
Definition: Parallel.h:155
SparseDoubleMatrix SparseMatrix
Sparse matrix with default entry value type.
static Array< int > Run(const PointSet &p1, const PointSet &p2)
For each point in the first set, find closest point in the second set.
Definition: IOConfig.h:41
void operator()(const blocked_range< int > &re) const
Internal function executed by worker threads.
Point & GetPoint(int)
Get n-th point.
Definition: PointSet.h:330
For each point in the first set, find closest point in the second set.
MIRTKCU_API float2x2 transpose(float2x2 m)
Transpose 2x2 matrix.
Definition: Math.h:681
double _z
z coordinate of Point
Definition: Point.h:48
Array< Entry > Entries
List of non-zero entries.
Definition: SparseMatrix.h:70
double _y
y coordinate of Point
Definition: Point.h:47