Orfeo Toolbox  3.16
itkActiveShapeModelCalculator.txx
Go to the documentation of this file.
1 /*=========================================================================
2 
3  Program: Insight Segmentation & Registration Toolkit
4  Module: $RCSfile: itkActiveShapeModelCalculator.txx,v $
5  Language: C++
6  Date: $Date: 2009-02-01 13:08:40 $
7  Version: $Revision: 1.8 $
8 
9  Copyright (c) Insight Software Consortium. All rights reserved.
10  See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details.
11 
12  This software is distributed WITHOUT ANY WARRANTY; without even
13  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  PURPOSE. See the above copyright notices for more information.
15 
16 =========================================================================*/
17 #ifndef __itkActiveShapeModelCalculator_txx
18 #define __itkActiveShapeModelCalculator_txx
20 
21 namespace itk
22 {
23 
25 {
26 public:
30  InvalidActiveShapeModeError(const char *file, unsigned int lineNumber) : ExceptionObject(file, lineNumber) { this->SetDescription("No valid training image are availble.");}
31 
35  InvalidActiveShapeModeError(const std::string& file, unsigned int lineNumber) : ExceptionObject(file, lineNumber) { this->SetDescription("No valid training image are available.");}
36 
38 };
39 
43 template<class TImage>
44 void
47 {
48  if( !m_Image )
49  {
50  return;
51  }
52 
53  typename GradientFilterType::Pointer gradientFilter = GradientFilterType::New();
54  typename BinaryFilterType::Pointer binaryFilter = BinaryFilterType::New();
55  typename DistanceMapFilterType::Pointer distanceFilter = DistanceMapFilterType::New();
56 
57  binaryFilter->SetOutsideValue( 0.0 );
58  binaryFilter->SetInsideValue( 1.0 );
59  binaryFilter->SetLowerThreshold( m_LowerThreshold );
60  binaryFilter->SetUpperThreshold( 255.0 );
61 
62  gradientFilter->SetInput( m_Image );
63  binaryFilter->SetInput( gradientFilter->GetOutput() );
64  distanceFilter->InputIsBinaryOn();
65  distanceFilter->SetInput( binaryFilter->GetOutput() );
66  distanceFilter->Update();
67 
68  typedef typename Image2DType::RegionType Region2DType;
69  typedef typename Region2DType::SizeType Size2DType;
70  typedef typename Region2DType::IndexType Index2DType;
71 
72  Region2DType region;
73  Size2DType size;
74  Index2DType index;
75 
76  typename Image3DType::ConstPointer inputImage;
77  typename Image2DType::Pointer outputImage = Image2DType::New();
78 
79  inputImage = distanceFilter->GetOutput();
80  typename Image3DType::RegionType requestedRegion = inputImage->GetRequestedRegion();
81 
82  unsigned int projectionDirection = 2;
83  unsigned int i, j;
84  unsigned int direction[2];
85 
86  for (i = 0, j = 0; i < 3; ++i )
87  {
88  if (i != projectionDirection)
89  {
90  direction[j] = i;
91  j++;
92  }
93  }
94 
95  index[ direction[0] ] = requestedRegion.GetIndex()[ direction[0] ];
96  index[ 1- direction[0] ] = requestedRegion.GetIndex()[ direction[1] ];
97  size[ direction[0] ] = requestedRegion.GetSize()[ direction[0] ];
98  size[ 1- direction[0] ] = requestedRegion.GetSize()[ direction[1] ];
99  unsigned int slices = requestedRegion.GetSize()[ 2 ];
100 
104  m_NumberOfTrainingImages = slices;
105 
106  region.SetSize( size );
107  region.SetIndex( index );
108  outputImage->SetRegions( region );
109  outputImage->Allocate();
110  SliceIteratorType inputIt( inputImage, inputImage->GetRequestedRegion() );
111  LinearIteratorType outputIt( outputImage, outputImage->GetRequestedRegion() );
112  inputIt.SetFirstDirection( direction[1] );
113  inputIt.SetSecondDirection( direction[0] );
114  outputIt.SetDirection( 1 - direction[0] );
115  outputIt.GoToBegin();
116 
117  while ( ! outputIt.IsAtEnd() )
118  {
119  while ( ! outputIt.IsAtEndOfLine() )
120  {
121  outputIt.Set( NumericTraits<unsigned char>::NonpositiveMin() );
122  ++outputIt;
123  }
124  outputIt.NextLine();
125  }
126 
127  inputIt.GoToBegin();
128  outputIt.GoToBegin();
129 
130  while( !inputIt.IsAtEnd() )
131  {
132  while ( !inputIt.IsAtEndOfSlice() )
133  {
134  while ( !inputIt.IsAtEndOfLine() )
135  {
136  float valueOutput = outputIt.Get();
137  float valueInput = inputIt.Get();
138  float sum = valueOutput + valueInput;
139  outputIt.Set( static_cast<PixelType> (sum) );
140  ++inputIt;
141  ++outputIt;
142  }
143  outputIt.NextLine();
144  inputIt.NextLine();
145  }
146  outputIt.GoToBegin();
147  inputIt.NextSlice();
148  }
149  outputIt.GoToBegin();
150 
151  while ( ! outputIt.IsAtEnd() )
152  {
153  while ( ! outputIt.IsAtEndOfLine() )
154  {
155  float valueOutput = outputIt.Get();
156  float mean = valueOutput / static_cast<float> (slices);
157  outputIt.Set( static_cast<PixelType>(mean) );
158  ++outputIt;
159  }
160  outputIt.NextLine();
161  }
162 
163  typename BinaryFilterType1::Pointer binaryFilter1 = BinaryFilterType1::New();
164  binaryFilter1->SetOutsideValue( 0 );
165  binaryFilter1->SetInsideValue( 1 );
166  binaryFilter1->SetLowerThreshold( 0.0 );
167  binaryFilter1->SetUpperThreshold( m_UpperThreshold1 );
168  typename ThinFilterType::Pointer thinFilter = ThinFilterType::New();
169  typename PruneFilterType::Pointer pruneFilter = PruneFilterType::New();
170  typename PointSetType::Pointer pointSet = PointSetType::New();
171  typename PointsContainer::Pointer points = PointsContainer::New();
172  typename PointDataContainer::Pointer pointData = PointDataContainer::New();
173 
174  binaryFilter1->SetInput( outputImage );
175  thinFilter->SetInput( binaryFilter1->GetOutput() );
176  pruneFilter->SetIteration( m_PruneIteration );
177 
178  pruneFilter->SetInput( thinFilter->GetOutput() );
179  pruneFilter->Update();
180 
181  typename Image2D8bitsType::Pointer pruneImage;
182  pruneImage = pruneFilter->GetOutput();
183  IteratorType ot( pruneImage, pruneImage->GetRequestedRegion() );
184 
185  PointType p;
186  IndexType position;
187  Pixel8bitsType value0 = 0;
188  Pixel8bitsType value1 = 1;
189  ot.GoToBegin();
190 
191  while( !ot.IsAtEnd() )
192  {
193  if ( ot.Get() )
194  {
195  position = ot.GetIndex();
196  ot.Set ( value0 );
197  break;
198  }
199  ++ot;
200  }
201 
202  for (unsigned int identifier = 0; identifier<2; identifier++)
203  {
204  p[identifier] = position[identifier];
205  }
206 
207  int pointId = 0;
208  points->InsertElement( pointId, p );
209  pointData->InsertElement( pointId, value1 );
210  pointId++;
211  typename NeighborIteratorType::RadiusType radius;
212  radius.Fill(1);
213  NeighborIteratorType otNeighbor( radius, pruneImage, pruneImage->GetRequestedRegion() );
214  typename NeighborIteratorType::OffsetType offset1 = {{1,0}};
215  typename NeighborIteratorType::OffsetType offset2 = {{0,-1}};
216  typename NeighborIteratorType::OffsetType offset3 = {{-1,0 }};
217  typename NeighborIteratorType::OffsetType offset4 = {{0,1}};
218  typename NeighborIteratorType::OffsetType offset5 = {{1,1}};
219  typename NeighborIteratorType::OffsetType offset6 = {{1,-1}};
220  typename NeighborIteratorType::OffsetType offset7 = {{-1,-1}};
221  typename NeighborIteratorType::OffsetType offset8 = {{-1,1}};
222  otNeighbor.SetLocation( position );
223  unsigned int count = 1;
224 
225  while(count)
226  {
227  count = 0;
228  if ( otNeighbor.GetPixel(offset1) )
229  {
230  position = otNeighbor.GetIndex(offset1);
231  otNeighbor.SetPixel ( offset1, value0 );
232  for (unsigned int identifier = 0; identifier<2; identifier++)
233  {
234  p[identifier] = position[identifier];
235  }
236  points->InsertElement( pointId, p );
237  pointData->InsertElement( pointId, value1 );
238  pointId++;
239  count = 1;
240  otNeighbor += offset1;
241  }
242  else
243  {
244  if ( otNeighbor.GetPixel(offset2) )
245  {
246  position = otNeighbor.GetIndex(offset2);
247  otNeighbor.SetPixel ( offset2, value0 );
248  for (unsigned int identifier = 0; identifier<2; identifier++)
249  {
250  p[identifier] = position[identifier];
251  }
252  points->InsertElement( pointId, p );
253  pointData->InsertElement( pointId, value1 );
254  pointId++;
255  count = 1;
256  otNeighbor += offset2;
257  }
258  else
259  {
260  if ( otNeighbor.GetPixel(offset3) )
261  {
262  position = otNeighbor.GetIndex(offset3);
263  otNeighbor.SetPixel ( offset3, value0 );
264  for (unsigned int identifier = 0; identifier<2; identifier++)
265  {
266  p[identifier] = position[identifier];
267  }
268  points->InsertElement( pointId, p );
269  pointData->InsertElement( pointId, value1 );
270  pointId++;
271  count = 1;
272  otNeighbor += offset3;
273  }
274  else
275  {
276  if ( otNeighbor.GetPixel(offset4) )
277  {
278  position = otNeighbor.GetIndex(offset4);
279  otNeighbor.SetPixel ( offset4, value0 );
280  for (unsigned int identifier = 0; identifier<2; identifier++)
281  {
282  p[identifier] = position[identifier];
283  }
284  points->InsertElement( pointId, p );
285  pointData->InsertElement( pointId, value1 );
286  pointId++;
287  count = 1;
288  otNeighbor += offset4;
289  }
290  else
291  {
292  if ( otNeighbor.GetPixel(offset5) )
293  {
294  position = otNeighbor.GetIndex(offset5);
295  otNeighbor.SetPixel ( offset5, value0 );
296  for (unsigned int identifier = 0; identifier<2; identifier++)
297  {
298  p[identifier] = position[identifier];
299  }
300  points->InsertElement( pointId, p );
301  pointData->InsertElement( pointId,value1 );
302  pointId++;
303  count = 1;
304  otNeighbor += offset5;
305  }
306  else
307  {
308  if ( otNeighbor.GetPixel(offset6) )
309  {
310  position = otNeighbor.GetIndex(offset6);
311  otNeighbor.SetPixel ( offset6,value0 );
312  for (unsigned int identifier = 0; identifier<2; identifier++)
313  {
314  p[identifier] = position[identifier];
315  }
316  points->InsertElement( pointId, p );
317  pointData->InsertElement( pointId, value1 );
318  pointId++;
319  count = 1;
320  otNeighbor += offset6;
321  }
322  else
323  {
324  if ( otNeighbor.GetPixel(offset7) )
325  {
326  position = otNeighbor.GetIndex(offset7);
327  otNeighbor.SetPixel ( offset7, value0 );
328  for (unsigned int identifier = 0; identifier<2; identifier++)
329  {
330  p[identifier] = position[identifier];
331  }
332  points->InsertElement( pointId, p );
333  pointData->InsertElement( pointId, value1 );
334  pointId++;
335  count = 1;
336  otNeighbor += offset7;
337  }
338  else
339  {
340  if ( otNeighbor.GetPixel(offset8) )
341  {
342  position = otNeighbor.GetIndex(offset8);
343  otNeighbor.SetPixel ( offset8, value0 );
344  for (unsigned int identifier = 0; identifier<2; identifier++)
345  {
346  p[identifier] = position[identifier];
347  }
348  points->InsertElement( pointId, p );
349  pointData->InsertElement( pointId, value1 );
350  pointId++;
351  count = 1;
352  otNeighbor += offset8;
353  }
354  }
355  }
356  }
357  }
358  }
359  }
360  }
361  }
362 
363  pointSet->SetPoints( points );
364  pointSet->SetPointData( pointData );
365  List2DType m_Queue;
366  List2DType m_Queue2;
367  IndexType current;
368  m_Queue.clear();
369  m_Queue2.clear();
370  position[ 0 ] = 0;
371  position[ 1 ] = (pointSet->GetNumberOfPoints()) - 1;
372  PointType p1, p2;
373  Vector3DType v1, v2, v3;
374  double squareNorm1, squareNorm2;
375  unsigned int pointId2 = 0;
376 
377  m_Queue.push_front(position);
378  while (! m_Queue.empty())
379  {
380  current = m_Queue.front();
381  m_Queue.pop_front();
382  double m_distance = 0;
383  pointSet->GetPoint( current[ 0 ], & p1 );
384  pointSet->GetPoint( current[ 1 ], & p2 );
385  for (unsigned int identifier = 0; identifier<2; identifier++)
386  {
387  v1[ identifier ]= p2[ identifier ] - p1[ identifier ];
388  }
389  v1[ 2 ] = 0;
390  squareNorm1 = v1.GetSquaredNorm();
391  for( pointId = (current[ 0 ] + 1); pointId < (current[ 1 ] - 1); pointId++)
392  {
393  pointSet->GetPoint( pointId, & p2 );
394  for (unsigned int identifier = 0; identifier < 2; identifier++)
395  {
396  v2[ identifier ]= p2[ identifier ] - p1[ identifier ];
397  }
398  v2[ 2 ] = 0;
399  for (unsigned int identifier = 0; identifier < 2; identifier++)
400  {
401  v3 [identifier] = 0;
402  }
403  v3[ 2 ] = (v1[0] * v2[ 1 ]) - (v1[ 1 ] * v2[ 0 ]);
404  squareNorm2 = v3.GetSquaredNorm();
405  double m_temp = squareNorm2 / squareNorm1;
406  if ( m_temp > m_distance)
407  {
408  m_distance = m_temp;
409  pointId2 = pointId;
410  }
411  }
412  if (m_distance > m_Tolerance)
413  {
414  position[ 0 ] = current[ 0 ];
415  position[ 1 ] = pointId2;
416  m_Queue.push_front( position );
417  position[ 0 ] = pointId2;
418  position[ 1 ] = current[ 1 ];
419  m_Queue.push_front( position );
420  }
421  else
422  {
423  position[ 0 ] = static_cast<unsigned long int> (p1[ 0 ]);
424  position[ 1 ] = static_cast<unsigned long int> (p1[ 1 ]);
425  m_Queue2.push_back( position );
426  }
427  }
428 
429  unsigned int numberOfLandmarks = static_cast<unsigned int> (m_Queue2.size());
430  typename SampleType::Pointer sampleLandmarks = SampleType::New();
432 
433 
434  while (! m_Queue2.empty())
435  {
436  current = m_Queue2.front();
437  m_Queue2.pop_front();
438  mv [ 0 ] = current [ 0 ];
439  mv [ 1 ] = current [ 1 ];
440  sampleLandmarks->PushBack( mv );
441  }
442 
443 
444  VectorType v;
445  typename VectorType::iterator p6;
446  MatrixOfIntegerType coordLandmarks( 2*numberOfLandmarks, slices );
447  coordLandmarks.fill(0);
448  m_Means.set_size( 2*numberOfLandmarks );
449  m_Means.fill(0);
450  MatrixOfDoubleType covarianceMatrix( 2*numberOfLandmarks, 2*numberOfLandmarks );
451  covarianceMatrix.fill(0);
452  MatrixOfDoubleType identityMatrix( 2*numberOfLandmarks, 2*numberOfLandmarks );
453  identityMatrix.set_identity();
454 
455  typename BinaryFilterType2::Pointer binaryFilter2 = BinaryFilterType2::New();
456  binaryFilter2->SetOutsideValue( 0 );
457  binaryFilter2->SetInsideValue( 255 );
458  binaryFilter2->SetLowerThreshold( 0.0 );
459  binaryFilter2->SetUpperThreshold( m_UpperThreshold2 );
460  binaryFilter2->SetInput( distanceFilter->GetOutput() );
461  typename Image3D8bitsType::ConstPointer inputImage2;
462  inputImage2 = binaryFilter2->GetOutput();
463  binaryFilter2->Update();
464  ConstIteratorType constIterator( inputImage2, inputImage2->GetLargestPossibleRegion());
465 
466  Index3DType position3D;
467  MeasurementVectorType posRight, posLeft, dxyRef1, dxyRef2;
468  int d, dx, dy, sx, sy;
469  unsigned int ax, ay;
470 
471  dxyRef1.Fill(0);
472  dxyRef2.Fill(0);
473 
474  for (i = 0; i < slices; i++)
475  {
476  for ( j = 0; j < numberOfLandmarks; j++ )
477  {
478  mv = sampleLandmarks->GetMeasurementVector(j);
479  position3D [ 0 ] = mv [ 0 ];
480  position3D [ 1 ] = mv [ 1 ];
481  position3D [ 2 ] = i;
482  constIterator.SetIndex(position3D);
483  if (constIterator.Get())
484  {
485  v.push_back (mv [ 0 ]);
486  v.push_back (mv [ 1 ]);
487  }
488  else
489  {
490  for (unsigned int identifier = 0; identifier<2; identifier++)
491  {
492  posRight[ identifier ] = mv [ identifier ];
493  posLeft[ identifier ] = mv [ identifier ];
494  }
495  if (j == 0)
496  {
497  dxyRef1 = sampleLandmarks->GetMeasurementVector(1) -
498  sampleLandmarks->GetMeasurementVector(0);
499  dxyRef2 = sampleLandmarks->GetMeasurementVector(numberOfLandmarks - 1) -
500  sampleLandmarks->GetMeasurementVector(0);
501  }
502  if (j == numberOfLandmarks - 1)
503  {
504  dxyRef1 = sampleLandmarks->GetMeasurementVector(1) -
505  sampleLandmarks->GetMeasurementVector(j);
506  dxyRef2 = sampleLandmarks->GetMeasurementVector(j-1) -
507  sampleLandmarks->GetMeasurementVector(j);
508  }
509  if ((j < numberOfLandmarks - 1) && (j > 1))
510  {
511  dxyRef1 = sampleLandmarks->GetMeasurementVector(j + 1) -
512  sampleLandmarks->GetMeasurementVector(j);
513  dxyRef2 = sampleLandmarks->GetMeasurementVector(j-1) -
514  sampleLandmarks->GetMeasurementVector(j);
515  }
516 
520  dx = dxyRef2[ 1 ] - dxyRef1[ 1 ];
521  dy = dxyRef1[ 0 ] - dxyRef2[ 0 ];
522 
523  ax = vcl_abs(dx) * 2;
524  ay = vcl_abs(dy) * 2;
525  if (dx < 0)
526  {
527  sx = -1;
528  }
529  else
530  {
531  sx = 1;
532  }
533  if (dy < 0)
534  {
535  sy = -1;
536  }
537  else
538  {
539  sy = 1;
540  }
541  count = 1;
542  if (ax > ay)
543  {
544  d = ay - ax/2;
545  while (count)
546  {
547  if (d >= 0)
548  {
549  posRight[ 1 ] = posRight[ 1 ] + sy;
550  posLeft[ 1 ] = posLeft[ 1 ] - sy;
551  d = d - ax;
552  }
553  posRight[ 0 ] = posRight[ 0 ] + sx;
554  posLeft[ 0 ] = posLeft[ 0 ] - sx;
555  d = d + ay;
556  position3D [ 0 ] = posRight [ 0 ];
557  position3D [ 1 ] = posRight[ 1 ];
558  position3D [ 2 ] = i;
559  constIterator.SetIndex(position3D);
560  if (constIterator.Get())
561  {
562  count = 0;
563  v.push_back (posRight[ 0 ]);
564  v.push_back (posRight[ 1 ]);
565  }
566  else
567  {
568  position3D [ 0 ] = posLeft [ 0 ];
569  position3D [ 1 ] = posLeft[ 1 ];
570  position3D [ 2 ] = i;
571  constIterator.SetIndex(position3D);
572  if (constIterator.Get())
573  {
574  count = 0;
575  v.push_back (posLeft[ 0 ]);
576  v.push_back (posLeft[ 1 ]);
577  }
578  }
579  }
580  }
581  else
582  {
583  d = ax - ay/2;
584  while (count)
585  {
586  if (d >= 0)
587  {
588  posRight[ 0 ] = posRight[ 0 ] + sx;
589  posLeft[ 0 ] = posLeft[ 0 ] - sx;
590  d = d - ay;
591  }
592  posRight[ 1 ] = posRight[ 1 ] + sy;
593  posLeft[ 1 ] = posLeft[ 1 ] - sy;
594  d = d + ax;
595  position3D [ 0 ] = posRight [ 0 ];
596  position3D [ 1 ] = posRight[ 1 ];
597  position3D [ 2 ] = i;
598  constIterator.SetIndex(position3D);
599  if (constIterator.Get())
600  {
601  count = 0;
602  v.push_back (posRight[ 0 ]);
603  v.push_back (posRight[ 1 ]);
604  }
605  else
606  {
607  position3D [ 0 ] = posLeft [ 0 ];
608  position3D [ 1 ] = posLeft[ 1 ];
609  position3D [ 2 ] = i;
610  constIterator.SetIndex(position3D);
611  if (constIterator.Get())
612  {
613  count = 0;
614  v.push_back (posLeft[ 0 ]);
615  v.push_back (posLeft[ 1 ]);
616  }
617  }
618  }
619  }
620  }
621  }
622  unsigned int row = 0;
623  for (p6 = v.begin(); p6 != v.end(); p6++)
624  {
625  coordLandmarks[row][i] = (*p6);
626  row++;
627  }
628  v.erase(v.begin(), v.end());
629  }
630 
631  for( i = 0; i < slices; i++)
632  {
633  for( j = 0; j < 2*numberOfLandmarks; j++)
634  {
635  m_Means[j] += coordLandmarks[j][i];
636  }
637  }
638  m_Means /= slices;
639 
640 
641  for(i = 0; i < 2*numberOfLandmarks; i++)
642  {
643  for(j = 0; j < 2*numberOfLandmarks; j++)
644  {
645  for(unsigned int k = 0; k < slices; k++)
646  {
647  covarianceMatrix[i][j] += (coordLandmarks[i][k] - m_Means[i]) * (coordLandmarks[j][k] - m_Means[j]);
648  }
649  }
650  }
651  if( ( slices - 1 ) != 0 )
652  {
653  covarianceMatrix /= ( slices - 1 );
654  }
655  else
656  {
657  covarianceMatrix.fill(0);
658  }
659 
660  vnl_generalized_eigensystem eigenVectors_eigenValues(covarianceMatrix, identityMatrix);
661 
662  MatrixOfDoubleType eigenVectorsFull = eigenVectors_eigenValues.V;
663  VectorOfDoubleType eigenValuesFull = (eigenVectors_eigenValues.D).diagonal();
664  eigenValuesFull.flip();
665  double maxVariance = 0.98 * eigenValuesFull.sum();
666  count = 0;
667  double temp = 0;
668  while (temp < maxVariance)
669  {
670  temp += eigenValuesFull[count];
671  count++;
672  }
673  m_EigenVectors.set_size(eigenVectorsFull.rows(),count);
674  m_EigenVectors = eigenVectorsFull.extract (eigenVectorsFull.rows(),count, 0, 0);
675  m_EigenValues.set_size(count);
676  m_EigenValues = eigenValuesFull.extract(count,0);
677 
678  /* *
679  * Remember that the moments are valid
680  */
681  m_Valid = 1;
682 
683 }
684 
685 /* *
686  * Get the mean shape
687  */
688 template<class TImage>
692 {
693  if (!m_Valid)
694  {
695  throw InvalidActiveShapeModeError(__FILE__, __LINE__);
696  }
697  return m_Means;
698 }
699 
703 template<class TImage>
707 {
708  if (!m_Valid)
709  {
710  throw InvalidActiveShapeModeError(__FILE__, __LINE__);
711  }
712  return m_EigenValues;
713 }
714 
718 template<class TImage>
722 {
723  if (!m_Valid)
724  {
725  throw InvalidActiveShapeModeError(__FILE__, __LINE__);
726  }
727  return m_EigenVectors;
728 }
729 
730 } // end namespace itk
731 
732 #endif

Generated at Sat Feb 2 2013 23:23:08 for Orfeo Toolbox with doxygen 1.8.1.1