OTB  10.0.0
Orfeo Toolbox
otbKullbackLeiblerProfileImageFilter.hxx
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2005-2024 Centre National d'Etudes Spatiales (CNES)
3  * Copyright (C) 2007-2012 Institut Mines Telecom / Telecom Bretagne
4  *
5  * This file is part of Orfeo Toolbox
6  *
7  * https://www.orfeo-toolbox.org/
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 
22 #ifndef otbKullbackLeiblerProfileImageFilter_hxx
23 #define otbKullbackLeiblerProfileImageFilter_hxx
24 
25 #include <vector>
26 
28 #include "otbMath.h"
29 #include "vcl_legacy_aliases.h"
30 
31 namespace otb
32 {
33 
34 /* *******************************************************************
35 * Class CumulantsForEdgeworthProfile
36 * ********************************************************************
37 */
38 template <class TInput>
39 CumulantsForEdgeworthProfile<TInput>::CumulantsForEdgeworthProfile(const TInput& input, std::vector<itk::Array2D<int>>& mask)
40 {
41  m_debug = MakeSumAndMoments(input, mask);
42  MakeCumulants();
43 }
44 
45 /* ===================== Kullback-Leibler Profile ==================== */
46 
47 template <class TInput>
48 template <class TInput2>
50 {
51  itk::VariableLengthVector<double> resu(fCum.size());
52 
53  Iterator iter1 = fCum.begin();
54  Iterator iter2 = cumulants.fCum.begin();
55 
56  for (unsigned int level = 0; level < resu.GetSize(); level++)
57  resu[level] = KL_profile((*iter1++), (*iter2++));
58 
59  return resu;
60 }
61 
62 /* =========== Kullback-Leibler divergence at a given scale ========== */
63 
64 template <class TInput>
66 {
67  double cum1 = cumulants1[0];
68  double cum2 = cumulants1[1];
69  double cum3 = cumulants1[2];
70  // double cum4 = cumulants1[3]; // unused
71 
72  double sqrt_cum2 = sqrt(cum2);
73  double cum2_3 = cum2 * cum2 * cum2;
74  double cum3_2 = cum3 * cum3;
75 
76  double tilde_cum1 = cumulants2[0];
77  double tilde_cum2 = cumulants2[1];
78  double tilde_cum3 = cumulants2[2];
79  double tilde_cum4 = cumulants2[3];
80 
81  double tilde_cum2_2 = cum2 * cum2;
82  double tilde_cum2_3 = cum2 * tilde_cum2_2;
83  double tilde_cum2_6 = tilde_cum2_3 * tilde_cum2_3;
84  double tilde_cum3_2 = tilde_cum3 * tilde_cum3;
85 
86  double beta = sqrt_cum2 / tilde_cum2;
87  double alpha = (cum1 - tilde_cum1) / tilde_cum2;
88 
89  double alpha_2 = alpha * alpha;
90  double alpha_4 = alpha_2 * alpha_2;
91  double alpha_6 = alpha_2 * alpha_4;
92 
93  double beta_2 = beta * beta;
94  double beta_4 = beta_2 * beta_2;
95  double beta_6 = beta_2 * beta_4;
96 
97  double c2 = alpha_2 + beta_2;
98  double c3 = alpha * (alpha_2 + 3.0 * beta_2);
99  double c4 = alpha_4 + 6.0 * alpha_2 * beta_2 + 3.0 * beta_4;
100  double c6 = alpha_6 + 15.0 * alpha_4 * beta_2 + 45.0 * alpha_2 * beta_4 + 15.0 * beta_6;
101 
102  double a1 = c3 - 3.0 * alpha / tilde_cum2;
103  double a2 = c4 - 6.0 * c2 / tilde_cum2 + 3.0 / tilde_cum2_2; // Watch for tilde_cum2_2 mistake in the article!
104  double a3 = c6 - 15.0 * c4 / tilde_cum2 + 45.0 * c2 / tilde_cum2_2 - 15.0 / tilde_cum2_3;
105 
106  double tmp = cum1 - tilde_cum1 + sqrt_cum2;
107  double resu = cum3_2 / (12.0 * cum2_3) + (log(tilde_cum2 / cum2) - 1.0 + tmp * tmp / tilde_cum2) / 2.0 -
108  (tilde_cum3 * a1 / 6.0 + tilde_cum4 * a2 / 24.0 + tilde_cum3_2 * a3 / 72.0) -
109  tilde_cum3_2 * (c6 - 6.0 * c4 / cum2 + 9.0 * c2 / tilde_cum2_2) / 72.0 -
110  10.0 * cum3 * tilde_cum3 * (cum1 - tilde_cum1) * (cum2 - tilde_cum2) / tilde_cum2_6;
111 
112  if (vnl_math_isnan(resu) || resu > 1e3)
113  resu = 1e3;
114 
115  return resu < 0.0 ? 0.0 : resu;
116 }
117 
118 /* ====== Moments estimation from nested neighborhoods ==== */
119 
120 template <class TInput>
121 int CumulantsForEdgeworthProfile<TInput>::MakeSumAndMoments(const TInput& input, std::vector<itk::Array2D<int>>& mask)
122 {
123  fMu.resize(mask.size());
124  std::vector<itk::Array2D<int>>::iterator iter = mask.begin();
125 
126  if (InitSumAndMoments(input, (*iter++)))
127  return 1;
128 
129  for (unsigned int level = 1; level < mask.size(); level++)
130  if (ReInitSumAndMoments(input, (*iter++), level))
131  return 1;
132 
133  return 0;
134 }
135 
136 /* ===================== Moments estimation ====================== */
137 /* =============== from the small window size =========== */
138 
139 template <class TInput>
140 int CumulantsForEdgeworthProfile<TInput>::InitSumAndMoments(const TInput& input, itk::Array2D<int>& mask)
141 {
142  fSum0 = fSum1 = fSum2 = fSum3 = fSum4 = 0.0;
143  fMu[0].Fill(0.0);
144 
145  unsigned int i, j;
146  unsigned long k = 0;
147  double pixel, pixel_2;
148 
149  // for ( unsigned long i = 0; i < input.Size(); ++i )
150  for (i = 0; i < mask.rows(); ++i)
151  {
152  for (j = 0; j < mask.cols(); ++j)
153  {
154  // std::cerr << "(" << i << "," << j << ") k=" << k << " ";
155  if (mask.get(i, j) == 1)
156  {
157  pixel = static_cast<double>(input.GetPixel(k));
158  pixel_2 = pixel * pixel;
159 
160  fSum0 += 1.0;
161  fSum1 += pixel;
162  fSum2 += pixel_2;
163  fSum3 += pixel_2 * pixel;
164  fSum4 += pixel_2 * pixel_2;
165  // std::cerr << "*\n";
166  }
167  ++k;
168  }
169  }
170  if (fSum0 == 0.0)
171  {
172  fDataAvailable = false;
173  return 1;
174  }
175 
176  double mu1, mu2;
177 
178  mu1 = fSum1 / fSum0;
179  mu2 = fSum2 / fSum0 - mu1 * mu1;
180 
181  if (mu2 == 0.0)
182  {
183  fDataAvailable = false;
184  return 1;
185  }
186 
187  double sigma = sqrt(mu2);
188 
189  itk::VariableLengthVector<double> tab(input.Size());
190  double* x = const_cast<double*>(tab.GetDataPointer());
191  for (unsigned long cp = 0; cp < input.Size(); ++cp)
192  *x++ = (static_cast<double>(input.GetPixel(cp)) - mu1) / sigma;
193 
194  double mu3 = 0.0;
195  double mu4 = 0.0;
196  x = const_cast<double*>(tab.GetDataPointer());
197 
198  // for ( unsigned long i = 0; i < input.Size(); ++i )
199  for (i = 0; i < mask.rows(); ++i)
200  {
201  for (j = 0; j < mask.cols(); ++j)
202  {
203  if (mask.get(i, j) == 1)
204  {
205  pixel = *x++;
206  pixel_2 = pixel * pixel;
207 
208  mu3 += pixel * pixel_2;
209  mu4 += pixel_2 * pixel_2;
210  }
211  else
212  ++x;
213  }
214  }
215 
216  mu3 /= fSum0;
217  mu4 /= fSum0;
218 
219  if (vnl_math_isnan(mu3) || vnl_math_isnan(mu4))
220  {
221  fDataAvailable = false;
222  return 1;
223  }
224 
225  fMu[0][0] = mu1;
226  fMu[0][1] = mu2;
227  fMu[0][2] = mu3;
228  fMu[0][3] = mu4;
229 
230  fDataAvailable = true;
231 
232  return 0;
233 }
234 
235 /* ================ Window size growth ============ */
236 
237 template <class TInput>
238 int CumulantsForEdgeworthProfile<TInput>::ReInitSumAndMoments(const TInput& input, itk::Array2D<int>& mask, int level)
239 {
240  fMu[level].Fill(0.0);
241  // mise a jour du comptage...
242  double sum0 = 0.0, sum1 = 0.0, sum2 = 0.0, sum3 = 0.0, sum4 = 0.0;
243 
244  double pixel, pixel_2;
245 
246  unsigned int i, j;
247  unsigned long k = 0L;
248 
249  // for ( unsigned long i = 0; i < input.Size(); ++i )
250  for (i = 0; i < mask.rows(); ++i)
251  {
252  for (j = 0; j < mask.cols(); ++j)
253  {
254  if (mask.get(i, j) == 1)
255  {
256  pixel = static_cast<double>(input.GetPixel(k));
257  pixel_2 = pixel * pixel;
258 
259  sum0 += 1.0;
260  sum1 += pixel;
261  sum2 += pixel_2;
262  sum3 += pixel * pixel_2;
263  sum4 += pixel_2 * pixel_2;
264  }
265  ++k;
266  }
267  }
268 
269  fSum0 += sum0;
270  fSum1 += sum1;
271  fSum2 += sum2;
272  fSum3 += sum3;
273  fSum4 += sum4;
274 
275  double mu = fSum1 / fSum0;
276  double mu_2 = mu * mu;
277  double mu_3 = mu_2 * mu;
278  double mu_4 = mu_2 * mu_2;
279 
280  fMu[level][0] = mu;
281  fMu[level][1] = fSum2 / fSum0 - mu_2;
282 
283  double sigma = sqrt(fSum2);
284  double sigma_2 = fSum2;
285  double sigma_3 = sigma * sigma_2;
286  double sigma_4 = sigma_2 * sigma_2;
287 
288  fMu[level][2] = (fSum3 - 3.0 * mu * fSum2 + 3.0 * mu_2 * fSum1 - fSum0 * mu_3) / (sigma_3 * fSum0);
289  fMu[level][3] = (fSum4 - 4.0 * mu * fSum3 + 6.0 * mu_2 * fSum2 - 4.0 * mu_3 * fSum1 + fSum0 * mu_4) / (sigma_4 * fSum0);
290 
291  return 0;
292 }
293 
294 /* =========== transformation moment -> cumulants ==================== */
295 
296 template <class TInput>
298 {
299  if (!IsDataAvailable())
300  return 1;
301 
302  fCum.resize(fMu.size());
303  fCum = fMu;
304 
305  for (unsigned int i = 0; i < fCum.size(); ++i)
306  fCum[i][3] -= 3.0;
307 
308  return 0;
309 }
310 
311 /* *******************************************************************
312 *
313 * Functor
314 *
315 * ********************************************************************
316 */
317 
318 namespace Functor
319 {
320 
321 template <class TInput1, class TInput2, class TOutput>
323 {
324  m_RadiusMin = 1;
325  m_RadiusMax = 3;
326 }
327 
328 /* =========== Gives the radius min and max of neighborhood ========== */
329 
330 template <class TInput1, class TInput2, class TOutput>
331 void KullbackLeiblerProfile<TInput1, TInput2, TOutput>::SetRadius(const unsigned char& min, const unsigned char& max)
332 {
333  m_RadiusMin = min < max ? min : max;
334  m_RadiusMax = max > min ? max : min;
335  MakeMultiscaleProfile();
336 }
337 
338 template <class TInput1, class TInput2, class TOutput>
340 {
341  return m_RadiusMin;
342 }
343 
344 template <class TInput1, class TInput2, class TOutput>
346 {
347  return m_RadiusMax;
348 }
349 
350 /* ====== Make the set of masks to play the increase in window size == */
351 
352 template <class TInput1, class TInput2, class TOutput>
354 {
355  m_mask.resize(m_RadiusMax - m_RadiusMin + 1);
356  int lenMax = 2 * m_RadiusMax + 1;
357  int i, j, middle = m_RadiusMax;
358 
359  // let's begin by the smaller neighborhood
360  std::vector<itk::Array2D<int>>::iterator outer_iter = m_mask.begin();
361  (*outer_iter).SetSize(lenMax, lenMax);
362  (*outer_iter).fill(0);
363  for (i = middle - m_RadiusMin; i <= middle + m_RadiusMin; ++i)
364  for (j = middle - m_RadiusMin; j <= middle + m_RadiusMin; ++j)
365  (*outer_iter).put(i, j, 1);
366 
367  // std::cerr << "outerIter = " << (*outer_iter) << std::endl;
368 
369  // let's continue with increasing neighborhoods
370  ++outer_iter;
371  for (int radius = m_RadiusMin + 1; radius <= m_RadiusMax; ++radius)
372  {
373  (*outer_iter).SetSize(lenMax, lenMax);
374  (*outer_iter).fill(0);
375 
376  for (i = middle - radius; i <= middle + radius; ++i)
377  {
378  (*outer_iter).put(i, middle - radius, 1);
379  (*outer_iter).put(i, middle + radius, 1);
380  (*outer_iter).put(middle - radius, i, 1);
381  (*outer_iter).put(middle + radius, i, 1);
382  }
383 
384  // std::cerr << "outerIter = " << (*outer_iter) << std::endl;
385  ++outer_iter;
386  }
387 }
388 
389 /* ========================== Functor ================================ */
390 
391 template <class TInput1, class TInput2, class TOutput>
392 TOutput KullbackLeiblerProfile<TInput1, TInput2, TOutput>::operator()(const TInput1& it1, const TInput2& it2)
393 {
394  CumulantsForEdgeworthProfile<TInput1> cum1(it1, m_mask);
395 
396  if (cum1.m_debug)
397  {
398  itk::VariableLengthVector<double> resu(m_RadiusMax - m_RadiusMin + 1);
399  resu.Fill(1e3);
400  return static_cast<TOutput>(resu);
401  }
402 
403  CumulantsForEdgeworthProfile<TInput2> cum2(it2, m_mask);
404 
405  if (cum2.m_debug)
406  {
407  itk::VariableLengthVector<double> resu(m_RadiusMax - m_RadiusMin + 1);
408  resu.Fill(1e3);
409  return static_cast<TOutput>(resu);
410  }
411 
412  return static_cast<TOutput>(cum1.KL_profile(cum2) + cum2.KL_profile(cum1));
413 }
414 
415 } // Functor
416 
417 } // namespace otb
418 
419 #endif
Helper class for KullbackLeiblerProfileImageFilter. Please refer to KullbackLeibleProfileImageFilter.
int InitSumAndMoments(const TInput &input, itk::Array2D< int > &mask)
itk::VariableLengthVector< double > KL_profile(CumulantsForEdgeworthProfile< TInput2 > &cumulants)
int ReInitSumAndMoments(const TInput &input, itk::Array2D< int > &mask, int level)
int MakeSumAndMoments(const TInput &input, std::vector< itk::Array2D< int >> &mask)
void SetRadius(const unsigned char &min, const unsigned char &max)
TOutput operator()(const TInput1 &it1, const TInput2 &it2)
The "otb" namespace contains all Orfeo Toolbox (OTB) classes.