Shapeworks Studio  2.1
Shape analysis software suite
itkPSMEntropyModelFilter.hxx
1 /*=========================================================================
2  *
3  * Copyright Insight Software Consortium
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0.txt
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  *=========================================================================*/
18 #ifndef __itkPSMEntropyModelFilter_hxx
19 #define __itkPSMEntropyModelFilter_hxx
20 #include "itkPSMEntropyModelFilter.h"
21 
22 #include "itkZeroCrossingImageFilter.h"
23 #include "itkImageRegionConstIteratorWithIndex.h"
24 
25 namespace itk
26 {
27 
28 template <class TImage, class TShapeMatrix>
29 PSMEntropyModelFilter<TImage, TShapeMatrix>::PSMEntropyModelFilter()
30 {
31  // Default parameters for the multiscale functionality
32  m_CurrentScale = 0;
33  this->SetNumberOfScales(1);
34 
35  m_Initializing = false;
36 
37  m_ParticleSystem = PSMParticleSystem<Dimension>::New();
38 
39  // Here we create the various necessary components for the Shape
40  // Model optimization. First we construct the Shape Matrix.
41  m_ShapeMatrix = ShapeMatrixType::New();
42  m_ShapeMatrix->SetDomainsPerShape(1);
43 
44  // Now we create the Particle Entropy Function. This is the entropy
45  // estimation of particle position distributions on shape surfaces.
46  m_ParticleEntropyFunction
47  = PSMParticleEntropyFunction<typename ImageType::PixelType, Dimension>::New();
48 
49  // Now initialize the Shape Space entropy function and point it to
50  // the Shape Matrix attribute. The Shape Space entropy function
51  // computes entropy of the shape samples in n-dimensional shape
52  // space.
53  m_ShapeEntropyFunction = PSMShapeEntropyFunction<Dimension>::New();
54  m_ShapeEntropyFunction->SetShapeMatrix(m_ShapeMatrix);
55 
56  // Now allocate an optimizer and set iteration callback.
57  m_Optimizer = OptimizerType::New();
58  m_IterateCallback = itk::MemberCommand<PSMEntropyModelFilter<TImage, TShapeMatrix> >::New();
59  m_IterateCallback->SetCallbackFunction(this, &PSMEntropyModelFilter<TImage, TShapeMatrix>::OptimizerIterateCallback);
60  m_Optimizer->AddObserver(itk::IterationEvent(), m_IterateCallback);
61 
62  // Finally, we need a cost function that combines the
63  // ParticleEntropy function with the ShapeEntropy function.
64  m_CostFunction = PSMTwoCostFunction<Dimension>::New();
65  m_CostFunction->SetFunctionA(m_ParticleEntropyFunction);
66  m_CostFunction->SetFunctionB(m_ShapeEntropyFunction);
67 
68  m_Initialized = false;
69 
70  // Register the Shape Matrix as an attribute of the particle system.
71  // This ensures that the Shape Matrix will receive any particle
72  // AddPosition/RemovePosition/UpdatePosition events, so that it can
73  // update its data accordingly.
74  m_ParticleSystem->RegisterAttribute(m_ShapeMatrix);
75 }
76 
77 
78 template<class TImage, class TShapeMatrix>
79 void
81 {
82  // Set up the various data caches that the optimization functions will use.
83  // Sigma cache is caching a parameter for the local particle entropy
84  // computation that is updated for each particle.
86  m_ParticleSystem->RegisterAttribute(m_SigmaCache);
87  m_ParticleEntropyFunction->SetSpatialSigmaCache(m_SigmaCache);
88 
89  // m_MeanCurvatureCache = PSMMeanCurvatureAttribute<typename ImageType::PixelType, Dimension>::New();
90  // m_ParticleSystem->RegisterAttribute(m_MeanCurvatureCache);
91 }
92 
93 template <class TImage, class TShapeMatrix>
94 void
96 {
97  m_WorkingImages.resize(this->GetNumberOfInputs());
98  for (unsigned int i = 0; i < this->GetNumberOfInputs(); i++)
99  {
100  m_WorkingImages[i] = const_cast<TImage *>(this->GetInput(i));
101  }
102 }
103 
104 template <class TImage, class TShapeMatrix>
105 void
108 {
109  // Allocate all the necessary domains and neighborhoods. This must be done
110  // *after* registering the attributes to the particle system since some of
111  // them respond to AddDomain.
112  for (unsigned int i = 0; i < this->GetNumberOfInputs(); i++)
113  {
114  m_DomainList.push_back( PSMImplicitSurfaceDomain<typename
115  ImageType::PixelType, Dimension>::New() );
116  m_NeighborhoodList.push_back( PSMSurfaceNeighborhood<ImageType>::New() );
117  m_DomainList[i]->SetSigma(m_WorkingImages[i]->GetSpacing()[0] * 2.0);
118  m_DomainList[i]->SetImage(m_WorkingImages[i]);
119 
120  if (m_CuttingPlanes.size() > i)
121  {
122  m_DomainList[i]->SetCuttingPlane(m_CuttingPlanes[i].x,
123  m_CuttingPlanes[i].y,
124  m_CuttingPlanes[i].z);
125  }
126 
127  m_ParticleSystem->AddDomain(m_DomainList[i]);
128  m_ParticleSystem->SetNeighborhood(i, m_NeighborhoodList[i]);
129  }
130 }
131 
132 
133 template <class TImage, class TShapeMatrix>
134 void
136 ::AddCuttingPlane(const vnl_vector_fixed<double,3> &x,
137  const vnl_vector_fixed<double,3> &y,
138  const vnl_vector_fixed<double,3> &z,
139  unsigned int domain)
140 {
141  if (m_CuttingPlanes.size() < domain+1)
142  {
143  m_CuttingPlanes.resize(domain+1);
144  }
146  p.x = x;
147  p.y = y;
148  p.z = z;
149  p.valid = true;
150  m_CuttingPlanes[domain] = p;
151 }
152 
153 template <class TImage, class TShapeMatrix>
154 void
156 ::SetDomainName(const std::string &s, unsigned int i)
157 {
158  if (m_DomainListNames.size() < (i+1))
159  {
160  m_DomainListNames.resize(i+1);
161  }
162  m_DomainListNames[i] = s;
163 }
164 
165 template <class TImage, class TShapeMatrix>
166 unsigned int
168 ::GetDomainIndexByName(const std::string &name)
169 {
170  // This is slow, but is not anticipated to be used very extensively.
171  // Better to have simple code than overengineer something not
172  // performance-critical.
173 
174  for (unsigned int i = 0; i < m_DomainListNames.size(); i++)
175  {
176  if (m_DomainListNames[i] == name)
177  {
178  return i;
179  }
180  }
181 
182  itkExceptionMacro("The domain name " + name + " was not found. ");
183  return 0; // algorithm will never reach this point
184 }
185 
186 template <class TImage, class TShapeMatrix>
187 void
189 {
190  // If the user has specified correspondence points on the input,
191  // then make sure the number of lists of correspondence points
192  // matches the number of inputs (distance transforms). Everything
193  // should match this->GetNumberOfInputs().
194  if (m_InputCorrespondencePoints.size() > 0)
195  {
196  if (this->GetNumberOfInputs() != m_InputCorrespondencePoints.size() )
197  {
198  itkExceptionMacro("The number of inputs does not match the number of correspondence point lists.");
199  }
200  else
201  {
202  for (unsigned int i = 0; i < m_InputCorrespondencePoints.size(); i++)
203  {
204  this->GetParticleSystem()->AddPositionList(m_InputCorrespondencePoints[i],i);
205  }
206  }
207  }
208  else // No input correspondences are specified, so add a point to each surface.
209  {
210  this->CreateSingleCorrespondence();
211  }
212 
213  // Push position information out to all observers (necessary to
214  // correctly fill out the shape matrix)
215  this->GetParticleSystem()->SynchronizePositions();
216 }
217 
218 template <class TImage, class TShapeMatrix>
219 void
221 {
222  // First, undo the default InPlaceImageFilter functionality that will
223  // release our inputs.
224  this->SetInPlace(false);
225 
226  // If this is the first call to GenerateData, then we need to allocate some structures
227  if (this->GetInitialized() == false)
228  {
229  this->AllocateWorkingImages();
230  this->AllocateDataCaches();
231  this->GetOptimizer()->SetCostFunction(m_CostFunction);
232 
233  this->AllocateDomainsAndNeighborhoods();
234 
235  // Point the optimizer to the particle system.
236  this->GetOptimizer()->SetParticleSystem(this->GetParticleSystem());
237  // this->ReadTransforms();
238  this->InitializeCorrespondences();
239  this->InitializeOptimizationFunctions();
240 
241  this->SetInitialized(true);
242  }
243 
244  // This filter can be run in "initialization mode", which causes all
245  // data structures to allocate and initialize, but does not run the
246  // actual optimization.
247  if (this->GetInitializing() == true) return;
248 
249  // Multiscale optimization. This can be stopped and restarted by
250  // calling GenerateData again by maintaining m_CurrentScale.
251  for (unsigned int scale = m_CurrentScale; scale < m_NumberOfScales; scale++)
252  {
253  m_CurrentScale = scale;
254 
255  // Set up the optimization parameters for this scale, unless
256  // this is a restart from a previous call to GenerateData. This
257  // section deals with convergence criteria.
258  if (this->GetAbortGenerateData() == false)
259  {
260  // This section deals with convergence.
261  m_Optimizer->SetTolerance(m_Tolerances[scale]);
262  m_Optimizer->SetMaximumNumberOfIterations(m_MaximumNumberOfIterations[scale]);
263 
264  // Set up the exponentially-decreasing regularization constant. If
265  // the decay span is greater than 1 iteration, then we will set up
266  // the annealing approach. Otherwise, the optimizer will simply use
267  // its constant annealing parameter.
268  if (m_RegularizationDecaySpan[scale] >= 1.0f)
269  {
270  m_ShapeEntropyFunction->SetMinimumVarianceDecay(m_RegularizationInitial[scale],
271  m_RegularizationFinal[scale],
272  m_RegularizationDecaySpan[scale]);
273  }
274  else
275  {
276  m_ShapeEntropyFunction->SetMinimumVariance(m_RegularizationInitial[scale]);
277  m_ShapeEntropyFunction->SetHoldMinimumVariance(true);
278  }
279  }
280 
281  this->SetAbortGenerateData(false); // reset the abort flag
282 
283  // Finally, run the optimization. Abort flag is checked in the
284  // iteration callback registered with the optimizer. See
285  // this->OptimizerIterateCallback
286  this->GetOptimizer()->StartOptimization();
287 
288 
289  // If this is multiscale, split particles for the next iteration
290  // -- but not if this is the last iteration.
291  if ((m_NumberOfScales > 1) && (scale != m_NumberOfScales-1) )
292  {
293  m_ParticleSystem->SplitAllParticles(this->GetInput()->GetSpacing()[0] * 1.0);
294  }
295 
296  }
297 }
298 
299 template <class TImage, class TShapeMatrix>
300 void
302 {
303  // Set the minimum neighborhood radius and maximum sigma based on
304  // the domain of the 1st input image.
305  unsigned int maxdim = 0;
306  double maxradius = 0.0;
307  double spacing = this->GetInput()->GetSpacing()[0];
308  for (unsigned int i = 0; i < TImage::ImageDimension; i++)
309  {
310  if (this->GetInput()->GetRequestedRegion().GetSize()[i] > maxdim)
311  {
312  maxdim = this->GetInput()->GetRequestedRegion().GetSize()[i];
313  maxradius = maxdim * this->GetInput()->GetSpacing()[i];
314  }
315  }
316 
317  // Initialize member variables of the optimization functions.
318  // m_ParticleEntropyFunction->SetMinimumNeighborhoodRadius(maxradius / 3.0);
319  m_ParticleEntropyFunction->SetMinimumNeighborhoodRadius(spacing * 5.0);
320  m_ParticleEntropyFunction->SetMaximumNeighborhoodRadius(maxradius);
321 
322  m_ShapeMatrix->Initialize();
323 }
324 
325 template <class TImage, class TShapeMatrix>
326 void
328 ::OptimizerIterateCallback(itk::Object *, const itk::EventObject &)
329 {
330  // Update any optimization values based on potential changes from
331  // the user during the last iteration here. This allows for changes in a
332  // thread-safe manner.
333 
334  // Now invoke any iteration events registered with this filter.
335  this->InvokeEvent(itk::IterationEvent());
336 
337  // Request to terminate this filter.
338  if (this->GetAbortGenerateData() == true)
339  {
340  m_Optimizer->StopOptimization();
341  }
342 }
343 
344 template <class TImage, class TShapeMatrix>
345 bool
348 {
349  bool ok = true;
350  for (unsigned int i = 0; i < m_ParticleSystem->GetNumberOfDomains(); i++)
351  {
352  typename itk::ZeroCrossingImageFilter<ImageType, ImageType>::Pointer zc =
353  itk::ZeroCrossingImageFilter<ImageType, ImageType>::New();
354 
356  m_ParticleSystem->GetDomain(i))->GetImage());
357  zc->Update();
358  itk::ImageRegionConstIteratorWithIndex<ImageType> it(zc->GetOutput(),
359  zc->GetOutput()->GetRequestedRegion());
360 
361  bool done = false;
362  for (it.GoToReverseBegin(); !it.IsAtReverseEnd() && done == false; --it)
363  {
364  if (it.Get() == 1.0)
365  {
366  PointType pos;
368  m_ParticleSystem->GetDomain(i))->GetImage()->TransformIndexToPhysicalPoint(it.GetIndex(), pos);
369  try
370  {
371  m_ParticleSystem->AddPosition(pos, i);
372  done = true;
373  }
374  catch(itk::ExceptionObject &)
375  {
376  done = false;
377  ok = false;
378  }
379  }
380  }
381  }
382  return ok;
383 }
384 
385 } // end namespace
386 
387 #endif
unsigned int GetDomainIndexByName(const std::string &name)
PSMSurfaceNeighborhood is a general purpose neighborhood object that computes neighborhoods based on ...
void OptimizerIterateCallback(Object *, const EventObject &)
void SetDomainName(const std::string &s, unsigned int i)
This the most basic of all PSM model optimization filters. This filter assembles all of the necessary...
void AddCuttingPlane(const vnl_vector_fixed< double, 3 > &x, const vnl_vector_fixed< double, 3 > &y, const vnl_vector_fixed< double, 3 > &z, unsigned int domain)