OTB  10.0.0
Orfeo Toolbox
otbScalarImageToTexturesFilter.hxx
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2005-2024 Centre National d'Etudes Spatiales (CNES)
3  *
4  * This file is part of Orfeo Toolbox
5  *
6  * https://www.orfeo-toolbox.org/
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 
22 #ifndef otbScalarImageToTexturesFilter_hxx
23 #define otbScalarImageToTexturesFilter_hxx
24 
26 #include "itkImageRegionIteratorWithIndex.h"
27 #include "itkConstNeighborhoodIterator.h"
28 #include "itkImageRegionIterator.h"
29 #include "itkProgressReporter.h"
30 #include "itkNumericTraits.h"
31 #include <vector>
32 #include <cmath>
33 
34 namespace otb
35 {
36 template <class TInputImage, class TOutputImage>
38  : m_Radius(),
39  m_Offset(),
40  m_NeighborhoodRadius(),
41  m_NumberOfBinsPerAxis(8),
42  m_InputImageMinimum(0),
43  m_InputImageMaximum(255),
44  m_SubsampleFactor(),
45  m_SubsampleOffset()
46 {
47  // There are 8 outputs corresponding to the 8 textures indices
48  this->SetNumberOfRequiredOutputs(8);
49 
50  // Create the 8 outputs
51  this->SetNthOutput(0, OutputImageType::New());
52  this->SetNthOutput(1, OutputImageType::New());
53  this->SetNthOutput(2, OutputImageType::New());
54  this->SetNthOutput(3, OutputImageType::New());
55  this->SetNthOutput(4, OutputImageType::New());
56  this->SetNthOutput(5, OutputImageType::New());
57  this->SetNthOutput(6, OutputImageType::New());
58  this->SetNthOutput(7, OutputImageType::New());
59 
60  this->m_SubsampleFactor.Fill(1);
61  this->m_SubsampleOffset.Fill(0);
62 }
63 
64 template <class TInputImage, class TOutputImage>
66 {
67 }
68 
69 template <class TInputImage, class TOutputImage>
71 {
72  if (this->GetNumberOfOutputs() < 1)
73  {
74  return nullptr;
75  }
76  return static_cast<OutputImageType*>(this->GetOutput(0));
77 }
78 
79 template <class TInputImage, class TOutputImage>
81 {
82  if (this->GetNumberOfOutputs() < 2)
83  {
84  return nullptr;
85  }
86  return static_cast<OutputImageType*>(this->GetOutput(1));
87 }
88 
89 template <class TInputImage, class TOutputImage>
91 {
92  if (this->GetNumberOfOutputs() < 3)
93  {
94  return nullptr;
95  }
96  return static_cast<OutputImageType*>(this->GetOutput(2));
97 }
98 
99 template <class TInputImage, class TOutputImage>
102 {
103  if (this->GetNumberOfOutputs() < 4)
104  {
105  return nullptr;
106  }
107  return static_cast<OutputImageType*>(this->GetOutput(3));
108 }
109 
110 template <class TInputImage, class TOutputImage>
112 {
113  if (this->GetNumberOfOutputs() < 5)
114  {
115  return nullptr;
116  }
117  return static_cast<OutputImageType*>(this->GetOutput(4));
118 }
119 
120 template <class TInputImage, class TOutputImage>
123 {
124  if (this->GetNumberOfOutputs() < 6)
125  {
126  return nullptr;
127  }
128  return static_cast<OutputImageType*>(this->GetOutput(5));
129 }
130 
131 template <class TInputImage, class TOutputImage>
134 {
135  if (this->GetNumberOfOutputs() < 7)
136  {
137  return nullptr;
138  }
139  return static_cast<OutputImageType*>(this->GetOutput(6));
140 }
141 
142 template <class TInputImage, class TOutputImage>
145 {
146  if (this->GetNumberOfOutputs() < 8)
147  {
148  return nullptr;
149  }
150  return static_cast<OutputImageType*>(this->GetOutput(7));
151 }
152 
153 template <class TInputImage, class TOutputImage>
155 {
156  // First, call superclass implementation
157  Superclass::GenerateOutputInformation();
158 
159  // Compute output size, origin & spacing
160  InputRegionType inputRegion = this->GetInput()->GetLargestPossibleRegion();
161  OutputRegionType outputRegion;
162  outputRegion.SetIndex(0, 0);
163  outputRegion.SetIndex(1, 0);
164  outputRegion.SetSize(0, 1 + (inputRegion.GetSize(0) - 1 - m_SubsampleOffset[0]) / m_SubsampleFactor[0]);
165  outputRegion.SetSize(1, 1 + (inputRegion.GetSize(1) - 1 - m_SubsampleOffset[1]) / m_SubsampleFactor[1]);
166 
167  typename OutputImageType::SpacingType outSpacing = this->GetInput()->GetSignedSpacing();
168  outSpacing[0] *= m_SubsampleFactor[0];
169  outSpacing[1] *= m_SubsampleFactor[1];
170 
171  typename OutputImageType::PointType outOrigin;
172  this->GetInput()->TransformIndexToPhysicalPoint(inputRegion.GetIndex() + m_SubsampleOffset, outOrigin);
173 
174  for (unsigned int i = 0; i < this->GetNumberOfOutputs(); i++)
175  {
176  OutputImagePointerType outputPtr = this->GetOutput(i);
177  outputPtr->SetLargestPossibleRegion(outputRegion);
178  outputPtr->SetOrigin(outOrigin);
179  outputPtr->SetSignedSpacing(outSpacing);
180  }
181 }
182 
183 template <class TInputImage, class TOutputImage>
185 {
186  // First, call superclass implementation
187  Superclass::GenerateInputRequestedRegion();
188 
189  // Retrieve the input and output pointers
190  InputImagePointerType inputPtr = const_cast<InputImageType*>(this->GetInput());
191  OutputImagePointerType outputPtr = this->GetOutput();
192 
193  if (!inputPtr || !outputPtr)
194  {
195  return;
196  }
197 
198  // Retrieve the output requested region
199  // We use only the first output since requested regions for all outputs are enforced to be equal
200  // by the default GenerateOutputRequestedRegiont() implementation
201  OutputRegionType outputRequestedRegion = outputPtr->GetRequestedRegion();
202 
203  typename OutputRegionType::IndexType outputIndex = outputRequestedRegion.GetIndex();
204  typename OutputRegionType::SizeType outputSize = outputRequestedRegion.GetSize();
205  typename InputRegionType::IndexType inputIndex;
206  typename InputRegionType::SizeType inputSize;
207  InputRegionType inputLargest = inputPtr->GetLargestPossibleRegion();
208 
209  // Convert index and size to full grid
210  outputIndex[0] = outputIndex[0] * m_SubsampleFactor[0] + m_SubsampleOffset[0] + inputLargest.GetIndex(0);
211  outputIndex[1] = outputIndex[1] * m_SubsampleFactor[1] + m_SubsampleOffset[1] + inputLargest.GetIndex(1);
212  outputSize[0] = 1 + (outputSize[0] - 1) * m_SubsampleFactor[0];
213  outputSize[1] = 1 + (outputSize[1] - 1) * m_SubsampleFactor[1];
214 
215  // First, apply offset
216  for (unsigned int dim = 0; dim < InputImageType::ImageDimension; ++dim)
217  {
218  inputIndex[dim] = std::min(outputIndex[dim], outputIndex[dim] + m_Offset[dim]);
219  inputSize[dim] = std::max(outputIndex[dim] + outputSize[dim], outputIndex[dim] + outputSize[dim] + m_Offset[dim]) - inputIndex[dim];
220  }
221 
222  // Build the input requested region
223  InputRegionType inputRequestedRegion;
224  inputRequestedRegion.SetIndex(inputIndex);
225  inputRequestedRegion.SetSize(inputSize);
226 
227  // Apply the radius
228  inputRequestedRegion.PadByRadius(m_Radius);
229 
230  // Try to apply the requested region to the input image
231  if (inputRequestedRegion.Crop(inputPtr->GetLargestPossibleRegion()))
232  {
233  inputPtr->SetRequestedRegion(inputRequestedRegion);
234  }
235  else
236  {
237  // Build an exception
238  itk::InvalidRequestedRegionError e(__FILE__, __LINE__);
239  e.SetLocation(ITK_LOCATION);
240  e.SetDescription("Requested region is (at least partially) outside the largest possible region.");
241  e.SetDataObject(inputPtr);
242  throw e;
243  }
244 }
245 
246 template <class TInputImage, class TOutputImage>
248 {
249  unsigned int minRadius = 0;
250  for (unsigned int i = 0; i < m_Offset.GetOffsetDimension(); i++)
251  {
252  unsigned int distance = std::abs(m_Offset[i]);
253  if (distance > minRadius)
254  {
255  minRadius = distance;
256  }
257  }
258  m_NeighborhoodRadius.Fill(minRadius);
259 }
260 
261 template <class TInputImage, class TOutputImage>
263 {
264  // Retrieve the input and output pointers
265  InputImagePointerType inputPtr = const_cast<InputImageType*>(this->GetInput());
266  OutputImagePointerType energyPtr = this->GetEnergyOutput();
267  OutputImagePointerType entropyPtr = this->GetEntropyOutput();
268  OutputImagePointerType correlationPtr = this->GetCorrelationOutput();
269  OutputImagePointerType invDiffMomentPtr = this->GetInverseDifferenceMomentOutput();
270  OutputImagePointerType inertiaPtr = this->GetInertiaOutput();
271  OutputImagePointerType clusterShadePtr = this->GetClusterShadeOutput();
272  OutputImagePointerType clusterProminencePtr = this->GetClusterProminenceOutput();
273  OutputImagePointerType haralickCorPtr = this->GetHaralickCorrelationOutput();
274 
275  // Build output iterators
276  itk::ImageRegionIteratorWithIndex<OutputImageType> energyIt(energyPtr, outputRegionForThread);
277  itk::ImageRegionIterator<OutputImageType> entropyIt(entropyPtr, outputRegionForThread);
278  itk::ImageRegionIterator<OutputImageType> correlationIt(correlationPtr, outputRegionForThread);
279  itk::ImageRegionIterator<OutputImageType> invDiffMomentIt(invDiffMomentPtr, outputRegionForThread);
280  itk::ImageRegionIterator<OutputImageType> inertiaIt(inertiaPtr, outputRegionForThread);
281  itk::ImageRegionIterator<OutputImageType> clusterShadeIt(clusterShadePtr, outputRegionForThread);
282  itk::ImageRegionIterator<OutputImageType> clusterProminenceIt(clusterProminencePtr, outputRegionForThread);
283  itk::ImageRegionIterator<OutputImageType> haralickCorIt(haralickCorPtr, outputRegionForThread);
284 
285  // Go to begin
286  energyIt.GoToBegin();
287  entropyIt.GoToBegin();
288  correlationIt.GoToBegin();
289  invDiffMomentIt.GoToBegin();
290  inertiaIt.GoToBegin();
291  clusterShadeIt.GoToBegin();
292  clusterProminenceIt.GoToBegin();
293  haralickCorIt.GoToBegin();
294 
295  const double log2 = std::log(2.0);
296 
297  InputRegionType inputLargest = inputPtr->GetLargestPossibleRegion();
298 
299  // Iterate on outputs to compute textures
300  while (!energyIt.IsAtEnd() && !entropyIt.IsAtEnd() && !correlationIt.IsAtEnd() && !invDiffMomentIt.IsAtEnd() && !inertiaIt.IsAtEnd() &&
301  !clusterShadeIt.IsAtEnd() && !clusterProminenceIt.IsAtEnd() && !haralickCorIt.IsAtEnd())
302  {
303  // Compute the region on which co-occurence will be estimated
304  typename InputRegionType::IndexType inputIndex;
305  typename InputRegionType::SizeType inputSize;
306 
307  // Convert index to full grid
308  typename OutputImageType::IndexType outIndex;
309 
310  // First, create an window for neighborhood iterator based on m_Radius
311  // For example, if xradius and yradius is 2. window size is 5x5 (2 * radius + 1).
312  for (unsigned int dim = 0; dim < InputImageType::ImageDimension; ++dim)
313  {
314  outIndex[dim] = energyIt.GetIndex()[dim] * m_SubsampleFactor[dim] + m_SubsampleOffset[dim] + inputLargest.GetIndex(dim);
315  inputIndex[dim] = outIndex[dim] - m_Radius[dim];
316  inputSize[dim] = 2 * m_Radius[dim] + 1;
317  }
318 
319  // Build the input region
320  InputRegionType inputRegion;
321  inputRegion.SetIndex(inputIndex);
322  inputRegion.SetSize(inputSize);
323  inputRegion.Crop(inputPtr->GetRequestedRegion());
324 
325  CooccurrenceIndexedListPointerType GLCIList = CooccurrenceIndexedListType::New();
326  GLCIList->Initialize(m_NumberOfBinsPerAxis, m_InputImageMinimum, m_InputImageMaximum);
327 
328  typedef itk::ConstNeighborhoodIterator<InputImageType> NeighborhoodIteratorType;
329  NeighborhoodIteratorType neighborIt;
330  neighborIt = NeighborhoodIteratorType(m_NeighborhoodRadius, inputPtr, inputRegion);
331  for (neighborIt.GoToBegin(); !neighborIt.IsAtEnd(); ++neighborIt)
332  {
333  const InputPixelType centerPixelIntensity = neighborIt.GetCenterPixel();
334  bool pixelInBounds;
335  const InputPixelType pixelIntensity = neighborIt.GetPixel(m_Offset, pixelInBounds);
336  if (!pixelInBounds)
337  {
338  continue; // don't put a pixel in the co-occurrence list if the value is
339  // out of bounds
340  }
341  GLCIList->AddPixelPair(centerPixelIntensity, pixelIntensity);
342  }
343 
344  double pixelMean = 0.;
345  double marginalMean;
346  double marginalDevSquared = 0.;
347  double pixelVariance = 0.;
348 
349  // Create and Initialize marginalSums
350  std::vector<double> marginalSums(m_NumberOfBinsPerAxis, 0);
351 
352  // get co-occurrence vector and totalfrequency
353  VectorType glcVector = GLCIList->GetVector();
354  double totalFrequency = static_cast<double>(GLCIList->GetTotalFrequency());
355 
356  // Normalize the co-occurrence indexed list and compute mean, marginalSum
357  typename VectorType::iterator it = glcVector.begin();
358  while (it != glcVector.end())
359  {
360  double frequency = (*it).second / totalFrequency;
361  CooccurrenceIndexType index = (*it).first;
362  pixelMean += index[0] * frequency;
363  marginalSums[index[0]] += frequency;
364  ++it;
365  }
366 
367  /* Now get the mean and deviaton of the marginal sums.
368  Compute incremental mean and SD, a la Knuth, "The Art of Computer
369  Programming, Volume 2: Seminumerical Algorithms", section 4.2.2.
370  Compute mean and standard deviation using the recurrence relation:
371  M(1) = x(1), M(k) = M(k-1) + (x(k) - M(k-1) ) / k
372  S(1) = 0, S(k) = S(k-1) + (x(k) - M(k-1)) * (x(k) - M(k))
373  for 2 <= k <= n, then
374  sigma = std::sqrt(S(n) / n) (or divide by n-1 for sample SD instead of
375  population SD).
376  */
377  std::vector<double>::const_iterator msIt = marginalSums.begin();
378  marginalMean = *msIt;
379  // Increment iterator to start with index 1
380  ++msIt;
381  for (int k = 2; msIt != marginalSums.end(); ++k, ++msIt)
382  {
383  double M_k_minus_1 = marginalMean;
384  double S_k_minus_1 = marginalDevSquared;
385  double x_k = *msIt;
386  double M_k = M_k_minus_1 + (x_k - M_k_minus_1) / k;
387  double S_k = S_k_minus_1 + (x_k - M_k_minus_1) * (x_k - M_k);
388  marginalMean = M_k;
389  marginalDevSquared = S_k;
390  }
391  marginalDevSquared = marginalDevSquared / m_NumberOfBinsPerAxis;
392 
393  VectorConstIteratorType constVectorIt;
394  constVectorIt = glcVector.begin();
395  while (constVectorIt != glcVector.end())
396  {
397  RelativeFrequencyType frequency = (*constVectorIt).second / totalFrequency;
398  CooccurrenceIndexType index = (*constVectorIt).first;
399  pixelVariance += (index[0] - pixelMean) * (index[0] - pixelMean) * frequency;
400  ++constVectorIt;
401  }
402 
403  double pixelVarianceSquared = pixelVariance * pixelVariance;
404  // Variance is only used in correlation. If variance is 0, then (index[0] - pixelMean) * (index[1] - pixelMean)
405  // should be zero as well. In this case, set the variance to 1. in order to
406  // avoid NaN correlation.
407  if (pixelVarianceSquared < GetPixelValueTolerance())
408  {
409  pixelVarianceSquared = 1.;
410  }
411 
412  // Initialize texture variables;
413  PixelValueType energy = itk::NumericTraits<PixelValueType>::Zero;
414  PixelValueType entropy = itk::NumericTraits<PixelValueType>::Zero;
415  PixelValueType correlation = itk::NumericTraits<PixelValueType>::Zero;
416  PixelValueType inverseDifferenceMoment = itk::NumericTraits<PixelValueType>::Zero;
417  PixelValueType inertia = itk::NumericTraits<PixelValueType>::Zero;
418  PixelValueType clusterShade = itk::NumericTraits<PixelValueType>::Zero;
419  PixelValueType clusterProminence = itk::NumericTraits<PixelValueType>::Zero;
420  PixelValueType haralickCorrelation = itk::NumericTraits<PixelValueType>::Zero;
421 
422  // Compute textures
423  constVectorIt = glcVector.begin();
424  while (constVectorIt != glcVector.end())
425  {
426  CooccurrenceIndexType index = (*constVectorIt).first;
427  RelativeFrequencyType frequency = (*constVectorIt).second / totalFrequency;
428  energy += frequency * frequency;
429  entropy -= (frequency > GetPixelValueTolerance()) ? frequency * std::log(frequency) / log2 : 0;
430  correlation += ((index[0] - pixelMean) * (index[1] - pixelMean) * frequency) / pixelVarianceSquared;
431  inverseDifferenceMoment += frequency / (1.0 + (index[0] - index[1]) * (index[0] - index[1]));
432  inertia += (index[0] - index[1]) * (index[0] - index[1]) * frequency;
433  clusterShade += std::pow((index[0] - pixelMean) + (index[1] - pixelMean), 3) * frequency;
434  clusterProminence += std::pow((index[0] - pixelMean) + (index[1] - pixelMean), 4) * frequency;
435  haralickCorrelation += index[0] * index[1] * frequency;
436  ++constVectorIt;
437  }
438 
439  haralickCorrelation = (fabs(marginalDevSquared) > 1E-8) ? (haralickCorrelation - marginalMean * marginalMean) / marginalDevSquared : 0;
440 
441  // Fill outputs
442  energyIt.Set(energy);
443  entropyIt.Set(entropy);
444  correlationIt.Set(correlation);
445  invDiffMomentIt.Set(inverseDifferenceMoment);
446  inertiaIt.Set(inertia);
447  clusterShadeIt.Set(clusterShade);
448  clusterProminenceIt.Set(clusterProminence);
449  haralickCorIt.Set(haralickCorrelation);
450 
451  // Increment iterators
452  ++energyIt;
453  ++entropyIt;
454  ++correlationIt;
455  ++invDiffMomentIt;
456  ++inertiaIt;
457  ++clusterShadeIt;
458  ++clusterProminenceIt;
459  ++haralickCorIt;
460  }
461 }
462 
463 } // End namespace otb
464 
465 #endif
void DynamicThreadedGenerateData(const OutputRegionType &outputRegion) override
VectorType::const_iterator VectorConstIteratorType
CooccurrenceIndexedListType::VectorType VectorType
CooccurrenceIndexedListType::RelativeFrequencyType RelativeFrequencyType
CooccurrenceIndexedListType::PixelValueType PixelValueType
CooccurrenceIndexedListType::IndexType CooccurrenceIndexType
CooccurrenceIndexedListType::Pointer CooccurrenceIndexedListPointerType
The "otb" namespace contains all Orfeo Toolbox (OTB) classes.