OTB  10.0.0
Orfeo Toolbox
otbFastNLMeansImageFilter.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 #ifndef otbFastNLMeansImageFilter_hxx
22 #define otbFastNLMeansImageFilter_hxx
23 
25 #include "itkImageRegionIterator.h"
26 #include "itkImageRegionConstIterator.h"
27 #include "itkNumericTraits.h"
28 #include <vector>
29 #include <tuple>
30 
31 namespace otb
32 {
33  template<class TInputImage, class TOutputImage>
35  {
36  // Define default attributes values
37  m_HalfSearchSize.Fill(7);
38  m_HalfPatchSize.Fill(2);
39  m_Var = 0.;
40  m_CutoffDistance = 0.1;
41 
42  m_NormalizeDistance = m_CutoffDistance * m_CutoffDistance
43  * (2*m_HalfPatchSize[m_ROW]+1) * (2*m_HalfPatchSize[m_COL]+1);
44  }
45 
46  template<class TInputImage, class TOutputImage>
47  std::tuple< typename NLMeansFilter<TInputImage, TOutputImage>::InRegionType,
48  int, int, int, int, bool>
50  (const OutRegionType& outputRegion) const
51  {
52  InImageConstPointerType inputPtr = this->GetInput();
53  auto const& inputSize = inputPtr->GetLargestPossibleRegion().GetSize();
54 
55  // Get output region specification
56  auto const& outIndex = outputRegion.GetIndex();
57  auto const& outSize = outputRegion.GetSize();
58 
59  // Define margin for processing
60  const InSizeType halfMargin = m_HalfSearchSize + m_HalfPatchSize;
61  const InSizeType sizeTwo = {{2,2}};
62  const InSizeType fullMargin = sizeTwo*halfMargin;
63 
64  // Define region to read
65  InIndexType inIndex = outIndex - halfMargin;
66  InSizeType requestedSize = outSize + fullMargin;
67 
68  // Initialize parameters for mirror padding
69  bool needMirrorPadding = false;
70  int mirrorFirstRow = 0;
71  int mirrorFirstCol = 0;
72  int mirrorLastRow = 0;
73  int mirrorLastCol = 0;
74  // Check that the requested region is inside image boundaries
75  // If not, store number of missing data and update region
76  if (inIndex[m_COL] < 0){
77  needMirrorPadding = true;
78  mirrorFirstCol = -inIndex[m_COL];
79  inIndex[m_COL] = 0;
80  requestedSize[m_COL] -= mirrorFirstCol;
81  }
82  if (inIndex[m_ROW] < 0){
83  needMirrorPadding = true;
84  mirrorFirstRow = -inIndex[m_ROW];
85  inIndex[m_ROW] = 0;
86  requestedSize[m_ROW] -= mirrorFirstRow;
87  }
88  unsigned int lastCol = inIndex[m_COL] + requestedSize[m_COL];
89  if (lastCol >= inputSize[m_COL]){
90  needMirrorPadding = true;
91  mirrorLastCol = lastCol - inputSize[m_COL];
92  requestedSize[m_COL] -= mirrorLastCol;
93  }
94  unsigned int lastRow = inIndex[m_ROW] + requestedSize[m_ROW];
95  if (lastRow >= inputSize[m_ROW]){
96  needMirrorPadding = true;
97  mirrorLastRow = lastRow - inputSize[m_ROW];
98  requestedSize[m_ROW] -= mirrorLastRow;
99  }
100 
101  InRegionType inRequestedRegion(inIndex, requestedSize);
102  return std::make_tuple(inRequestedRegion, mirrorFirstRow, mirrorFirstCol,
103  mirrorLastRow, mirrorLastCol, needMirrorPadding);
104  }
105 
106  template <class TInputImage, class TOutputImage>
109  {
110  // Call the superclass' implementation of this method
111  Superclass::GenerateInputRequestedRegion();
112 
113  auto const& outputRequestedRegion = this->GetOutput()->GetRequestedRegion();
114  auto regionAndMirror = this->OutputRegionToInputRegion(outputRequestedRegion);
115  InRegionType inRequestedRegion = std::get<0>(regionAndMirror);
116 
117  InImageType * inputPtr = const_cast<InImageType * >(this->GetInput());
118  inputPtr->SetRequestedRegion(inRequestedRegion);
119  }
120 
121  template<class TInputImage, class TOutputImage>
122  void
124  (const OutRegionType& outputRegionForThread)
125  {
126  InImageConstPointerType inputPtr = this->GetInput();
127  auto regionAndMirror = OutputRegionToInputRegion(outputRegionForThread);
128  // Unpack all values returned
129  InRegionType inputRegionForThread = std::get<0>(regionAndMirror);
130  int mirrorFirstRow = std::get<1>(regionAndMirror);
131  int mirrorFirstCol = std::get<2>(regionAndMirror);
132  int mirrorLastRow = std::get<3>(regionAndMirror);
133  int mirrorLastCol = std::get<4>(regionAndMirror);
134  bool needMirror = std::get<5>(regionAndMirror);
135 
136  // initialize and allocate vector to store temporary output values
137  // It makes it easier to store them in vectors to access various non-contiguous locations
138  auto const& outSize = outputRegionForThread.GetSize();
139  std::vector<double> outTemp(outSize[m_ROW]*outSize[m_COL]);
140  // initialize and allocate buffer to store all weights
141  std::vector<double> weights(outSize[m_ROW]*outSize[m_COL]);
142 
143  typedef itk::ImageRegionConstIterator<InImageType> InIteratorType;
144  InIteratorType inIt(inputPtr, inputRegionForThread);
145  auto const& inputSize = inputRegionForThread.GetSize();
146 
147  auto mirrorCol = inputSize[m_COL] + mirrorFirstCol + mirrorLastCol;
148  auto mirrorRow = inputSize[m_ROW] + mirrorFirstRow + mirrorLastRow;
149  InSizeType const& mirrorSize = {{mirrorCol, mirrorRow}};
150 
151  std::vector<double> dataInput(mirrorSize[m_ROW]*mirrorSize[m_COL]);
152  inIt.GoToBegin();
153  for (unsigned int row=static_cast<unsigned int>(mirrorFirstRow);
154  row<static_cast<unsigned int>(mirrorFirstRow)+inputSize[m_ROW]; row++)
155  for (unsigned int col=static_cast<unsigned int>(mirrorFirstCol);
156  col<static_cast<unsigned int>(mirrorFirstCol)+inputSize[m_COL]; col++)
157  {
158  auto index = row * mirrorSize[m_COL] + col;
159  dataInput[index] = static_cast<double>(inIt.Get());
160  ++inIt;
161  }
162 
163  if (needMirror)
164  {
165  // Perform mirror on upper lines
166  for (int row=0; row<mirrorFirstRow; row++)
167  {
168  int lineToCopy = (2*mirrorFirstRow - row)*mirrorSize[m_COL];
169  std::copy(dataInput.begin() + lineToCopy,
170  dataInput.begin() + lineToCopy + mirrorSize[m_COL],
171  dataInput.begin() + row*mirrorSize[m_COL] );
172  }
173  // Perform mirror on lower lines
174  int lastRowRead = mirrorFirstRow+inputSize[m_ROW];
175  for (int row=0; row<mirrorLastRow; row++)
176  {
177  int lineToCopy = (lastRowRead - row -2)*mirrorSize[m_COL];
178  std::copy(dataInput.begin() + lineToCopy,
179  dataInput.begin() + lineToCopy + mirrorSize[m_COL],
180  dataInput.begin() + (lastRowRead + row)*mirrorSize[m_COL]);
181  }
182  // Perform mirror on left-hand columns
183  if (mirrorFirstCol > 0) {
184  for (unsigned int row=0; row<mirrorSize[m_ROW]; row++)
185  {
186  std::reverse_copy(dataInput.begin() + row*mirrorSize[m_COL] + mirrorFirstCol+1,
187  dataInput.begin() + row*mirrorSize[m_COL] +2*mirrorFirstCol+1,
188  dataInput.begin() + row*mirrorSize[m_COL]);
189  }
190 
191  }
192  // Perform mirror on right-hand columns
193  if (mirrorLastCol > 0){
194  for (unsigned int row=0; row<mirrorSize[m_ROW]; row++)
195  {
196  std::reverse_copy(dataInput.begin() + (row+1)*mirrorSize[m_COL] - 2*mirrorLastCol-1,
197  dataInput.begin() + (row+1)*mirrorSize[m_COL] - mirrorLastCol-1,
198  dataInput.begin() + (row+1)*mirrorSize[m_COL] - mirrorLastCol);
199  }
200  }
201  }
202 
203  // For loops on all shifts possible
204  int fullMarginRow = static_cast<int>(m_HalfSearchSize[m_ROW]+m_HalfPatchSize[m_ROW]);
205  int fullMarginCol = static_cast<int>(m_HalfSearchSize[m_COL]+m_HalfPatchSize[m_COL]);
206  int searchSizeRow = static_cast<int>(m_HalfSearchSize[m_ROW]);
207  int searchSizeCol = static_cast<int>(m_HalfSearchSize[m_COL]);
208  // Allocate integral image
209  const InSizeType sizeTwo = {{2,2}};
210  auto const& inSize = outSize + sizeTwo * m_HalfPatchSize;
211 
212  std::vector<double> imIntegral(inSize[m_ROW]*inSize[m_COL]);
213  for (int drow=-searchSizeRow; drow < searchSizeRow+1; drow++)
214  for (int dcol=-searchSizeCol; dcol < searchSizeCol+1; dcol++)
215  {
216  // Compute integral image for current shift (drow, dcol)
217  OutIndexType shift = {{dcol, drow}};
218  ComputeIntegralImage(dataInput, imIntegral, shift, inSize, mirrorSize);
219 
220  for(unsigned int row=0; row<outSize[m_ROW]; row++)
221  for (unsigned int col=0; col<outSize[m_COL]; col++)
222  {
223  // Compute distance from integral image for patch centered at
224  // (row, col) + (m_HalfPatchSize, m_HalfPatchSize)
225  OutPixelType distance = ComputeDistance(row, col, imIntegral, inSize[m_COL]);
226  if (distance < 5.0)
227  {
228  double weight = exp(static_cast<double>(-distance));
229  outTemp[row*outSize[m_COL] + col] += weight*dataInput[(row+drow+fullMarginRow)*mirrorSize[m_COL]
230  + col+dcol+fullMarginCol];
231  weights[row*outSize[m_COL] + col] += weight;
232  }
233  }
234  }
235 
236  // Normalize all results by dividing output by weights (store in output)
237  typedef itk::ImageRegionIterator<OutImageType> OutputIteratorType;
238  OutImagePointerType outputPtr = this->GetOutput();
239  OutputIteratorType outIt(outputPtr, outputRegionForThread);
240  outIt.GoToBegin();
241  for(unsigned int index=0; index<outSize[m_ROW]*outSize[m_COL]; index++)
242  {
243  outIt.Set(static_cast<OutPixelType>(outTemp[index]/weights[index]));
244  ++outIt;
245  }
246  }
247 
248  template<class TInputImage, class TOutputImage>
249  void
251  (const std::vector<double> & dataInput,
252  std::vector<double> &imIntegral,
253  const OutIndexType shift, const InSizeType sizeIntegral, const InSizeType sizeInput) const
254  {
255  // dataInput has a margin of m_HalfSearchSize+m_HalfPatchSize to allow
256  // computation of all shifts (computation of all integral images)
257  // integral images just have the m_HalfPatchSize margin necessary
258  // to compute patches differences for a given shift
259  // hence, the first point used in computation for the non-shifted image
260  // is located at m_HalfSearchSize
261  auto const& offsetRef = m_HalfSearchSize;
262  OutSizeType const& offsetShift = {{offsetRef[0] + shift[0], offsetRef[1] + shift[1]}};
263 
264  // Initialize integral image (compute position (0,0))
265  auto indexInput = offsetRef[m_ROW]*sizeInput[m_COL] + offsetRef[m_COL];
266  auto indexShift = offsetShift[m_ROW]*sizeInput[m_COL] + offsetShift[m_COL];
267  double diff = dataInput[indexInput] - dataInput[indexShift];
268  imIntegral[0] = (diff * diff) - m_Var;
269  // Compute first line of integral image
270  for (unsigned int col=1; col<sizeIntegral[m_COL]; col++)
271  {
272  auto indexInputCol = indexInput + col;
273  auto indexShiftCol = indexShift + col;
274  diff = dataInput[indexInputCol] - dataInput[indexShiftCol];
275  double distance = diff * diff - m_Var;
276  imIntegral[col] = distance + imIntegral[col-1];
277  assert(imIntegral[col] < itk::NumericTraits<double>::max());
278  }
279  // Compute first column of integral image
280  for (unsigned int row=1; row<sizeIntegral[m_ROW]; row++)
281  {
282  auto indexInputRow = indexInput + row*sizeInput[m_COL];
283  auto indexShiftRow = indexShift + row*sizeInput[m_COL];
284  diff = dataInput[indexInputRow] - dataInput[indexShiftRow];
285  double distance = diff * diff - m_Var;
286  imIntegral[row*sizeIntegral[m_COL]] = distance + imIntegral[(row-1)*sizeIntegral[m_COL]];
287  assert(imIntegral[row*sizeIntegral[m_COL]] < itk::NumericTraits<double>::max());
288  }
289  // All initializations have been done previously
290  // Remaining coefficients can be computed
291  for (unsigned int row=1; row<sizeIntegral[m_ROW]; row++)
292  for (unsigned int col=1; col<sizeIntegral[m_COL]; col++)
293  {
294  indexInput = (offsetRef[m_ROW]+row)*sizeInput[m_COL] + offsetRef[m_COL]+col;
295  indexShift = (offsetShift[m_ROW]+row)*sizeInput[m_COL] + offsetShift[m_COL]+col;
296  diff = dataInput[indexInput] - dataInput[indexShift];
297  double distance = diff*diff - m_Var;
298  imIntegral[row*sizeIntegral[m_COL] + col] = distance + imIntegral[row*sizeIntegral[m_COL] + col-1]
299  + imIntegral[(row-1)*sizeIntegral[m_COL] + col] - imIntegral[(row-1)*sizeIntegral[m_COL] + col-1];
300  assert(imIntegral[row*sizeIntegral[m_COL] + col] < itk::NumericTraits<double>::max());
301  }
302 
303  }
304 
305  template <class TInputImage, class TOutputImage>
307  NLMeansFilter<TInputImage, TOutputImage>::ComputeDistance
308  (const unsigned int row, const unsigned int col,
309  const std::vector<double>& imIntegral, const unsigned int nbCols) const
310  {
311  // (row, col) is the central position of the local window in the output image
312  // however, integral image is shifted by (m_HalfPatchSize, m_HalfPatchSize) compared to output image
313  // Thus, (row, col) corresponds, in integral image, to the upper left corner of the local window
314  double distance_patch =
315  imIntegral[(row+2*m_HalfPatchSize[m_ROW])*nbCols + col+2*m_HalfPatchSize[m_COL]]
316  - imIntegral[row*nbCols + col+2*m_HalfPatchSize[m_COL]]
317  - imIntegral[(row+2*m_HalfPatchSize[m_ROW])*nbCols + col]
318  + imIntegral[row*nbCols + col];
319 
320  distance_patch = std::max(distance_patch, 0.0) / (m_NormalizeDistance);
321  return static_cast<OutPixelType>(distance_patch);
322  }
323 
324  template<class TInputImage, class TOutputImage>
326  ::PrintSelf(std::ostream & os, itk::Indent indent) const
327  {
328  Superclass::PrintSelf(os, indent);
329 
330  os<<indent<<"NL Means Patch Size : "<<2*m_HalfPatchSize[m_ROW]+1
331  <<" x "<<2*m_HalfPatchSize[m_COL]+1<< std::endl;
332  os<<indent<<"NL Means Window Search Size : "<<2*m_HalfSearchSize[m_ROW]+1
333  <<" x "<<2*m_HalfSearchSize[m_COL]+1<< std::endl;
334  os<<indent<<"NL Means variance : "<<m_Var<<std::endl;
335  os<<indent<<"NL Means threshold for similarity : "<<m_CutoffDistance
336  << std::endl;
337  }
338 
339 } // end namespace otb
340 
341 #endif
This class implements a fast approximated version of NL Means denoising algorithm....
InImageType::SizeType InSizeType
std::tuple< InRegionType, int, int, int, int, bool > OutputRegionToInputRegion(const OutRegionType &outputRegion) const
OutImageType::PixelType OutPixelType
void DynamicThreadedGenerateData(const OutRegionType &outputRegionForThread) override
InImageType::RegionType InRegionType
InImageType::ConstPointer InImageConstPointerType
void GenerateInputRequestedRegion() override
OutImageType::RegionType OutRegionType
InImageType::IndexType InIndexType
OutImageType::Pointer OutImagePointerType
OutImageType::IndexType OutIndexType
void PrintSelf(std::ostream &os, itk::Indent indent) const override
The "otb" namespace contains all Orfeo Toolbox (OTB) classes.