ANIMA  4.0
animaNODDICompartment.cxx
Go to the documentation of this file.
3 #include <animaErrorFunctions.h>
4 #include <animaKummerFunctions.h>
6 #include <animaMCMConstants.h>
7 #include <boost/math/special_functions/legendre.hpp>
8 
9 namespace anima
10 {
11 
12 void NODDICompartment::UpdateSignals(double bValue, const Vector3DType &gradient)
13 {
14  if (std::abs(bValue - m_CurrentBValue) < 1.0e-6 && anima::ComputeNorm(gradient - m_CurrentGradient) < 1.0e-6 && !m_ModifiedParameters)
15  return;
16 
17  // This implementation of NODDI signal expression follows three papers:
18  // 1) Zhang et al., 2012, Neuroimage: paper that introduces NODDI, with
19  // integral representation of the signal in terms of NODDI parameters
20  // (Eqs 1-8).
21  // 2) Zhang et al., 2011, Neuroimage: it shows how the integral
22  // representation of the NODDI intra-axonal signal (Eq. A2 with L_\perp
23  // = 0 and L_// = d) can be written in series expansion using the
24  // spherical harmonic expansion of the Watson probability density (Eq.
25  // A4).
26  // 3) Jespersen et al., 2007, Neuroimage: it gives the analytic
27  // expression of the C function involved in the SH series expansion of
28  // NODDI intra-axonal signal (Eq. 14).
29 
30  this->UpdateKappaValues();
31 
32  double theta = this->GetOrientationTheta();
33  double phi = this->GetOrientationPhi();
34  double nuic = 1.0 - this->GetExtraAxonalFraction();
35  double dpara = this->GetAxialDiffusivity();
36 
37  Vector3DType compartmentOrientation(0.0);
38  anima::TransformSphericalToCartesianCoordinates(theta,phi,1.0,compartmentOrientation);
39  double innerProd = anima::ComputeScalarProduct(gradient, compartmentOrientation);
40 
41  // Aic and its derivatives w.r.t. params
42  m_IntraAxonalSignal = 0;
43  m_IntraAngleDerivative = 0;
44  m_IntraKappaDerivative = 0;
45  m_IntraAxialDerivative = 0;
46  double x = bValue * dpara;
47 
48  for (unsigned int i = 0;i < m_WatsonSHCoefficients.size();++i)
49  {
50  double coefVal = m_WatsonSHCoefficients[i];
51  double sqrtVal = std::sqrt((4.0 * i + 1.0) / (4.0 * M_PI));
52  double legendreVal = boost::math::legendre_p(2 * i, innerProd);
53  double kummerVal = anima::GetKummerFunctionValue(-x, i + 0.5, 2.0 * i + 1.5) * std::tgamma(i + 0.5) / std::tgamma(2.0 * i + 1.5);
54  double xPowVal = std::pow(-x, (double)i);
55  double cVal = xPowVal * kummerVal;
56 
57  // Signal
58  m_IntraAxonalSignal += coefVal * sqrtVal * legendreVal * cVal;
59 
60  // Derivatives
61  double coefDerivVal = m_WatsonSHCoefficientDerivatives[i];
62  double legendreDerivVal = boost::math::legendre_p_prime(2 * i, innerProd);
63 
64  double cDerivVal = 0.0;
65  if (m_EstimateAxialDiffusivity)
66  {
67  cDerivVal = -xPowVal * anima::GetKummerFunctionValue(-x, i + 1.5, 2.0 * i + 2.5) * std::tgamma(i + 1.5) / std::tgamma(2.0 * i + 2.5);
68  if (i > 0)
69  cDerivVal += xPowVal * i * kummerVal / x;
70  }
71 
72  m_IntraAngleDerivative += coefVal * sqrtVal * legendreDerivVal * cVal;
73  m_IntraKappaDerivative += coefDerivVal * sqrtVal * legendreVal * cVal;
74  m_IntraAxialDerivative += coefVal * sqrtVal * legendreVal * cDerivVal;
75  }
76 
77  m_IntraAxonalSignal /= 2.0;
78  m_IntraAngleDerivative /= 2.0;
79  m_IntraKappaDerivative /= 2.0;
80  m_IntraAxialDerivative /= 2.0;
81 
82  // Aec
83  double appAxialDiff = dpara * (1.0 - nuic * (1.0 - m_Tau1));
84  double appRadialDiff = dpara * (1.0 - nuic * (1.0 + m_Tau1) / 2.0);
85 
86  m_ExtraAxonalSignal = std::exp(-bValue * (appRadialDiff + (appAxialDiff - appRadialDiff) * innerProd * innerProd));
87 
88  m_CurrentBValue = bValue;
89  m_CurrentGradient = gradient;
90  m_ModifiedParameters = false;
91 }
92 
93 double NODDICompartment::GetFourierTransformedDiffusionProfile(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient)
94 {
95  double bValue = anima::GetBValueFromAcquisitionParameters(smallDelta, bigDelta, gradientStrength);
96  this->UpdateSignals(bValue, gradient);
97  double nuec = this->GetExtraAxonalFraction();
98  double signal = (1.0 - nuec) * m_IntraAxonalSignal + nuec * m_ExtraAxonalSignal;
99  return signal;
100 }
101 
102 NODDICompartment::ListType &NODDICompartment::GetSignalAttenuationJacobian(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient)
103 {
104  itkExceptionMacro("As of now, derivative is not functional for NODDI, please use bobyqa optimizer");
105 
106  double bValue = anima::GetBValueFromAcquisitionParameters(smallDelta, bigDelta, gradientStrength);
107  this->UpdateSignals(bValue, gradient);
108 
109  m_JacobianVector.resize(this->GetNumberOfParameters());
110 
111  double theta = this->GetOrientationTheta();
112  double phi = this->GetOrientationPhi();
113  double nuic = 1.0 - this->GetExtraAxonalFraction();
114  double dpara = this->GetAxialDiffusivity();
115 
116  Vector3DType compartmentOrientation(0.0);
117  anima::TransformSphericalToCartesianCoordinates(theta,phi,1.0,compartmentOrientation);
118  double innerProd = anima::ComputeScalarProduct(gradient, compartmentOrientation);
119 
120  // Base extra angle derivative
121  double extraAngleDerivative = -bValue * dpara * nuic * (3.0 * m_Tau1 - 1.0) * innerProd * m_ExtraAxonalSignal;
122 
123  //------------------------
124  // Derivative w.r.t. theta
125  //------------------------
126  double innerProdThetaDeriv = (gradient[0] * std::cos(phi) + gradient[1] * std::sin(phi)) * std::cos(theta) - gradient[2] * std::sin(theta);
127 
128  // Derivative of Aic w.r.t. theta
129  double intraThetaDerivative = m_IntraAngleDerivative * innerProdThetaDeriv;
130 
131  // Derivative of Aec w.r.t. theta
132  double extraThetaDerivative = extraAngleDerivative * innerProdThetaDeriv;
133 
134  m_JacobianVector[0] = (nuic * intraThetaDerivative + (1.0 - nuic) * extraThetaDerivative);
135 
136  //----------------------
137  // Derivative w.r.t. phi
138  //----------------------
139  double innerProdPhiDeriv = std::sin(theta) * (gradient[1] * std::cos(phi) - gradient[0] * std::sin(phi));
140 
141  // Derivative of Aic w.r.t. phi
142  double intraPhiDerivative = m_IntraAngleDerivative * innerProdPhiDeriv;
143 
144  // Derivative of Aec w.r.t. phi
145  double extraPhiDerivative = extraAngleDerivative * innerProdPhiDeriv;
146 
147  m_JacobianVector[1] = (nuic * intraPhiDerivative + (1.0 - nuic) * extraPhiDerivative);
148 
149  //------------------------
150  // Derivative w.r.t. kappa
151  //------------------------
152  unsigned int pos = 2;
153 
154  if (m_EstimateOrientationConcentration)
155  {
156  // Derivative of Aec w.r.t. kappa
157  double extraKappaDerivative = bValue * dpara * nuic * m_Tau1Deriv * (1.0 - 3.0 * innerProd * innerProd) * m_ExtraAxonalSignal / 2.0;
158 
159  m_JacobianVector[pos] = (nuic * m_IntraKappaDerivative + (1.0 - nuic) * extraKappaDerivative);
160  ++pos;
161  }
162 
163  //---------------------
164  // Derivative w.r.t. nu
165  //---------------------
166  double tmpVal = 1.0 + m_Tau1 - (3.0 * m_Tau1 - 1.0) * innerProd * innerProd;
167  if (m_EstimateExtraAxonalFraction)
168  {
169  double extraNuicDeriv = bValue * dpara * tmpVal * m_ExtraAxonalSignal / 2.0;
170 
171  m_JacobianVector[pos] = - (m_IntraAxonalSignal - m_ExtraAxonalSignal + (1.0 - nuic) * extraNuicDeriv);
172  ++pos;
173  }
174 
175  if (m_EstimateAxialDiffusivity)
176  {
177  //---------------------------
178  // Derivative w.r.t. to dpara
179  //---------------------------
180  m_JacobianVector[pos] = bValue * (nuic * m_IntraAxialDerivative - (1.0 - nuic) * m_ExtraAxonalSignal * (1.0 - nuic * tmpVal / 2.0));
181  }
182 
183  return m_JacobianVector;
184 }
185 
187 {
188  // The PDF of the intra-axonal space is not well defined (degenerated from 3D to 1D).
189  // So we use here the extra-axonal diffusion tensor to define a zero-mean 3D Gaussian
190  // diffusion profile.
191 
192  this->UpdateKappaValues();
193 
194  double intraFraction = 1.0 - this->GetExtraAxonalFraction();
195  double axialDiff = this->GetAxialDiffusivity();
196  double appAxialDiff = axialDiff * (1.0 - intraFraction * (1.0 - m_Tau1));
197  double appRadialDiff = axialDiff * (1.0 - intraFraction * (1.0 + m_Tau1) / 2.0);
198 
199  Vector3DType compartmentOrientation(0.0);
201 
202  double innerProd = anima::ComputeScalarProduct(compartmentOrientation,sample);
203 
204  double resVal = - 1.5 * std::log(2.0 * M_PI) - 0.5 * std::log(appAxialDiff) - std::log(appRadialDiff);
205  resVal -= (sample.squared_magnitude() - (1.0 - appRadialDiff / appAxialDiff) * innerProd * innerProd) / (2.0 * appRadialDiff);
206 
207  return resVal;
208 }
209 
211 {
212  if (num != this->GetOrientationTheta())
213  {
214  m_ModifiedParameters = true;
216  }
217 }
218 
220 {
221  if (num != this->GetOrientationPhi())
222  {
223  m_ModifiedParameters = true;
225  }
226 }
227 
229 {
230  if (num != this->GetOrientationConcentration())
231  {
232  m_ModifiedParameters = true;
233  m_ModifiedConcentration = true;
235  }
236 }
237 
239 {
240  if (num != this->GetExtraAxonalFraction())
241  {
242  m_ModifiedParameters = true;
244  }
245 }
246 
248 {
249  if (num != this->GetAxialDiffusivity())
250  {
251  m_ModifiedParameters = true;
253  }
254 }
255 
257 {
258  if (params.size() != this->GetNumberOfParameters())
259  return;
260 
261  this->SetOrientationTheta(params[0]);
262  this->SetOrientationPhi(params[1]);
263 
264  unsigned int pos = 2;
265  if (m_EstimateOrientationConcentration)
266  {
267  this->SetOrientationConcentration(params[pos]);
268  ++pos;
269  }
270 
271  if (m_EstimateExtraAxonalFraction)
272  {
273  this->SetExtraAxonalFraction(params[pos]);
274  ++pos;
275  }
276 
277  if (m_EstimateAxialDiffusivity)
278  this->SetAxialDiffusivity(params[pos]);
279 }
280 
282 {
284 
287 
288  unsigned int pos = 2;
289  if (m_EstimateOrientationConcentration)
290  {
292  ++pos;
293  }
294 
295  if (m_EstimateExtraAxonalFraction)
296  {
298  ++pos;
299  }
300 
301  if (m_EstimateAxialDiffusivity)
303 
304  return m_ParametersVector;
305 }
306 
308 {
310 
312 
313  if (m_EstimateAxialDiffusivity)
315 
317 }
318 
320 {
322 
325 
326  unsigned int pos = 2;
327 
328  if (m_EstimateOrientationConcentration)
329  {
331  ++pos;
332  }
333 
334  if (m_EstimateExtraAxonalFraction)
335  {
337  ++pos;
338  }
339 
340  if (m_EstimateAxialDiffusivity)
342 
344 }
345 
347 {
348  if (m_EstimateOrientationConcentration == arg)
349  return;
350 
351  m_EstimateOrientationConcentration = arg;
352  m_ChangedConstraints = true;
353 }
354 
356 {
357  if (m_EstimateAxialDiffusivity == arg)
358  return;
359 
360  m_EstimateAxialDiffusivity = arg;
361  m_ChangedConstraints = true;
362 }
363 
365 {
366  if (m_EstimateExtraAxonalFraction == arg)
367  return;
368 
369  m_EstimateExtraAxonalFraction = arg;
370  m_ChangedConstraints = true;
371 }
372 
374 {
375  if (compartmentVector.GetSize() != this->GetCompartmentSize())
376  itkExceptionMacro("The input vector size does not match the size of the compartment");
377 
378  Vector3DType compartmentOrientation, sphDir;
379 
380  for (unsigned int i = 0;i < m_SpaceDimension;++i)
381  compartmentOrientation[i] = compartmentVector[i];
382 
383  anima::TransformCartesianToSphericalCoordinates(compartmentOrientation,sphDir);
384  this->SetOrientationTheta(sphDir[0]);
385  this->SetOrientationPhi(sphDir[1]);
386 
387  unsigned int currentPos = m_SpaceDimension;
388 
389  // Display OD map (Zhang et al., 2012, Neuroimage, Eq. 9) to stay consistent
390  // with the authors' original work but Would be better to show (3 tau1 - 1) / 2.
391  double odi = compartmentVector[currentPos];
392  this->SetOrientationConcentration(1.0 / std::tan(M_PI / 2.0 * odi));
393  ++currentPos;
394 
395  // Display intra-axonal fraction map to stay consistent with NODDI
396  // parametrization as in Zhang et al., 2012, Neuroimage.
397  this->SetExtraAxonalFraction(1.0 - compartmentVector[currentPos]);
398  ++currentPos;
399 
400  this->SetAxialDiffusivity(compartmentVector[currentPos]);
401 
402  m_ModifiedParameters = false;
403  m_ModifiedConcentration = false;
404 }
405 
407 {
408  return 6;
409 }
410 
412 {
413  if (!m_ChangedConstraints)
414  return m_NumberOfParameters;
415 
416  m_NumberOfParameters = 2;
417 
418  if (m_EstimateOrientationConcentration)
419  ++m_NumberOfParameters;
420 
421  if (m_EstimateAxialDiffusivity)
422  ++m_NumberOfParameters;
423 
424  if (m_EstimateExtraAxonalFraction)
425  ++m_NumberOfParameters;
426 
427  m_ChangedConstraints = false;
428  return m_NumberOfParameters;
429 }
430 
432 {
433  if (m_CompartmentVector.GetSize() != this->GetCompartmentSize())
434  m_CompartmentVector.SetSize(this->GetCompartmentSize());
435 
436  Vector3DType compartmentOrientation(0.0);
438 
439  for (unsigned int i = 0;i < m_SpaceDimension;++i)
440  m_CompartmentVector[i] = compartmentOrientation[i];
441 
442  unsigned int currentPos = m_SpaceDimension;
443 
444  // Display OD map (Zhang et al., 2012, Neuroimage, Eq. 9) to stay consistent
445  // with the authors' original work but Would be better to show (3 tau1 - 1) / 2.
446  m_CompartmentVector[currentPos] = 2.0 / M_PI * std::atan(1.0 / this->GetOrientationConcentration());
447  ++currentPos;
448 
449  // Display intra-axonal fraction map to stay consistent with NODDI
450  // parametrization as in Zhang et al., 2012, Neuroimage.
451  m_CompartmentVector[currentPos] = 1.0 - this->GetExtraAxonalFraction();
452  ++currentPos;
453 
454  m_CompartmentVector[currentPos] = this->GetAxialDiffusivity();
455 
456  return m_CompartmentVector;
457 }
458 
460 {
461  // Get an apparent diffusion tensor based on the extra-axonal space.
462  this->UpdateKappaValues();
463 
464  double intraFraction = 1.0 - this->GetExtraAxonalFraction();
465  double axialDiff = this->GetAxialDiffusivity();
466  double appAxialDiff = axialDiff * (1.0 - intraFraction * (1.0 - m_Tau1));
467  double appRadialDiff = axialDiff * (1.0 - intraFraction * (1.0 + m_Tau1) / 2.0);
468 
469  m_DiffusionTensor.Fill(0.0);
470 
471  for (unsigned int i = 0;i < m_SpaceDimension;++i)
472  m_DiffusionTensor(i,i) = appRadialDiff;
473 
474  Vector3DType compartmentOrientation(0.0);
476 
477  for (unsigned int i = 0;i < m_SpaceDimension;++i)
478  for (unsigned int j = i;j < m_SpaceDimension;++j)
479  {
480  m_DiffusionTensor(i,j) += compartmentOrientation[i] * compartmentOrientation[j] * (appAxialDiff - appRadialDiff);
481  if (i != j)
483  }
484 
485  return m_DiffusionTensor;
486 }
487 
489 {
490  if (!m_ModifiedConcentration)
491  return;
492 
493  double kappa = this->GetOrientationConcentration();
494  double dawsonValue = anima::EvaluateDawsonIntegral(std::sqrt(kappa), true);
495  m_Tau1 = (1.0 / dawsonValue - 1.0) / (2.0 * kappa);
496  m_Tau1Deriv = (1.0 - (1.0 - dawsonValue * (2.0 * kappa - 1.0)) / (2.0 * dawsonValue * dawsonValue)) / (2.0 * kappa * kappa);
497  anima::GetStandardWatsonSHCoefficients(kappa,m_WatsonSHCoefficients,m_WatsonSHCoefficientDerivatives);
498 
499  m_ModifiedConcentration = false;
500 }
501 
503 {
504  double intraFraction = 1.0 - this->GetExtraAxonalFraction();
505  double axialDiff = this->GetAxialDiffusivity();
506  double l1 = axialDiff * (1.0 - intraFraction * (1.0 - m_Tau1));
507  double l2 = axialDiff * (1.0 - intraFraction * (1.0 + m_Tau1) / 2.0);
508  double l3 = l2;
509 
510  double numFA = std::sqrt ((l1 - l2) * (l1 - l2) + (l2 - l3) * (l2 - l3) + (l3 - l1) * (l3 - l1));
511  double denomFA = std::sqrt (l1 * l1 + l2 * l2 + l3 * l3);
512 
513  double fa = 0;
514  if (denomFA != 0)
515  fa = std::sqrt(0.5) * (numFA / denomFA);
516 
517  return fa;
518 }
519 
520 } //end namespace anima
const double MCMZeroLowerBound
Default zero lower bound (in case we want something else than zero one day)
void SetOrientationPhi(double num) ITK_OVERRIDE
std::vector< double > ListType
void TransformSphericalToCartesianCoordinates(const VectorType &v, VectorType &resVec)
virtual void SetAxialDiffusivity(double num)
ListType m_ParametersLowerBoundsVector
Vector holding current parameters lower bounds.
void SetOrientationConcentration(double num) ITK_OVERRIDE
void UpdateKappaValues()
Update quantities that depend on kappa.
const double MCMAzimuthAngleUpperBound
Azimuth angle upper bound.
void SetEstimateOrientationConcentration(bool arg)
void SetCompartmentVector(ModelOutputVectorType &compartmentVector) ITK_OVERRIDE
Set compartment overall description vector, for setting automatically the individual parameters when ...
virtual double GetOrientationPhi()
ModelOutputVectorType & GetCompartmentVector() ITK_OVERRIDE
Get compartment overall description vector, mainly for writing, should be self-contained.
unsigned int GetCompartmentSize() ITK_OVERRIDE
Number of parameters to describe the model, these parameters will be self-contained, i.e. include fixed parameters for example.
const double MCMFractionUpperBound
Fraction upper bound (intra/extra axonal)
void SetEstimateAxialDiffusivity(bool arg)
const Matrix3DType & GetDiffusionTensor() ITK_OVERRIDE
Get compartment as a 3D tensor (default behavior: throw exception if not supported by the compartment...
virtual double GetFourierTransformedDiffusionProfile(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient) ITK_OVERRIDE
virtual double GetAxialDiffusivity()
double ComputeNorm(const VectorType &v)
ListType m_ParametersVector
Vector holding current parameters vector.
double GetBValueFromAcquisitionParameters(double smallDelta, double bigDelta, double gradientStrength)
Given gyromagnetic ratio in rad/s/T, gradient strength in T/mm and deltas in s, computes b-value in s...
virtual double GetOrientationTheta()
const double MCMPolarAngleUpperBound
Polar angle upper bound (used in tensor for now)
virtual void SetOrientationConcentration(double num)
double GetApparentFractionalAnisotropy() ITK_OVERRIDE
Superclass::Vector3DType Vector3DType
virtual void SetExtraAxonalFraction(double num)
void SetAxialDiffusivity(double num) ITK_OVERRIDE
virtual double GetExtraAxonalFraction()
virtual ListType & GetParameterLowerBounds() ITK_OVERRIDE
virtual double GetLogDiffusionProfile(const Vector3DType &sample) ITK_OVERRIDE
virtual void SetOrientationTheta(double num)
ListType m_ParametersUpperBoundsVector
Vector holding current parameters upper bounds.
Superclass::ModelOutputVectorType ModelOutputVectorType
ListType m_JacobianVector
Vector holding current jacobian value.
void GetStandardWatsonSHCoefficients(const ScalarType k, std::vector< ScalarType > &coefficients, std::vector< ScalarType > &derivatives)
const double MCMConcentrationUpperBound
Concentration upper bound (used in NODDI and DDI)
const double MCMDiffusivityUpperBound
Diffusivity upper bound.
virtual void SetParametersFromVector(const ListType &params) ITK_OVERRIDE
Various methods for optimization parameters setting and getting.
double ComputeScalarProduct(const VectorType &v1, const VectorType &v2, const unsigned int NDimension)
virtual void SetOrientationPhi(double num)
virtual ListType & GetParameterUpperBounds() ITK_OVERRIDE
const double MCMDiffusivityLowerBound
Diffusivity lower bound for estimation.
virtual ListType & GetParametersAsVector() ITK_OVERRIDE
Superclass::Matrix3DType Matrix3DType
double GetKummerFunctionValue(const double &x, const double &a, const double &b, const unsigned int maxIter, const double tol)
Computes the confluent hypergeometric function 1F1 also known as the Kummer function M...
Matrix3DType m_DiffusionTensor
Matrix to hold working value of diffusion tensor approximation to the model.
unsigned int GetNumberOfParameters() ITK_OVERRIDE
Number of optimized parameters: smaller than total number of parameters.
virtual ListType & GetSignalAttenuationJacobian(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient) ITK_OVERRIDE
void TransformCartesianToSphericalCoordinates(const VectorType &v, VectorType &resVec)
ModelOutputVectorType m_CompartmentVector
Vector to hold working value of compartment vector.
void UpdateSignals(double bValue, const Vector3DType &gradient)
Compute intra- and extra-axonal signals and useful signals for derivatives.
void SetExtraAxonalFraction(double num) ITK_OVERRIDE
static const unsigned int m_SpaceDimension
void SetOrientationTheta(double num) ITK_OVERRIDE
virtual double GetOrientationConcentration()
void SetEstimateExtraAxonalFraction(bool arg)
double EvaluateDawsonIntegral(const double x, const bool scaled)