mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
imageXCorrDiscrete.hpp
Go to the documentation of this file.
1/** \file imageXCorrDiscrete.hpp
2 * \brief A class to register images using the discrete cross correlation.
3 * \ingroup image_processing_files
4 * \author Jared R. Males (jaredmales@gmail.com)
5 *
6 */
7
8//***********************************************************************//
9// Copyright 2020 Jared R. Males (jaredmales@gmail.com)
10//
11// This file is part of mxlib.
12//
13// mxlib is free software: you can redistribute it and/or modify
14// it under the terms of the GNU General Public License as published by
15// the Free Software Foundation, either version 3 of the License, or
16// (at your option) any later version.
17//
18// mxlib is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
25//***********************************************************************//
26
27#ifndef imageXCorrDiscrete_hpp
28#define imageXCorrDiscrete_hpp
29
30#include <algorithm>
31#include <cmath>
32#include <limits>
33
34#include "../mxlib.hpp"
36#include "imageUtils.hpp"
37
38namespace mx
39{
40namespace improc
41{
42
43enum class xcorrPeakMethod
44{
45 centroid,
46 gaussfit,
47 interp
48};
49
50/// Find the optimum shift to align two images using the discrete cross correlation.
51/** The reference image must be smaller than the target image. Both the reference image and the section of
52 * target image being analyzed are mean subtracted and variance normalized. An optional mask can be supplied,
53 * which limits the pixels used for the mean and variance calculation.
54 *
55 * Typical usage will be to set the mask, then the reference, then repeatedly call operator() to
56 * determine the shifts for a sequence of imaages. No new heap allocations take place on these calls
57 * to operator(), and the reference image is not re-normalized on each call.
58 *
59 * The shift is reported in pixels such that if the mxlib imageShift function is used
60 * to shift the input image by the negative of the shifts, it will align with the
61 * reference at the center of the array.
62 *
63 * Three peak finding methods are provided. xcorrPeakMethod::centroid uses center of light,
64 * xcorrPeakMethod::gaussfit uses Gaussian centroiding, and xcorrPeakMethod::interp uses interpolation
65 * to find the peak to a given tolerance.
66 *
67 * \tparam _ccImT is the Eigen-like array type used for image processing. See typedefs.
68 *
69 * \ingroup image_reg
70 */
71template <class _ccImT>
73{
74 public:
75 typedef _ccImT ccImT; ///< the Eigen-like array type used for image processing
76 typedef typename _ccImT::Scalar Scalar; ///< the scalar type of the image type
77
78 protected:
79 public:
80 /** \name Working Memory
81 * @{
82 */
83
84 ccImT m_refIm; ///< The normalized reference image.
85
86 ccImT m_maskIm; ///< Mask image to use, may be needed for proper normalization even if refIm has 0 mask applied.
87
88 bool m_haveMask{ false };
89
90 ccImT m_normIm; ///< The normalized image.
91
92 ccImT m_ccIm; ///< The cross-correlation image
93
94 ccImT m_magIm; ///< The magnified image, or empty when interpolation falls back to the discrete peak.
95
96 ///@}
97
99
100 int m_maxLag{ 0 }; ///< The maximum lag to consider in the initial cross-correlation.
101
102 Scalar m_tol{ 0.1 }; ///< The tolerance of the interpolated-magnified image, in pixels.
103
104 Scalar m_magSize{ 0 }; ///< Magnified size of the ccIm when using interp. Set as function of m_tol and m_maxLag.
105
106 public:
107 xcorrPeakMethod m_peakMethod{ xcorrPeakMethod::centroid };
108
109 public:
110 /// Default c'tor
112
113 /// Construct seeting maxLag.
114 explicit imageXCorrDiscrete( int maxLag );
115
116 /// Get the current maximum lag
117 /**
118 * \returns the current value of m_maxLag
119 */
120 int maxLag();
121
122 /// Set the maximum lag
123 void maxLag( int ml /**< [in] the new maximum lag */ );
124
125 /// Get the tolerance of the interpolated-magnified image, in pixels.
126 /**
127 * \returns the current value of m_tol.
128 */
129 Scalar tol();
130
131 /// Set the tolerance of the interpolated-magnified image, in pixels.
132 void tol( Scalar nt /**< [in] The new value of the interpolation tolerance. */ );
133
134 /// Set the size of the cross-correlation images.
135 /** This resizes all working memory.
136 *
137 * \returns 0 on success
138 * \returns -1 on error
139 */
140 int resize( int nrows, ///< [in] the number of rows in the images to register
141 int ncols ///< [in] the number of columns in the images to register
142 );
143
144 /// Set the mask image
145 /**
146 * \returns 0 on success
147 * \returns -1 on error
148 */
149 int maskIm( const ccImT &mask /**< [in] the new mask image */ );
150
151 /// Get a reference to the mask image.
152 /**
153 * \returns a const referance to the mask image.
154 */
155 const ccImT &maskIm();
156
157 /// Set the reference image
158 /** Normalizes the reference image by mean subtraction and variance division. Applies
159 * the mask first if supplied.
160 *
161 * \returns 0 on success
162 * \returns -1 on error
163 */
164 int refIm( const ccImT &im0 );
165
166 /// Get a reference to the reference image.
167 /**
168 * \returns a const referent to m_refIm.
169 */
170 const ccImT &refIm();
171
172 /// Get a reference to the normalized image.
173 /**
174 * \returns a const referent to m_normIm.
175 */
176 const ccImT &normIm();
177
178 /// Get a reference to the cross correlation image.
179 /**
180 * \returns a const referent to m_ccIm.
181 */
182 const ccImT &ccIm();
183
184 /// Get a reference to the magnified image.
185 /**
186 * \returns a const referent to m_magIm.
187 */
188 const ccImT &magIm();
189
190 /// Conduct the cross correlation to a specified tolerance
191 /** Cubic interpolation requires at least five correlation samples in each
192 * dimension and cannot interpolate a maximum on the outer boundary. It
193 * also pads unsupported magnified borders with zero, which would dominate
194 * a non-positive correlation surface. In these cases, this falls back to
195 * the discrete correlation maximum and leaves `magIm()` empty.
196 *
197 * \returns 0 on success
198 * \returns -1 on error
199 */
200 template <class imT>
201 int operator()( Scalar &xShift, ///< [out] the x shift of im w.r.t. im0, in pixels
202 Scalar &yShift, ///< [out] the y shift of im w.r.t. im0, in pixels
203 const imT &im ///< [in] the image to cross-correlate with the reference
204 );
205};
206
207template <class ccImT>
211
212template <class ccImT>
217
218template <class ccImT>
220{
221 return m_maxLag;
222}
223
224template <class ccImT>
226{
227 m_maxLag = ml;
228 tol( m_tol );
229}
230
231template <class ccImT>
232typename ccImT::Scalar imageXCorrDiscrete<ccImT>::tol()
233{
234 return m_tol;
235}
236
237template <class ccImT>
239{
240 m_tol = nt;
241 m_magSize = 0;
242
243 if( !math::isFinite( nt ) || nt <= 0 || m_maxLag <= 0 )
244 {
245 return;
246 }
247
248 m_magSize = ceil( ( ( 2. * m_maxLag + 1 ) - 1.0 ) / nt ) + 1;
249
250 Scalar mag = ( m_magSize - 1.0 ) / ( ( 2. * m_maxLag + 1 ) - 1.0 );
251
252 m_tol = 1.0 / mag;
253}
254
255template <class ccImT>
257{
258 m_maskIm = mask;
259 m_haveMask = true;
260
261 return 0;
262}
263
264template <class ccImT>
266{
267 return m_maskIm;
268}
269
270template <class ccImT>
272{
273 ccImT im0;
274 if( m_haveMask )
275 {
276 if( im.rows() != m_maskIm.rows() || im.cols() != m_maskIm.cols() )
277 {
278 internal::mxlib_error_report( error_t::sizeerr, "reference and mask are not the same size" );
279 return -1;
280 }
281
282 im0 = im * m_maskIm;
283 }
284 else
285 {
286 im0 = im;
287 }
288
289 Scalar m = imageMean<Scalar>( im0 );
290 Scalar v = imageVariance( im0, m );
291 m_refIm = ( im0 - m ) / sqrt( v );
292
293 return 0;
294}
295
296template <class ccImT>
298{
299 return m_refIm;
300}
301
302template <class ccImT>
304{
305 return m_normIm;
306}
307
308template <class ccImT>
310{
311 return m_ccIm;
312}
313
314template <class ccImT>
316{
317 return m_magIm;
318}
319
320template <class ccImT>
321template <class imT>
322int imageXCorrDiscrete<ccImT>::operator()( Scalar &xShift, Scalar &yShift, const imT &im )
323{
324 if( im.rows() <= m_refIm.rows() )
325 {
326 internal::mxlib_error_report(error_t::sizeerr,"reference must be smaller than target image (rows)" );
327 return -1;
328 }
329
330 if( im.cols() <= m_refIm.cols() )
331 {
332 internal::mxlib_error_report(error_t::sizeerr, "reference must be smaller than target image (cols)" );
333 return -1;
334 }
335 // 16/4 15/4 16/3 15/3
336 int maxLag_r = ( im.rows() - m_refIm.rows() ) / 2; // 6 5 6 6
337 int maxLag_c = ( im.cols() - m_refIm.cols() ) / 2;
338
339 if( maxLag_r > m_maxLag && m_maxLag != 0 )
340 maxLag_r = m_maxLag;
341 if( maxLag_c > m_maxLag && m_maxLag != 0 )
342 maxLag_c = m_maxLag;
343
344 m_ccIm.resize( 2 * maxLag_r + 1, 2 * maxLag_c + 1 );
345
346 for( int rL = -maxLag_r; rL <= maxLag_r; ++rL )
347 {
348 for( int cL = -maxLag_c; cL <= maxLag_c; ++cL )
349 {
350 // 16/4 15/4 16/3 15/3
351 int r0 = 0.5 * im.rows() + rL - 0.5 * m_refIm.rows(); // 0-15 0-13 0-14 0-14
352 int c0 = 0.5 * im.cols() + cL - 0.5 * m_refIm.cols();
353
354 if( m_haveMask )
355 {
356 m_normIm = im.block( r0, c0, m_refIm.rows(), m_refIm.cols() ) * m_maskIm;
357 }
358 else
359 {
360 m_normIm = im.block( r0, c0, m_refIm.rows(), m_refIm.cols() );
361 }
362
364 Scalar sv = sqrt( imageVariance( m_normIm, m ) );
365 if( sv <= 0 )
366 {
367 m_ccIm( maxLag_r + rL, maxLag_c + cL ) = 0;
368 }
369 else
370 {
371 m_normIm = ( m_normIm - m ) / sv;
372 m_ccIm( maxLag_r + rL, maxLag_c + cL ) = ( m_normIm * m_refIm ).sum();
373 // ds9Interface ds9( m_normIm, 1);
374 }
375 }
376 }
377
378 if( m_peakMethod == xcorrPeakMethod::gaussfit )
379 {
380 int xLag0, yLag0;
381 Scalar pk = m_ccIm.maxCoeff( &xLag0, &yLag0 );
382 Scalar mn = m_ccIm.minCoeff();
383 m_fitter.setArray( m_ccIm.data(), m_ccIm.rows(), m_ccIm.cols() );
384 m_fitter.setGuess( mn, pk, xLag0, yLag0, 0.2 * m_ccIm.rows(), 0.2 * m_ccIm.cols(), 0 );
385 m_fitter.fit();
386
387 xShift = m_fitter.x0() - maxLag_r;
388 yShift = m_fitter.y0() - maxLag_c;
389 }
390 else if( m_peakMethod == xcorrPeakMethod::interp )
391 {
392 if( !math::isFinite( m_tol ) || m_tol <= 0 )
393 {
394 internal::mxlib_error_report( error_t::invalidarg, "interpolation tolerance must be finite and positive" );
395 return -1;
396 }
397
398 int xPeak, yPeak;
399 const Scalar peakValue = m_ccIm.maxCoeff( &xPeak, &yPeak );
400 if( m_ccIm.rows() < 5 || m_ccIm.cols() < 5 || !( peakValue > 0 ) || !math::isFinite( peakValue ) ||
401 xPeak == 0 || yPeak == 0 || xPeak == m_ccIm.rows() - 1 || yPeak == m_ccIm.cols() - 1 )
402 {
403 m_magIm.resize( 0, 0 );
404 m_magSize = 0;
405 xShift = xPeak - maxLag_r;
406 yShift = yPeak - maxLag_c;
407 return 0;
408 }
409
410 const Scalar magRowsScalar = ceil( ( m_ccIm.rows() - 1.0 ) / m_tol ) + 1.0;
411 const Scalar magColsScalar = ceil( ( m_ccIm.cols() - 1.0 ) / m_tol ) + 1.0;
412 if( !math::isFinite( magRowsScalar ) || !math::isFinite( magColsScalar ) || magRowsScalar <= 1 ||
413 magColsScalar <= 1 || magRowsScalar > std::numeric_limits<int>::max() ||
414 magColsScalar > std::numeric_limits<int>::max() )
415 {
416 internal::mxlib_error_report( error_t::sizeerr, "invalid interpolated cross-correlation dimensions" );
417 return -1;
418 }
419
420 const int magRows = static_cast<int>( magRowsScalar );
421 const int magCols = static_cast<int>( magColsScalar );
422 m_magSize = std::max( magRows, magCols );
423 m_magIm.resize( magRows, magCols );
425 int x, y;
426 m_magIm.maxCoeff( &x, &y );
427 const Scalar xScale = ( m_ccIm.rows() - 1.0 ) / ( m_magIm.rows() - 1.0 );
428 const Scalar yScale = ( m_ccIm.cols() - 1.0 ) / ( m_magIm.cols() - 1.0 );
429 xShift = x * xScale - maxLag_r;
430 yShift = y * yScale - maxLag_c;
431 }
432 else
433 {
434 int xPeak, yPeak;
435 m_ccIm.maxCoeff( &xPeak, &yPeak );
436
437 int centroidHalfWidth = std::min( xPeak, static_cast<int>( m_ccIm.rows() ) - 1 - xPeak );
438 centroidHalfWidth = std::min( centroidHalfWidth, yPeak );
439 centroidHalfWidth =
440 std::min( centroidHalfWidth, static_cast<int>( m_ccIm.cols() ) - 1 - yPeak );
441
442 if( centroidHalfWidth == 0 )
443 {
444 xShift = xPeak - maxLag_r;
445 yShift = yPeak - maxLag_c;
446 return 0;
447 }
448
449 const int centroidWidth = 2 * centroidHalfWidth + 1;
450 const int x0 = xPeak - centroidHalfWidth;
451 const int y0 = yPeak - centroidHalfWidth;
452 m_magIm = m_ccIm.block( x0, y0, centroidWidth, centroidWidth );
453 m_magIm -= m_magIm.minCoeff();
454
455 const Scalar centroidWeight = m_magIm.sum();
456 if( !( centroidWeight > 0 ) || !math::isFinite( centroidWeight ) )
457 {
458 xShift = xPeak - maxLag_r;
459 yShift = yPeak - maxLag_c;
460 return 0;
461 }
462
463 Scalar x, y;
465 xShift = x + x0 - maxLag_r;
466 yShift = y + y0 - maxLag_c;
467 }
468
469 return 0;
470}
471
472} // namespace improc
473} // namespace mx
474
475#endif // imageXCorrDiscrete_hpp
ccImT m_normIm
The normalized image.
_ccImT::Scalar Scalar
the scalar type of the image type
const ccImT & refIm()
Get a reference to the reference image.
int m_maxLag
The maximum lag to consider in the initial cross-correlation.
ccImT m_maskIm
Mask image to use, may be needed for proper normalization even if refIm has 0 mask applied.
ccImT m_magIm
The magnified image, or empty when interpolation falls back to the discrete peak.
int maxLag()
Get the current maximum lag.
ccImT m_ccIm
The cross-correlation image.
Scalar m_tol
The tolerance of the interpolated-magnified image, in pixels.
Scalar m_magSize
Magnified size of the ccIm when using interp. Set as function of m_tol and m_maxLag.
_ccImT ccImT
the Eigen-like array type used for image processing
ccImT m_refIm
The normalized reference image.
Scalar tol()
Get the tolerance of the interpolated-magnified image, in pixels.
int operator()(Scalar &xShift, Scalar &yShift, const imT &im)
Conduct the cross correlation to a specified tolerance.
const ccImT & maskIm()
Get a reference to the mask image.
const ccImT & magIm()
Get a reference to the magnified image.
const ccImT & normIm()
Get a reference to the normalized image.
int resize(int nrows, int ncols)
Set the size of the cross-correlation images.
const ccImT & ccIm()
Get a reference to the cross correlation image.
Class to manage fitting a 2D Gaussian to data via the levmarInterface.
Tools for fitting Gaussians to data.
calcT imageVariance(imageT &im, calcT mn)
Calculate the variance of an image given its mean.
int imageCenterOfLight(typename imageT::Scalar &x, typename imageT::Scalar &y, const imageT &im)
Calculate the center of light of an image.
calcT imageMean(imageT &im)
Calculate the mean value of an image.
@ sizeerr
A size was invalid or calculated incorrectly.
Definition error_t.hpp:35
@ invalidarg
An argument was invalid.
Definition error_t.hpp:29
error_t mxlib_error_report(const error_t &code, const std::string &expl, const std::source_location &loc=std::source_location::current())
Print a report to stderr given an mxlib error_t code and explanation and return the code.
Definition error.hpp:331
bool isFinite(realT value)
Test whether a floating-point value is finite, including under finite-math-only optimization.
xcorrPeakMethod
Methods for finding the cross-correlation peak.
void imageMagnify(arrOutT &transim, const arrInT &im, transformT trans)
Magnify an image.
Header for the image processing utilities.
Declarations of some libarary wide utilities.
The mxlib c++ namespace.
Definition mxlib.hpp:37
Transformation by cubic convolution interpolation.