ANIMA  4.0
animaODFProbabilisticTractographyImageFilter.cxx
Go to the documentation of this file.
2 #include <cmath>
3 #include <random>
4 
6 #include <animaNLOPTOptimizers.h>
7 
11 #include <animaVMFDistribution.h>
13 
14 namespace anima
15 {
16 
19 {
20  m_ODFSHOrder = 4;
21  m_GFAThreshold = 0.1;
22  m_MinimalDiffusionProbability = 0;
23 
24  m_CurvatureScale = 6.0;
25 
26  m_ODFSHBasis = NULL;
27 
28  this->SetModelDimension(15);
29 }
30 
32 {
33  if (m_ODFSHBasis)
34  delete m_ODFSHBasis;
35 }
36 
38 {
39  // Call base preparation
41 
42  m_ODFSHOrder = std::round(-1.5 + 0.5 * std::sqrt(8 * this->GetInputModelImage()->GetNumberOfComponentsPerPixel() + 1));
43  this->SetModelDimension((m_ODFSHOrder + 1)*(m_ODFSHOrder + 2)/2);
44 
45  // Initialize estimation matrices for Aganj et al based estimation
46  if (m_ODFSHBasis)
47  delete m_ODFSHBasis;
48 
49  m_ODFSHBasis = new anima::ODFSphericalHarmonicBasis(m_ODFSHOrder);
50 }
51 
54  Vector3DType &sampling_direction, double &log_prior,
55  double &log_proposal, std::mt19937 &random_generator,
56  unsigned int threadId)
57 {
58  Vector3DType resVec(0.0);
59  bool is2d = (this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] == 1);
60 
61  DirectionVectorType maximaODF;
62  unsigned int numDirs = this->FindODFMaxima(modelValue,maximaODF,m_MinimalDiffusionProbability,is2d);
63  ListType mixtureWeights(numDirs,0);
64  ListType kappaValues(numDirs,0);
65 
66  double chosenKappa = 0;
67 
68  Vector3DType sphDirection;
69 
70  double sumWeights = 0;
71 
72  for (unsigned int i = 0;i < numDirs;++i)
73  {
74  if (anima::ComputeScalarProduct(oldDirection, maximaODF[i]) < 0)
75  maximaODF[i] *= -1;
76 
77  anima::TransformCartesianToSphericalCoordinates(maximaODF[i],sphDirection);
78  mixtureWeights[i] = m_ODFSHBasis->getValueAtPosition(modelValue,sphDirection[0],sphDirection[1]);
79 
80  // 0.5 is for Watson kappa
81  kappaValues[i] = 0.5 * m_CurvatureScale * m_ODFSHBasis->getCurvatureAtPosition(modelValue,sphDirection[0],sphDirection[1]);
82 
83  if ((std::isnan(kappaValues[i]))||(kappaValues[i] <= 0)||(kappaValues[i] >= 1000))
84  mixtureWeights[i] = 0;
85 
86  sumWeights += mixtureWeights[i];
87  }
88 
89  if (sumWeights == 0)
90  {
91  numDirs = 0;
92  sampling_direction = oldDirection;
93  chosenKappa = this->GetKappaOfPriorDistribution();
94  }
95  else
96  {
97  for (unsigned int i = 0;i < numDirs;++i)
98  mixtureWeights[i] /= sumWeights;
99 
100  std::discrete_distribution<> dist(mixtureWeights.begin(),mixtureWeights.end());
101  unsigned int chosenDirection = dist(random_generator);
102 
103  sampling_direction = maximaODF[chosenDirection];
104  chosenKappa = kappaValues[chosenDirection];
105  }
106 
107  // if (chosenKappa > 700)
108  // anima::SampleFromVMFDistributionNumericallyStable(chosenKappa,sampling_direction,resVec,random_generator);
109  // else
110  // anima::SampleFromVMFDistribution(chosenKappa,sampling_direction,resVec,random_generator);
111 
112  anima::SampleFromWatsonDistribution(chosenKappa,sampling_direction,resVec,3,random_generator);
113 
114  if (is2d)
115  {
116  resVec[InputModelImageType::ImageDimension - 1] = 0;
117  resVec.Normalize();
118  }
119 
120  if (numDirs > 0)
121  {
122  // log_prior = anima::safe_log( anima::ComputeVMFPdf(resVec, oldDirection, this->GetKappaOfPriorDistribution()));
123  log_prior = anima::safe_log( anima::EvaluateWatsonPDF(resVec, oldDirection, this->GetKappaOfPriorDistribution()));
124 
125  log_proposal = 0;
126  for (unsigned int i = 0;i < numDirs;++i)
127  {
128  // log_proposal += mixtureWeights[i] * anima::ComputeVMFPdf(resVec, maximaODF[i], kappaValues[i]);
129  log_proposal += mixtureWeights[i] * anima::EvaluateWatsonPDF(resVec, maximaODF[i], kappaValues[i]);
130  }
131 
132  log_proposal = anima::safe_log(log_proposal);
133  }
134 
135  if (anima::ComputeScalarProduct(oldDirection, resVec) < 0)
136  resVec *= -1;
137 
138  return resVec;
139 }
140 
142  unsigned int threadId)
143 {
144  Vector3DType resVec(0.0), tmpVec;
145  bool is2d = (this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] == 1);
146 
147  DirectionVectorType maximaODF;
148  unsigned int numDirs = this->FindODFMaxima(modelValue,maximaODF,m_MinimalDiffusionProbability,is2d);
149  if (numDirs == 0)
150  return colinearDir;
151 
152  switch (this->GetInitialDirectionMode())
153  {
154  case Colinear:
155  {
156  double maxVal = 0;
157  for (unsigned int i = 0;i < numDirs;++i)
158  {
159  for (unsigned int j = 0;j < InputModelImageType::ImageDimension;++j)
160  tmpVec[j] = maximaODF[i][j];
161 
162  double tmpVal = anima::ComputeScalarProduct(colinearDir, tmpVec);
163 
164  if (tmpVal < 0)
165  {
166  tmpVec *= -1;
167  tmpVal *= -1;
168  }
169 
170  if (tmpVal > maxVal)
171  {
172  resVec = tmpVec;
173  maxVal = tmpVal;
174  }
175  }
176 
177  break;
178  }
179 
180  case Weight:
181  default:
182  {
183  resVec = maximaODF[0];
184  if (anima::ComputeScalarProduct(colinearDir,resVec) < 0)
185  resVec *= -1;
186  break;
187  }
188  }
189 
190  if (is2d)
191  {
192  resVec[2] = 0;
193  resVec.Normalize();
194  }
195 
196  return resVec;
197 }
198 
199 bool ODFProbabilisticTractographyImageFilter::CheckModelProperties(double estimatedB0Value, double estimatedNoiseValue, VectorType &modelValue, unsigned int threadId)
200 {
201  if (estimatedB0Value < 50.0)
202  return false;
203 
204  bool isModelNull = true;
205  for (unsigned int j = 0;j < this->GetModelDimension();++j)
206  {
207  if (modelValue[j] != 0)
208  {
209  isModelNull = false;
210  break;
211  }
212  }
213 
214  if (isModelNull)
215  return false;
216 
217  double fractionalAnisotropy = this->GetGeneralizedFractionalAnisotropy(modelValue);
218  if (fractionalAnisotropy < m_GFAThreshold)
219  return false;
220 
221  return true;
222 }
223 
224 double ODFProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0Value, double noiseValue, Vector3DType &newDirection, VectorType &modelValue,
225  double &log_prior, double &log_proposal, unsigned int threadId)
226 {
227  double logLikelihood = 0.0;
228 
229  bool is2d = (this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] == 1);
230 
231  DirectionVectorType maximaODF;
232  unsigned int numDirs = this->FindODFMaxima(modelValue,maximaODF,m_MinimalDiffusionProbability,is2d);
233 
234  double concentrationParameter = b0Value / std::sqrt(noiseValue);
235 
236  for (unsigned int i = 0;i < numDirs;++i)
237  {
238  double tmpVal = std::log(anima::EvaluateWatsonPDF(maximaODF[i], newDirection, concentrationParameter));
239  if ((tmpVal > logLikelihood)||(i == 0))
240  logLikelihood = tmpVal;
241  }
242 
243  double resVal = logLikelihood + log_prior - log_proposal;
244  return resVal;
245 }
246 
248 unsigned int ODFProbabilisticTractographyImageFilter::FindODFMaxima(const VectorType &modelValue, DirectionVectorType &maxima, double minVal, bool is2d)
249 {
250  ListType modelValueList(modelValue.GetSize());
251  for (unsigned int i = 0;i < modelValue.GetSize();++i)
252  modelValueList[i] = modelValue[i];
253 
254  std::vector < std::vector <double> > initDirs(15);
255 
256  // Find the max of the ODF v, set max to the value...
257  struct XYZ array[] = {
258  {-1,0,0},
259  {0,-1,0},
260  {0,0,-1},
261  {0.1789,0.1113,-0.9776},
262  {0.0635,-0.3767,-0.9242},
263  {-0.7108,-0.0516,-0.7015},
264  {-0.6191,0.4385,-0.6515},
265  {-0.2424,-0.7843,-0.571},
266  {0.2589,0.618,-0.7423},
267  {0.8169,-0.1697,-0.5513},
268  {0.8438,-0.5261,-0.106},
269  {0.2626,-0.9548,-0.1389},
270  {-1e-04,-0.9689,0.2476},
271  {-0.7453,-0.6663,0.0242},
272  {-0.9726,-0.2317,0.0209}
273 };
274 
275  for (unsigned int i = 0;i < initDirs.size();++i)
276  {
277  initDirs[i].resize(3);
278  initDirs[i][0] = array[i].x;
279  initDirs[i][1] = array[i].y;
280  initDirs[i][2] = array[i].z;
281  }
282 
283  typedef anima::ODFMaximaCostFunction CostFunctionType;
284  typedef anima::NLOPTOptimizers OptimizerType;
285 
286  OptimizerType::Pointer opt = OptimizerType::New();
287  opt->SetAlgorithm(NLOPT_LN_BOBYQA);
288  opt->SetXTolRel(1.0e-4);
289  opt->SetFTolRel(1.0e-6);
290  opt->SetMaxEval(200);
291  opt->SetVectorStorageSize(2000);
292 
293  typedef std::map < double, Vector3DType > MapType;
294  MapType dmap;
295 
296  std::vector <double> angle;
297  OptimizerType::ParametersType tmpValue(2);
298  Vector3DType tmpValueVector(1.0);
299  Vector3DType cartesianVector(1.0);
300 
301  itk::Array<double> lowerBounds(2);
302  itk::Array<double> upperBounds(2);
303 
304  lowerBounds.fill(0.0);
305  upperBounds[0] = M_PI;
306  upperBounds[1] = 2.0 * M_PI;
307 
308  opt->SetLowerBoundParameters(lowerBounds);
309  opt->SetUpperBoundParameters(upperBounds);
310 
311  for (unsigned int i = 0; i < initDirs.size();++i)
312  {
314  tmpValue[0] = angle[0];
315  tmpValue[1] = angle[1];
316 
317  CostFunctionType::Pointer cost = CostFunctionType::New();
318  cost->SetODFSHOrder(m_ODFSHOrder);
319  cost->SetBasisParameters(modelValueList);
320 
321  opt->SetCostFunction(cost);
322  opt->SetMaximize(true);
323 
324  opt->SetInitialPosition(tmpValue);
325  opt->StartOptimization();
326 
327  tmpValue = opt->GetCurrentPosition();
328  tmpValueVector[0] = tmpValue[0];
329  tmpValueVector[1] = tmpValue[1];
330 
331  // Some check needed here to see if we really found a maximum
332  anima::TransformSphericalToCartesianCoordinates(tmpValueVector,cartesianVector);
333  dmap[opt->GetValue()] = cartesianVector;
334  }
335 
336  // Find true maximas
337  std::vector <bool> usefulMaxima(15,true);
338  unsigned int pos = 0;
339  for (MapType::reverse_iterator it = dmap.rbegin();it != dmap.rend();++it)
340  {
341  unsigned int posIn = pos+1;
342  MapType::reverse_iterator itin = it;
343  ++itin;
344  for (;itin != dmap.rend();++itin)
345  {
346  if (anima::ComputeOrientationAngle((*it).second,(*itin).second) < 15)
347  usefulMaxima[posIn] = false;
348 
349  ++posIn;
350  }
351 
352  ++pos;
353  }
354 
355  pos = 0;
356  maxima.clear();
357  for (MapType::reverse_iterator it = dmap.rbegin();it != dmap.rend();++it)
358  {
359  if ((usefulMaxima[pos])&&((*it).first > minVal))
360  maxima.push_back((*it).second);
361 
362  ++pos;
363  }
364 
365  if (is2d)
366  {
367  std::vector <bool> outOfPlaneDirs(maxima.size(),false);
368  for (unsigned int i = 0;i < maxima.size();++i)
369  {
370  maxima[i][2] = 0;
371 
372  double norm = 0;
373  for (unsigned int j = 0;j < InputModelImageType::ImageDimension - 1;++j)
374  norm += maxima[i][j] * maxima[i][j];
375  norm = sqrt(norm);
376 
377  outOfPlaneDirs[i] = (std::abs(norm) < 0.5);
378 
379  if (!outOfPlaneDirs[i])
380  {
381  for (unsigned int j = 0;j < InputModelImageType::ImageDimension - 1;++j)
382  maxima[i][j] /= norm;
383  }
384  }
385 
386  DirectionVectorType outMaxima;
387 
388  for (unsigned int i = 0;i < maxima.size();++i)
389  {
390  if (!outOfPlaneDirs[i])
391  outMaxima.push_back(maxima[i]);
392  }
393 
394  maxima = outMaxima;
395  }
396 
397  return maxima.size();
398 }
399 
401  VectorType &modelValue)
402 {
403  modelValue.SetSize(this->GetModelDimension());
404  modelValue.Fill(0.0);
405 
406  if (modelInterpolator->IsInsideBuffer(index))
407  modelValue = modelInterpolator->EvaluateAtContinuousIndex(index);
408 }
409 
411 {
412  double sumSquares = 0;
413  for (unsigned int i = 0;i < this->GetModelDimension();++i)
414  sumSquares += modelValue[i]*modelValue[i];
415 
416  return std::sqrt(1.0 - modelValue[0]*modelValue[0]/sumSquares);
417 }
418 
419 } // end of namespace anima
double getCurvatureAtPosition(const T &coefficients, double theta, double phi)
void TransformSphericalToCartesianCoordinates(const VectorType &v, VectorType &resVec)
virtual bool CheckModelProperties(double estimatedB0Value, double estimatedNoiseValue, VectorType &modelValue, unsigned int threadId) ITK_OVERRIDE
Implements an ITK wrapper for the NLOPT library.
unsigned int FindODFMaxima(const VectorType &modelValue, DirectionVectorType &maxima, double minVal, bool is2d)
Returns ODF maxima ordered by probability of diffusion, only those with probability superior to minVa...
double EvaluateWatsonPDF(const VectorType &v, const VectorType &meanAxis, const ScalarType &kappa)
virtual Vector3DType ProposeNewDirection(Vector3DType &oldDirection, VectorType &modelValue, Vector3DType &sampling_direction, double &log_prior, double &log_proposal, std::mt19937 &random_generator, unsigned int threadId) ITK_OVERRIDE
double ComputeOrientationAngle(const VectorType &v1, const VectorType &v2)
virtual void ComputeModelValue(InterpolatorPointer &modelInterpolator, ContinuousIndexType &index, VectorType &modelValue) ITK_OVERRIDE
double ComputeScalarProduct(const VectorType &v1, const VectorType &v2, const unsigned int NDimension)
double safe_log(const ScalarType x)
virtual void PrepareTractography()
Generate seed points (can be re-implemented but this one has to be called)
virtual Vector3DType InitializeFirstIterationFromModel(Vector3DType &colinearDir, VectorType &modelValue, unsigned int threadId) ITK_OVERRIDE
void TransformCartesianToSphericalCoordinates(const VectorType &v, VectorType &resVec)
void SampleFromWatsonDistribution(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, unsigned int DataDimension, std::mt19937 &generator)
virtual double ComputeLogWeightUpdate(double b0Value, double noiseValue, Vector3DType &newDirection, VectorType &modelValue, double &log_prior, double &log_proposal, unsigned int threadId) ITK_OVERRIDE
double getValueAtPosition(const T &coefficients, double theta, double phi)