Medical Imaging Interaction Toolkit  2018.4.99-389bf124
Medical Imaging Interaction Toolkit
itkContourExtractor2DImageFilter.txx
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 /*===================================================================
14 
15 This file is based heavily on a corresponding ITK filter.
16 
17 ===================================================================*/
18 
19 #ifndef __itkContourExtractor2DImageFilter_txx
20 #define __itkContourExtractor2DImageFilter_txx
21 
22 #include "itkConstShapedNeighborhoodIterator.h"
23 #include "itkConstShapedNeighborhoodIterator.h"
24 #include "itkContourExtractor2DImageFilter.h"
25 #include "itkProgressReporter.h"
26 #include "vcl_cmath.h"
27 
28 namespace itk
29 {
30  // Constructor
31  template <class TInputImage>
32  ContourExtractor2DImageFilter<TInputImage>::ContourExtractor2DImageFilter()
33  {
34  this->m_ContourValue = NumericTraits<InputRealType>::Zero;
35  this->m_ReverseContourOrientation = false;
36  this->m_VertexConnectHighPixels = false;
37  this->m_UseCustomRegion = false;
38  this->m_NumberOfContoursCreated = 0;
39  }
40 
41  // Destructor
42  template <class TInputImage>
43  ContourExtractor2DImageFilter<TInputImage>::~ContourExtractor2DImageFilter()
44  {
45  }
46 
47  template <class TInputImage>
48  void ContourExtractor2DImageFilter<TInputImage>::GenerateData()
49  {
50  // Make sure the structures for containing, looking up, and numbering the
51  // growing contours are empty and ready.
52  m_Contours.clear();
53  m_ContourStarts.clear();
54  m_ContourEnds.clear();
55  m_NumberOfContoursCreated = 0;
56 
57  // Set up an iterator to "march the squares" across the image.
58  // We associate each 2px-by-2px square with the pixel in the upper left of
59  // that square. We then iterate across the image, examining these 2x2 squares
60  // and building the contour. By iterating the upper-left pixel of our
61  // "current square" across every pixel in the image except those on the
62  // bottom row and rightmost column, we have visited every valid square in the
63  // image.
64 
65  InputRegionType region = this->GetInput()->GetRequestedRegion();
66  typename InputRegionType::SizeType shrunkSize = region.GetSize();
67  shrunkSize[0] -= 1;
68  shrunkSize[1] -= 1;
69  InputRegionType shrunkRegion(region.GetIndex(), shrunkSize);
70 
71  // Set up a progress reporter
72  ProgressReporter progress(this, 0, shrunkRegion.GetNumberOfPixels());
73 
74  // A 1-pixel radius sets up a neighborhood with the following indices:
75  // 0 1 2
76  // 3 4 5
77  // 6 7 8
78  // We are interested only in the square of 4,5,7,8 which is the 2x2 square
79  // with the center pixel at the top-left. So we only activate the
80  // coresponding offsets, and only query pixels 4, 5, 7, and 8 with the
81  // iterator's GetPixel method.
82  typedef ConstShapedNeighborhoodIterator<InputImageType> SquareIterator;
83  typename SquareIterator::RadiusType radius = {{1, 1}};
84  SquareIterator it(radius, this->GetInput(), shrunkRegion);
85  InputOffsetType none = {{0, 0}};
86  InputOffsetType right = {{1, 0}};
87  InputOffsetType down = {{0, 1}};
88  InputOffsetType diag = {{1, 1}};
89  it.ActivateOffset(none);
90  it.ActivateOffset(right);
91  it.ActivateOffset(down);
92  it.ActivateOffset(diag);
93 
94  for (it.GoToBegin(); !it.IsAtEnd(); ++it)
95  {
96  // There are sixteen different possible square types, diagramed below.
97  // A + indicates that the vertex is above the contour value, and a -
98  // indicates that the vertex is below or equal to the contour value.
99  // The vertices of each square are here numbered:
100  // 01
101  // 23
102  // and treated as a binary value with the bits in that order. Thus each
103  // square can be so numbered:
104  // 0-- 1+- 2-+ 3++ 4-- 5+- 6-+ 7++
105  // -- -- -- -- +- +- +- +-
106  //
107  // 8-- 9+- 10-+ 11++ 12-- 13+- 14-+ 15++
108  // -+ -+ -+ -+ ++ ++ ++ ++
109  //
110  // The position of the line segment that cuts through (or doesn't, in case
111  // 0 and 15) each square is clear, except in cases 6 and 9. In this case,
112  // where the segments are placed is determined by
113  // m_VertexConnectHighPixels. If m_VertexConnectHighPixels is false, then
114  // lines like are drawn through square 6, and lines like are drawn through
115  // square 9. Otherwise, the situation is reversed.
116  // Finally, recall that we draw the lines so that (moving from tail to
117  // head) the lower-valued pixels are on the left of the line. So, for
118  // example, case 1 entails a line slanting from the middle of the top of
119  // the square to the middle of the left side of the square.
120 
121  // (1) Determine what number square we are currently inspecting. Remember
122  // that as far as the neighborhood iterator is concerned, our square
123  // 01 is numbered as 45
124  // 23 78
125 
126  InputPixelType v0, v1, v2, v3;
127  v0 = it.GetPixel(4);
128  v1 = it.GetPixel(5);
129  v2 = it.GetPixel(7);
130  v3 = it.GetPixel(8);
131  InputIndexType index = it.GetIndex();
132  unsigned char squareCase = 0;
133  if (v0 > m_ContourValue)
134  squareCase += 1;
135  if (v1 > m_ContourValue)
136  squareCase += 2;
137  if (v2 > m_ContourValue)
138  squareCase += 4;
139  if (v3 > m_ContourValue)
140  squareCase += 8;
141 
142 // Set up macros to find the ContinuousIndex where the contour intersects
143 // one of the sides of the square. Normally macros should, of course, be
144 // eschewed, but since this is an inner loop not calling the function four
145 // times when two would do is probably worth while. Plus, copy-pasting
146 // these into the switch below is even worse. InterpolateContourPosition
147 // takes the values at two vertices, the index of the first vertex, and the
148 // offset between the two vertices.
149 #define TOP_ this->InterpolateContourPosition(v0, v1, index, right)
150 #define BOTTOM_ this->InterpolateContourPosition(v2, v3, index + down, right)
151 #define LEFT_ this->InterpolateContourPosition(v0, v2, index, down)
152 #define RIGHT_ this->InterpolateContourPosition(v1, v3, index + right, down)
153 
154  // (2) Add line segments to the growing contours as defined by the cases.
155  // AddSegment takes a "from" vertex and a "to" vertex, and adds it to the
156  // a growing contour, creates a new contour, or merges two together.
157  switch (squareCase)
158  {
159  case 0: // no line
160  break;
161  case 1: // top to left
162  this->AddSegment(TOP_, LEFT_);
163  break;
164  case 2: // right to top
165  this->AddSegment(RIGHT_, TOP_);
166  break;
167  case 3: // right to left
168  this->AddSegment(RIGHT_, LEFT_);
169  break;
170  case 4: // left to bottom
171  this->AddSegment(LEFT_, BOTTOM_);
172  break;
173  case 5: // top to bottom
174  this->AddSegment(TOP_, BOTTOM_);
175  break;
176  case 6:
177  if (m_VertexConnectHighPixels)
178  {
179  // left to top
180  this->AddSegment(LEFT_, TOP_);
181  // right to bottom
182  this->AddSegment(RIGHT_, BOTTOM_);
183  }
184  else
185  {
186  // right to top
187  this->AddSegment(RIGHT_, TOP_);
188  // left to bottom
189  this->AddSegment(LEFT_, BOTTOM_);
190  }
191  break;
192  case 7: // right to bottom
193  this->AddSegment(RIGHT_, BOTTOM_);
194  break;
195  case 8: // bottom to right
196  this->AddSegment(BOTTOM_, RIGHT_);
197  break;
198  case 9:
199  if (m_VertexConnectHighPixels)
200  {
201  // top to right
202  this->AddSegment(TOP_, RIGHT_);
203  // bottom to left
204  this->AddSegment(BOTTOM_, LEFT_);
205  }
206  else
207  {
208  // top to left
209  this->AddSegment(TOP_, LEFT_);
210  // bottom to right
211  this->AddSegment(BOTTOM_, RIGHT_);
212  }
213  break;
214  case 10: // bottom to top
215  this->AddSegment(BOTTOM_, TOP_);
216  break;
217  case 11: // bottom to left
218  this->AddSegment(BOTTOM_, LEFT_);
219  break;
220  case 12: // left to right
221  this->AddSegment(LEFT_, RIGHT_);
222  break;
223  case 13: // top to right
224  this->AddSegment(TOP_, RIGHT_);
225  break;
226  case 14: // left to top
227  this->AddSegment(LEFT_, TOP_);
228  break;
229  case 15: // no line
230  break;
231  } // switch squareCase
232  progress.CompletedPixel();
233  } // iteration
234 
235  // Now create the outputs paths from the deques we've been using.
236  this->FillOutputs();
237  m_Contours.clear();
238  m_ContourStarts.clear();
239  m_ContourEnds.clear();
240  m_NumberOfContoursCreated = 0;
241  }
242 
243  template <class TInputImage>
244  inline typename ContourExtractor2DImageFilter<TInputImage>::VertexType
245  ContourExtractor2DImageFilter<TInputImage>::InterpolateContourPosition(InputPixelType fromValue,
246  InputPixelType toValue,
247  InputIndexType fromIndex,
248  InputOffsetType toOffset)
249  {
250  VertexType output;
251  // Now calculate the fraction of the way from 'from' to 'to' that the contour
252  // crosses. Interpolate linearly: y = v0 + (v1 - v0) * x, and solve for the
253  // x that gives y = m_ContourValue: x = (m_ContourValue - v0) / (v1 - v0).
254  // This assumes that v0 and v1 are separated by exactly ONE unit. So the to
255  // Offset. value must have exactly one component 1 and the other component 0.
256  // Also this assumes that fromValue and toValue are different. Otherwise we
257  // can't interpolate anything!
258  itkAssertOrThrowMacro((fromValue != toValue), "source and destination are the same");
259 
260  itkAssertOrThrowMacro(((toOffset[0] == 0 && toOffset[1] == 1) || (toOffset[0] == 1 && toOffset[1] == 0)),
261  "toOffset has unexpected values");
262 
263  double x =
264  (m_ContourValue - static_cast<InputRealType>(fromValue)) / (toValue - static_cast<InputRealType>(fromValue));
265 
266  output[0] = fromIndex[0] + x * toOffset[0];
267  output[1] = fromIndex[1] + x * toOffset[1];
268 
269  return output;
270  }
271 
272  template <class TInputImage>
273  void ContourExtractor2DImageFilter<TInputImage>::AddSegment(VertexType from, VertexType to)
274  {
275  if (from == to)
276  {
277  // Arc is degenerate: ignore, and the from/two point will be connected
278  // later by other squares. Degeneracy happens when (and only when) a square
279  // has exactly one vertex that is the contour value, and the rest are above
280  // that value.
281  return;
282  }
283 
284  // Try to find an existing contour that starts where the new segment ends.
285  VertexMapIterator newTail = m_ContourStarts.find(to);
286  // Try to find an existing contour that ends where the new segment starts.
287  VertexMapIterator newHead = m_ContourEnds.find(from);
288 
289  if (newTail != m_ContourStarts.end() && newHead != m_ContourEnds.end())
290  {
291  // We need to connect these two contours. The act of connecting them will
292  // add the needed arc.
293  auto tail = newTail->second;
294  itkAssertOrThrowMacro((tail->front() == to), "End doesn't match Beginning");
295  auto head = newHead->second;
296  itkAssertOrThrowMacro((head->back() == from), "Beginning doesn't match End");
297  if (head == tail)
298  {
299  // We've closed a contour. Add the end point, and remove from the maps
300  head->push_back(to);
301  m_ContourStarts.erase(newTail);
302  // erase the front of tail. Because head and tail are the same contour,
303  // don't worry about erasing the front of head!
304  m_ContourEnds.erase(newHead); // erase the end of head/tail.
305  }
306  else
307  {
308  // We have found two distinct contours that need to be joined. Careful
309  // here: we want to keep the first segment in the list when merging so
310  // that contours are always returned in top-to-bottom, right-to-left
311  // order (with regard to the image pixel found to be inside the contour).
312  if (tail->m_ContourNumber > head->m_ContourNumber)
313  {
314  // if tail was created later than head...
315  // Copy tail to the end of head and remove
316  // tail from everything.
317  head->insert(head->end(), tail->begin(), tail->end());
318 
319  // Now remove 'tail' from the list and the maps because it has been
320  // subsumed.
321  m_ContourStarts.erase(newTail);
322  int erased = m_ContourEnds.erase(tail->back());
323  // There should be exactly one entry in the hash for that endpoint
324  if (erased != 1)
325  {
326  itkWarningMacro(<< "There should be exactly one entry in the hash for that endpoint, but there are "
327  << erased);
328  }
329  m_Contours.erase(tail); // remove from the master list
330 
331  // Now remove the old end of 'head' from the ends map and add
332  // the new end.
333  m_ContourEnds.erase(newHead);
334  m_ContourEnds.insert(VertexContourRefPair(head->back(), head));
335  }
336  else
337  {
338  // Copy head to the beginning of tail and remove
339  // head from everything.
340  tail->insert(tail->begin(), head->begin(), head->end());
341 
342  // Now remove 'head' from the list and the maps because
343  // it has been subsumed.
344  m_ContourEnds.erase(newHead);
345  int erased = m_ContourStarts.erase(head->front());
346  if (erased != 1)
347  {
348  itkWarningMacro(<< "There should be exactly one entry in the hash for that endpoint, but there are "
349  << erased);
350  }
351  m_Contours.erase(head); // remove from the master list
352 
353  // Now remove the old start of 'tail' from the starts map and
354  // add the new start.
355  m_ContourStarts.erase(newTail);
356  m_ContourStarts.insert(VertexContourRefPair(tail->front(), tail));
357  }
358  }
359  }
360  else if (newTail == m_ContourStarts.end() && newHead == m_ContourEnds.end())
361  {
362  // No contours found: add a new one.
363  // Make it on the heap. It will be copied into m_Contours.
364  ContourType contour;
365 
366  // Add the endpoints
367  contour.push_front(from);
368  contour.push_back(to);
369  contour.m_ContourNumber = m_NumberOfContoursCreated++;
370  // Add the contour to the end of the list and get a reference to it.
371  m_Contours.push_back(contour);
372 
373  // recall that end() is an iterator to one past the back!
374  auto newContour = --m_Contours.end();
375  // add the endpoints and an iterator pointing to the contour
376  // in the list to the maps.
377  m_ContourStarts.insert(VertexContourRefPair(from, newContour));
378  m_ContourEnds.insert(VertexContourRefPair(to, newContour));
379  }
380  else if (newTail != m_ContourStarts.end() && newHead == m_ContourEnds.end())
381  {
382  // Found a single contour to which the new arc should be prepended.
383  auto tail = newTail->second;
384  itkAssertOrThrowMacro((tail->front() == to), "End doesn't match Beginning");
385  tail->push_front(from);
386  // erase the old start of this contour
387  m_ContourStarts.erase(newTail);
388  // Now add the new start of this contour.
389  m_ContourStarts.insert(VertexContourRefPair(from, tail));
390  }
391  else if (newTail == m_ContourStarts.end() && newHead != m_ContourEnds.end())
392  {
393  // Found a single contour to which the new arc should be appended.
394  auto head = newHead->second;
395  itkAssertOrThrowMacro((head->back() == from), "Beginning doesn't match End");
396  head->push_back(to);
397  // erase the old end of this contour
398  m_ContourEnds.erase(newHead);
399  // Now add the new start of this contour.
400  m_ContourEnds.insert(VertexContourRefPair(to, head));
401  }
402  }
403 
404  template <class TInputImage>
405  void ContourExtractor2DImageFilter<TInputImage>::FillOutputs()
406  {
407  this->SetNumberOfIndexedOutputs(m_Contours.size());
408  int i = 0;
409  for (auto it = m_Contours.begin(); it != m_Contours.end(); it++, i++)
410  {
411  OutputPathPointer output = this->GetOutput(i);
412  if (output.IsNull())
413  {
414  // Static cast is OK because we know PathSource will make its templated
415  // class type
416  output = static_cast<OutputPathType *>(this->MakeOutput(i).GetPointer());
417  this->SetNthOutput(i, output.GetPointer());
418  }
419  typename VertexListType::Pointer path = const_cast<VertexListType *>(output->GetVertexList());
420  path->Initialize();
421  path->reserve(it->size()); // use std::vector version of 'reserve()'
422  // instead of VectorContainer::Reserve() to work around
423  // the fact that the latter is essentially std::vector::resize(),
424  // which is not what we want.
425 
426  // Now put all the points from the contour deque into the path and
427  // mark output as modified
428 
429  typedef typename ContourType::const_iterator ConstIteratorType;
430  if (m_ReverseContourOrientation)
431  {
432  ConstIteratorType itC = (*it).end();
433  do
434  {
435  itC--;
436  path->push_back(*itC);
437  } while (itC != (*it).begin());
438  }
439  else
440  {
441  ConstIteratorType itC = (*it).begin();
442  while (itC != (*it).end())
443  {
444  path->push_back(*itC);
445  itC++;
446  }
447  }
448  output->Modified();
449  }
450  }
451 
452  template <class TInputImage>
453  void ContourExtractor2DImageFilter<TInputImage>::SetRequestedRegion(const InputRegionType region)
454  {
455  itkDebugMacro("setting RequestedRegion to " << region);
456  m_UseCustomRegion = true;
457  if (this->m_RequestedRegion != region)
458  {
459  this->m_RequestedRegion = region;
460  this->Modified();
461  }
462  }
463 
464  template <class TInputImage>
465  void ContourExtractor2DImageFilter<TInputImage>::ClearRequestedRegion()
466  {
467  itkDebugMacro("Clearing RequestedRegion.");
468  if (this->m_UseCustomRegion == true)
469  {
470  this->m_UseCustomRegion = false;
471  this->Modified();
472  }
473  }
474 
475  template <class TInputImage>
476  void ContourExtractor2DImageFilter<TInputImage>::GenerateInputRequestedRegion()
477  {
478  InputImageType *input = const_cast<InputImageType *>(this->GetInput());
479  if (!input)
480  return;
481 
482  if (m_UseCustomRegion)
483  {
484  InputRegionType requestedRegion = m_RequestedRegion;
485  if (requestedRegion.Crop(input->GetLargestPossibleRegion()))
486  {
487  input->SetRequestedRegion(requestedRegion);
488  return;
489  }
490  else
491  {
492  // Couldn't crop the region (requested region is outside the largest
493  // possible region). Throw an exception.
494 
495  // store what we tried to request (prior to trying to crop)
496  input->SetRequestedRegion(requestedRegion);
497 
498  // build an exception
499  InvalidRequestedRegionError e(__FILE__, __LINE__);
500  e.SetLocation(ITK_LOCATION);
501  e.SetDescription("Requested region is outside the largest possible region.");
502  e.SetDataObject(input);
503  throw e;
504  }
505  }
506  else
507  {
508  input->SetRequestedRegion(input->GetLargestPossibleRegion());
509  }
510  }
511 
512  /**
513  * Standard "PrintSelf" method
514  */
515  template <class TInputImage>
516  void ContourExtractor2DImageFilter<TInputImage>::PrintSelf(std::ostream &os, Indent indent) const
517  {
518  Superclass::PrintSelf(os, indent);
519  os << indent << "ReverseContourOrientation: " << m_ReverseContourOrientation << std::endl;
520  os << indent << "VertexConnectHighPixels: " << m_VertexConnectHighPixels << std::endl;
521  os << indent << "UseCustomRegion: " << m_UseCustomRegion << std::endl;
522  os << indent << "NumericTraits: " << m_UseCustomRegion << std::endl;
523  os << indent << "NumberOfContoursCreated: " << m_NumberOfContoursCreated << std::endl;
524  if (m_UseCustomRegion)
525  {
526  os << indent << "Custom region: " << m_RequestedRegion << std::endl;
527  }
528 
529  typedef typename NumericTraits<InputRealType>::PrintType InputRealPrintType;
530 
531  os << indent << "Contour value: " << static_cast<InputRealPrintType>(m_ContourValue) << std::endl;
532  }
533 
534 } // end namespace itk
535 
536 #endif