ANIMA  4.0
animaGaussianMCMVariableProjectionCost.cxx
Go to the documentation of this file.
2 #include <cmath>
3 #include <algorithm>
4 
5 #include <boost/math/special_functions/gamma.hpp>
6 #include <boost/math/special_functions/fpclassify.hpp>
7 
8 namespace anima
9 {
10 
13 {
14  unsigned int nbParams = parameters.GetSize();
15 
16  // Set MCM parameters
17  m_TestedParameters.resize(nbParams);
18 
19  for (unsigned int i = 0;i < nbParams;++i)
20  m_TestedParameters[i] = parameters[i];
21 
22  m_MCMStructure->SetParametersFromVector(m_TestedParameters);
23 
24  this->PrepareDataForLLS();
25 
27 
28  m_MCMStructure->SetCompartmentWeights(m_OptimalWeights);
29  return m_Residuals;
30 }
31 
33 {
34  // This is -2log(L) so that we only have to give one formula
35  double costValue = 0;
36  unsigned int nbImages = m_Residuals.size();
37 
38  costValue = nbImages * (1.0 + std::log(2.0 * M_PI * m_SigmaSquare));
39 
40  return costValue;
41 }
42 
43 void
45 {
46  unsigned int nbValues = m_Gradients.size();
47  unsigned int numCompartments = m_IndexesUsefulCompartments.size();
48 
49  m_SigmaSquare = 0.0;
50 
51  for (unsigned int i = 0;i < nbValues;++i)
52  {
53  m_Residuals[i] = m_ObservedSignals[i];
54  for (unsigned int j = 0;j < numCompartments;++j)
55  m_Residuals[i] -= m_PredictedSignalAttenuations.get(i,j) * m_OptimalUsefulWeights[j];
56 
57  m_SigmaSquare += m_Residuals[i] * m_Residuals[i];
58  }
59 
60  m_SigmaSquare /= nbValues;
61 }
62 
63 void
65 {
66  unsigned int nbValues = m_Gradients.size();
67  unsigned int numCompartments = m_MCMStructure->GetNumberOfCompartments();
68 
69  m_OptimalWeights.resize(numCompartments);
70  std::fill(m_OptimalWeights.begin(),m_OptimalWeights.end(),0.0);
71 
72  m_IndexesUsefulCompartments.resize(numCompartments);
73  unsigned int pos = 0;
74 
75  // Trick to keep the compartments with the lowest number of parameters by default
76  // This is a trick because it assumes the first compartments (i.e. the iso ones are the ones with the lowest number of parameters)
77  for (int i = numCompartments - 1;i >= 0;--i)
78  {
79  bool duplicated = false;
80 
81  for (int j = 0;j < i;++j)
82  {
83  if (m_MCMStructure->GetCompartment(i)->IsEqual(m_MCMStructure->GetCompartment(j)))
84  {
85  duplicated = true;
86  break;
87  }
88  }
89 
90  if (!duplicated)
91  {
92  m_IndexesUsefulCompartments[pos] = i;
93  ++pos;
94  }
95  }
96 
97  numCompartments = pos;
98  m_IndexesUsefulCompartments.resize(numCompartments);
99  std::sort(m_IndexesUsefulCompartments.begin(),m_IndexesUsefulCompartments.end());
100 
101  // Compute predicted signals and jacobian
102  m_PredictedSignalAttenuations.set_size(nbValues,numCompartments);
103 
104  for (unsigned int i = 0;i < nbValues;++i)
105  {
106  for (unsigned int j = 0;j < numCompartments;++j)
107  {
108  unsigned int indexComp = m_IndexesUsefulCompartments[j];
109  double predictedSignal = m_MCMStructure->GetCompartment(indexComp)->GetFourierTransformedDiffusionProfile(m_SmallDelta, m_BigDelta, m_GradientStrengths[i], m_Gradients[i]);
110  m_PredictedSignalAttenuations.put(i,j,predictedSignal);
111  }
112  }
113 
114  m_CholeskyMatrix.set_size(numCompartments,numCompartments);
115  m_FSignals.SetSize(numCompartments);
116  bool negativeWeights = m_MCMStructure->GetNegativeWeightBounds();
117 
118  for (unsigned int i = 0;i < numCompartments;++i)
119  {
120  m_FSignals[i] = 0.0;
121  for (unsigned int j = 0;j < nbValues;++j)
122  m_FSignals[i] += m_PredictedSignalAttenuations.get(j,i) * m_ObservedSignals[j];
123 
124  if (negativeWeights)
125  m_FSignals[i] *= -1;
126 
127  for (unsigned int j = i;j < numCompartments;++j)
128  {
129  double cholValue = 0.0;
130  for (unsigned int k = 0;k < nbValues;++k)
131  cholValue += m_PredictedSignalAttenuations.get(k,i) * m_PredictedSignalAttenuations.get(k,j);
132 
133  m_CholeskyMatrix.put(i,j,cholValue);
134  if (i != j)
135  m_CholeskyMatrix.put(j,i,cholValue);
136  }
137  }
138 
139  m_CholeskySolver.SetInputMatrix(m_CholeskyMatrix);
140  m_CholeskySolver.PerformDecomposition();
141  m_OptimalUsefulWeights = m_CholeskySolver.SolveLinearSystem(m_FSignals);
142 
143  bool nnlsNeeded = false;
144 
145  for (unsigned int i = 0;i < numCompartments;++i)
146  {
147  if (m_OptimalUsefulWeights[i] <= 0.0)
148  {
149  nnlsNeeded = true;
150  break;
151  }
152  }
153 
154  if (nnlsNeeded)
155  {
156  // Check usability of cholesky matrix for NNLS
157  bool useCholeskyMatrix = true;
158  for (int i = numCompartments - 1;i >= 0;--i)
159  {
160  double normRef = 0;
161  for (unsigned int k = 0;k < numCompartments;++k)
162  normRef += m_CholeskyMatrix.get(k,i) * m_CholeskyMatrix.get(k,i);
163  normRef = std::sqrt(normRef);
164 
165  for (int j = 0;j < i;++j)
166  {
167  double normTest = 0;
168  for (unsigned int k = 0;k < numCompartments;++k)
169  normTest += m_CholeskyMatrix.get(k,j) * m_CholeskyMatrix.get(k,j);
170  normTest = std::sqrt(normTest);
171 
172  double normsProduct = normRef * normTest;
173  double dotProduct = 0.0;
174  for (unsigned int k = 0;k < numCompartments;++k)
175  dotProduct += m_CholeskyMatrix.get(k,i) * m_CholeskyMatrix.get(k,j) / normsProduct;
176 
177  if (std::abs(dotProduct - 1.0) < 1.0e-6)
178  {
179  useCholeskyMatrix = false;
180  break;
181  }
182  }
183 
184  if (!useCholeskyMatrix)
185  break;
186  }
187 
188  if (useCholeskyMatrix)
189  {
190  m_NNLSBordersOptimizer->SetDataMatrix(m_CholeskyMatrix);
191  m_NNLSBordersOptimizer->SetPoints(m_FSignals);
192  m_NNLSBordersOptimizer->SetSquaredProblem(true);
193  }
194  else
195  {
196  ParametersType observedSignals(nbValues);
197  for (unsigned int i = 0;i < nbValues;++i)
198  {
199  observedSignals[i] = m_ObservedSignals[i];
200  if (negativeWeights)
201  observedSignals[i] *= -1;
202  }
203 
204  m_NNLSBordersOptimizer->SetDataMatrix(m_PredictedSignalAttenuations);
205  m_NNLSBordersOptimizer->SetPoints(observedSignals);
206  m_NNLSBordersOptimizer->SetSquaredProblem(false);
207  }
208 
209  m_NNLSBordersOptimizer->StartOptimization();
210  m_OptimalUsefulWeights = m_NNLSBordersOptimizer->GetCurrentPosition();
211  }
212 
213  if (negativeWeights)
214  m_OptimalUsefulWeights *= -1;
215 
216  m_OptimalUsefulWeights.set_size(numCompartments);
217 
218  m_CompartmentSwitches.resize(numCompartments);
219  for (unsigned int i = 0;i < numCompartments;++i)
220  {
221  m_OptimalWeights[m_IndexesUsefulCompartments[i]] = m_OptimalUsefulWeights[i];
222  if (m_OptimalUsefulWeights[i] <= 0)
223  m_CompartmentSwitches[i] = false;
224  else
225  {
226  m_OptimalWeights[m_IndexesUsefulCompartments[i]] = m_OptimalUsefulWeights[i];
227  m_CompartmentSwitches[i] = true;
228  }
229  }
230 
231  m_Residuals.SetSize(nbValues);
232 }
233 
234 void
236 {
237  unsigned int nbValues = m_Gradients.size();
238  unsigned int numCompartments = m_IndexesUsefulCompartments.size();
239 
240  unsigned int numOnCompartments = 0;
241  for (unsigned int i = 0;i < numCompartments;++i)
242  numOnCompartments += m_CompartmentSwitches[i];
243 
244  unsigned int nbParams = m_MCMStructure->GetNumberOfParameters();
245  // Compute jacobian parts
246  vnl_matrix<double> zeroMatrix(nbValues,numCompartments,0.0);
247  m_SignalAttenuationsJacobian.resize(nbParams);
248  std::fill(m_SignalAttenuationsJacobian.begin(),m_SignalAttenuationsJacobian.end(),zeroMatrix);
249  ListType compartmentJacobian;
250 
251  m_GramMatrix.set_size(numOnCompartments,numOnCompartments);
252  m_InverseGramMatrix.set_size(numOnCompartments,numOnCompartments);
253 
254  unsigned int posX = 0;
255  unsigned int posY = 0;
256  for (unsigned int i = 0;i < numCompartments;++i)
257  {
258  if (!m_CompartmentSwitches[i])
259  continue;
260 
261  m_GramMatrix.put(posX,posX,m_CholeskyMatrix.get(i,i));
262  posY = posX + 1;
263  for (unsigned int j = i + 1;j < numCompartments;++j)
264  {
265  if (!m_CompartmentSwitches[j])
266  continue;
267 
268  double val = m_CholeskyMatrix(i,j);
269 
270  m_GramMatrix.put(posX,posY,val);
271  m_GramMatrix.put(posY,posX,val);
272  ++posY;
273  }
274 
275  ++posX;
276  }
277 
278  if (numOnCompartments > 0)
279  m_leCalculator->GetTensorPower(m_GramMatrix,m_InverseGramMatrix,-1.0);
280 
281  // Here gets F G^-1
282  m_FMatrixInverseG.set_size(nbValues,numOnCompartments);
283  for (unsigned int i = 0;i < nbValues;++i)
284  {
285  for (unsigned int j = 0;j < numOnCompartments;++j)
286  {
287  double fInvGValue = 0.0;
288  unsigned int posK = 0;
289  for (unsigned int k = 0;k < numCompartments;++k)
290  {
291  if (!m_CompartmentSwitches[k])
292  continue;
293 
294  fInvGValue += m_PredictedSignalAttenuations(i,k) * m_InverseGramMatrix(posK,j);
295  ++posK;
296  }
297 
298  m_FMatrixInverseG.put(i,j,fInvGValue);
299  }
300  }
301 
302  for (unsigned int i = 0;i < nbValues;++i)
303  {
304  unsigned int pos = 0;
305 
306  for (unsigned int j = 0;j < numCompartments;++j)
307  {
308  unsigned int indexComp = m_IndexesUsefulCompartments[j];
309 
310  compartmentJacobian = m_MCMStructure->GetCompartment(indexComp)->GetSignalAttenuationJacobian(m_SmallDelta, m_BigDelta,
312 
313  unsigned int compartmentSize = compartmentJacobian.size();
314  for (unsigned int k = 0;k < compartmentSize;++k)
315  m_SignalAttenuationsJacobian[pos+k].put(i,j,compartmentJacobian[k]);
316 
317  pos += compartmentSize;
318  }
319  }
320 }
321 
322 void
324 {
325  unsigned int nbParams = parameters.GetSize();
326  if (nbParams == 0)
327  return;
328 
329  unsigned int nbValues = m_ObservedSignals.size();
330  unsigned int numCompartments = m_IndexesUsefulCompartments.size();
331 
332  this->PrepareDataForDerivative();
333  unsigned int numOnCompartments = m_GramMatrix.rows();
334 
335  // Assume get derivative is called with the same parameters as GetValue just before
336  for (unsigned int i = 0;i < nbParams;++i)
337  {
338  if (m_TestedParameters[i] != parameters[i])
339  {
340  std::cerr << parameters << std::endl;
341  itkExceptionMacro("Get derivative not called with the same parameters as GetValue, suggestive of NaN...");
342  }
343  }
344 
345  derivative.SetSize(nbValues, nbParams);
346  derivative.Fill(0.0);
347  ListType DFw(nbValues,0.0);
348 
349  ListType tmpVec(numOnCompartments,0.0);
350 
351  for (unsigned int k = 0;k < nbParams;++k)
352  {
353  // First, compute
354  // - DFw = DF[k] w
355  std::fill(DFw.begin(),DFw.end(),0.0);
356  for (unsigned int i = 0;i < nbValues;++i)
357  {
358  DFw[i] = 0.0;
359  for (unsigned int j = 0;j < numCompartments;++j)
360  DFw[i] += m_SignalAttenuationsJacobian[k].get(i,j) * m_OptimalUsefulWeights[j];
361  }
362 
363  // Then, compute
364  // - tmpVec = F^T DF[k] w - (DF[k])^T Residuals
365  unsigned int pos = 0;
366  for (unsigned int j = 0;j < numCompartments;++j)
367  {
368  if (!m_CompartmentSwitches[j])
369  continue;
370 
371  tmpVec[pos] = 0.0;
372 
373  for (unsigned int i = 0;i < nbValues;++i)
374  tmpVec[pos] += m_PredictedSignalAttenuations.get(i,j) * DFw[i] - m_SignalAttenuationsJacobian[k].get(i,j) * m_Residuals[i];
375 
376  ++pos;
377  }
378 
379  // Finally, get
380  // - derivative = FMatrixInverseG tmpVec - DFw
381  for (unsigned int i = 0;i < nbValues;++i)
382  {
383  double derivativeVal = - DFw[i];
384 
385  for (unsigned int j = 0;j < numOnCompartments;++j)
386  derivativeVal += m_FMatrixInverseG.get(i,j) * tmpVec[j];
387 
388  derivative.put(i,k,derivativeVal);
389  }
390  }
391 
392  bool problem = false;
393  for (unsigned int i = 0;i < nbParams;++i)
394  {
395  if (!std::isfinite(derivative.get(0,i)))
396  {
397  problem = true;
398  break;
399  }
400  }
401 
402  if (problem)
403  {
404  std::cerr << "Derivative: " << derivative << std::endl;
405  std::cerr << "Optimal weights: " << m_OptimalUsefulWeights << std::endl;
406  std::cerr << "Gram matrix inverse: " << m_InverseGramMatrix << std::endl;
407  std::cerr << "Residuals: " << m_Residuals << std::endl;
408 
409  std::cerr << "Params: " << parameters << std::endl;
410  itkExceptionMacro("Non finite derivative");
411  }
412 }
413 
414 void
416 {
417  unsigned int nbParams = derivativeMatrix.columns();
418  unsigned int nbValues = derivativeMatrix.rows();
419 
420  // Current derivative of the system is 2 residual^T derivativeMatrix
421  // To handle the fact that the cost function is -2 log(L) and not the rms problem itself
422  derivative.set_size(nbParams);
423  for (unsigned int i = 0;i < nbParams;++i)
424  {
425  derivative[i] = 0.0;
426  for (unsigned int j = 0;j < nbValues;++j)
427  derivative[i] += m_Residuals[j] * derivativeMatrix.get(j,i);
428 
429  derivative[i] *= 2.0 / m_SigmaSquare;
430  }
431 }
432 
433 } // end namespace anima
itk::OptimizerParameters< double > ParametersType
MCMPointer m_MCMStructure
void GetDerivativeMatrix(const ParametersType &parameters, DerivativeMatrixType &derivative) ITK_OVERRIDE
Get residual derivatives for a given set of parameters, returns a matrix of residuals derivatives...
void PerformDecomposition()
Performs LDL decomposition from input matrix.
void SetInputMatrix(const MatrixType &matrix)
Set input matrix to decompose.
MeasureType GetValues(const ParametersType &parameters) ITK_OVERRIDE
Get residual values for a given set of parameters, returns a vector of residuals. ...
double GetCurrentCostValue() ITK_OVERRIDE
For the current set of parameters, compute the cost function value, requires GetValues to be called f...
void SolveLinearLeastSquares()
Computes maximum likelihood estimates of weights.
void GetCurrentDerivative(DerivativeMatrixType &derivativeMatrix, DerivativeType &derivative) ITK_OVERRIDE
Get cost function derivatives from a derivative matrix obtained from GetDerivativeMatrix.
VectorType & SolveLinearSystem(const VectorType &b)
Solves linear system Ax=b and outputs result in new variable.
ListType m_GradientStrengths
MCMType::ListType ListType
itk::Array2D< double > DerivativeMatrixType
itk::Array< double > MeasureType
std::vector< Vector3DType > m_Gradients
itk::Array< double > DerivativeType