/**************************************************************************** Copyright (c) 2006, Colorado School of Mines and others. All rights reserved. This program and accompanying materials are made available under the terms of the Common Public License - v1.0, which accompanies this distribution, and is available at http://www.eclipse.org/legal/cpl-v10.html ****************************************************************************/ package edu.mines.jtk.dsp; import static edu.mines.jtk.util.ArrayMath.*; import edu.mines.jtk.util.Check; /** * Estimates displacement vector fields for two images. For example, given * two 2-D images f(x1,x2) and g(x1,x2), a shift finder estimates two vector * components of displacement u1(x1,x2) and u2(x1,x2) such that * f(x1,x2) ~ g(x1+u1(x1,x2),x2+u2(x1,x2)). *

* Like the images f and g, the components of displacement are sampled * functions of coordinates x1 and x2. That is, displacements may vary * from sample to sample. The components u1 and u2 represent displacements * in the x1 and x2 coordinate directions, respectively. *

* This shift finder estimates each component of displacement using local * cross-correlations. For each image sample, the estimated shift is that * which yields the maximum correlation coefficient. This coefficient is * found by quadratic interpolation of correlation functions sampled at * integer lags. *

* Methods are provided to find and compensate for each component of shift * sequentially. As each component is found, that component can be removed * from the image g before estimating another component. For example, again * for 2-D images f(x1,x2) and g(x1,x2), we might first estimate u1(x1,x2). * If we then compute an image h(x1,x2) = g(x1+u1(x1,x2),x2), we can use * f(x1,x2) and h(x1,x2) to estimate u2(x1,x2). By repeating this process * sequentially, we obtain estimates for both u1(x1,x2) and u2(x1,x2) such * that f(x1,x2) ~ g(x1+u1(x1,x2),x2+u2(x1,x2)). *

* Methods are also provided to whiten 2-D and 3-D images before estimating * displacement vectors. This (spectral) whitening improves estimates of * displacements parallel to image features that may be otherwise poorly * resolved. Whitening is performed with local prediction error filters * computed from local auto-correlations. * * @author Dave Hale, Colorado School of Mines * @version 2006.11.18 */ public class LocalShiftFinder { /** * Construct a shift estimator with specified parameters. * When applied to multi-dimensional arrays, the estimator has the * same correlation window half-width for all dimensions. * @param sigma the correlation window half-width; must not be less than 1. */ public LocalShiftFinder(double sigma) { this(sigma,sigma,sigma); } /** * Construct a shift estimator with specified parameters. * When applied to multi-dimensional arrays, the estimator has half-width * sigma1 for the 1st dimension and half-width sigma2 for 2nd and higher * dimensions. * @param sigma1 correlaton window half-width for 0st dimension; * must not be less than 1. * @param sigma2 correlation window half-width for 2nd and higher * dimensions; must not be less than 1. */ public LocalShiftFinder(double sigma1, double sigma2) { this(sigma1,sigma2,sigma2); } /** * Construct a shift estimator with specified parameters. * When applied to multi-dimensional arrays, the estimator has half-width * sigma1 for the 1st dimension, half-width sigma2 for the 2nd dimension, * and half-width sigma3 for 3rd and higher dimensions. * @param sigma1 correlation window half-width for 1st dimension; * must not be less than 1. * @param sigma2 correlation window half-width for 2nd dimension; * must not be less than 1. * @param sigma3 correlation window half-width for 3rd and higher * dimensions; must not be less than 1. */ public LocalShiftFinder(double sigma1, double sigma2, double sigma3) { Check.argument(sigma1>=1.0,"sigma1>=1.0"); Check.argument(sigma2>=1.0,"sigma2>=1.0"); Check.argument(sigma3>=1.0,"sigma3>=1.0"); _lcfSimple = new LocalCorrelationFilter( LocalCorrelationFilter.Type.SIMPLE, LocalCorrelationFilter.Window.GAUSSIAN, sigma1,sigma2,sigma3); _si = new SincInterpolator(); _si.setExtrapolation(SincInterpolator.Extrapolation.CONSTANT); } /** * Enables or disables interpolation of displacements when shifting. * The default is to interpolate displacements. This is the most * accurate method when sequentially applying non-constant shifts. * @param enable true, to enable interpolation; false, to disable. */ public void setInterpolateDisplacements(boolean enable) { _interpolateDisplacements = enable; } /** * Finds shifts in the 1st (and only) dimension. * @param min1 the minimum shift. * @param max1 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find1( int min1, int max1, float[] f, float[] g, float[] u) { findShifts(min1,max1,f,g,u); } /** * Finds shifts in the 1st dimension. * @param min1 the minimum shift. * @param max1 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find1( int min1, int max1, float[][] f, float[][] g, float[][] u) { findShifts(1,min1,max1,f,g,u); } /** * Finds shifts in the 2nd dimension. * @param min2 the minimum shift. * @param max2 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find2( int min2, int max2, float[][] f, float[][] g, float[][] u) { findShifts(2,min2,max2,f,g,u); } /** * Finds shifts in the 1st dimension. * @param min1 the minimum shift. * @param max1 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find1( int min1, int max1, float[][][] f, float[][][] g, float[][][] u) { findShifts(1,min1,max1,f,g,u); } /** * Finds shifts in the 2nd dimension. * @param min2 the minimum shift. * @param max2 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find2( int min2, int max2, float[][][] f, float[][][] g, float[][][] u) { findShifts(2,min2,max2,f,g,u); } /** * Finds shifts in the 3rd dimension. * @param min3 the minimum shift. * @param max3 the maximum shift. * @param f the input array f. * @param g the input array g. * @param u output array of shifts. */ public void find3( int min3, int max3, float[][][] f, float[][][] g, float[][][] u) { findShifts(3,min3,max3,f,g,u); } /** * Applies specified shift in the 1st (and only) dimension. * @param du input array of changes to displacements in 1st dimension. * @param u1 input/output array of displacements in 1st dimension. * @param h input/output array of image samples. */ public void shift1(float[] du, float[] u1, float[] h) { int n1 = h.length; float[] xu1 = new float[n1]; float[] u1a = u1; float[] u1b = new float[n1]; float[] ha = h; float[] hb = new float[n1]; for (int i1=0; i10.0) { double l22 = sqrt(d22); double v1 = b1/l11; double v2 = (b2-l21*v1)/l22; x2 = v2/l22; x1 = (v1-l21*x2)/l11; } float a1 = (float)x1; float a2 = (float)x2; s[i2][i1] = f[i2][i1] - a1*f[i2][i1-1] - a2*f[i2-1][i1]; } } if (sigma>=1.0) { RecursiveGaussianFilter rgf = new RecursiveGaussianFilter(sigma); rgf.apply0X(s,t); rgf.applyX0(t,g); } else { copy(s,g); } } /** * Applies local prediction-error (spectal whitening) filters. * The input and output arrays f and g can be the same array. * Smooths the output with a Gaussian filter with half-width sigma = 1.0. * @param f the input array. * @param g the output array. */ public void whiten(float[][][] f, float[][][] g) { whiten(1.0,f,g); } /** * Applies local prediction-error (spectal whitening) filters. * The input and output arrays f and g can be the same array. * @param sigma half-width of Gaussian smoothing applied after whitening; * less than one for no smoothing. * @param f the input array. * @param g the output array. */ public void whiten(double sigma, float[][][] f, float[][][] g) { int n1 = f[0][0].length; int n2 = f[0].length; int n3 = f.length; float[][][] r000 = new float[n3][n2][n1]; float[][][] rpm0 = new float[n3][n2][n1]; float[][][] rp0m = new float[n3][n2][n1]; float[][][] r0pm = new float[n3][n2][n1]; float[][][] rm00 = new float[n3][n2][n1]; float[][][] r0m0 = new float[n3][n2][n1]; float[][][] r00m = new float[n3][n2][n1]; float[][][] s = rm00; float[][][] t = r0m0; _lcfSimple.setInputs(f,f); _lcfSimple.correlate( 0, 0, 0,r000); _lcfSimple.correlate( 1,-1, 0,rpm0); _lcfSimple.correlate( 1, 0,-1,rp0m); _lcfSimple.correlate( 0, 1,-1,r0pm); _lcfSimple.correlate(-1, 0, 0,rm00); _lcfSimple.correlate( 0,-1, 0,r0m0); _lcfSimple.correlate( 0, 0,-1,r00m); for (int i3=0; i30.0) { double l22 = sqrt(d22); double l32 = (a32-l31*l21)/l22; double d33 = a33-l31*l31-l32*l32; if (d33>0.0) { double l33 = sqrt(d33); double v1 = b1/l11; double v2 = (b2-l21*v1)/l22; double v3 = (b3-l31*v1-l32*v2)/l33; x3 = v3/l33; x2 = (v2-l32*x3)/l22; x1 = (v1-l21*x2-l31*x3)/l11; } } float a1 = (float)x1; float a2 = (float)x2; float a3 = (float)x3; s[i3][i2][i1] = f[i3][i2][i1] - a1*f[i3][i2][i1-1] - a2*f[i3][i2-1][i1] - a3*f[i3-1][i2][i1]; } } } if (sigma>=1.0) { RecursiveGaussianFilter rgf = new RecursiveGaussianFilter(sigma); rgf.apply0XX(s,t); rgf.applyX0X(t,s); rgf.applyXX0(s,g); } else { copy(s,g); } } /////////////////////////////////////////////////////////////////////////// // private private LocalCorrelationFilter _lcfSimple; private SincInterpolator _si; private boolean _interpolateDisplacements = true; private void findShifts( int min, int max, float[] f, float[] g, float[] u) { int n1 = f.length; // Default shifts are zero. zero(u); // Arrays to contain cross-correlations for three consecutive lags. float[][] c = new float[3][n1]; // Array for current correlation maximum values. float[] cmax = new float[n1]; // Correlate for min lag. LocalCorrelationFilter lcf = _lcfSimple; lcf.setInputs(f,g); int lag1 = min; lcf.correlate(lag1,c[1]); lcf.normalize(lag1,c[1]); // For all lags in range [min,max], ... for (int lag=min; lag<=max; ++lag) { // Arrays ca, cb, and cc will contain three cross-correlations. For // first and last lags, buffers a and c are the same. In other words, // assume that correlation values are symmetric about the min and max // lags scanned. This assumption enables local maxima to occur at the // specified min and max lags, but forces displacements to lie within // the range [min,max]. int i = lag-min; float[] ca = (lag>min)?c[(i )%3]:c[(i+2)%3]; float[] cb = c[(i+1)%3]; float[] cc = (lag=ai && bi>=ci) { double c0 = bi; double c1 = 0.5*(ci-ai); double c2 = 0.5*(ci+ai)-bi; double up = (c2<0.0)?-0.5*c1/c2:0.0; double cp = c0+up*(c1+up*c2); if (cp>cmax[i1]) { cmax[i1] = (float)cp; u[i1] = (float)(lag+up); } } } } } private void findShifts( int dim, int min, int max, float[][] f, float[][] g, float[][] u) { int n1 = f[0].length; int n2 = f.length; // Default shifts are zero. zero(u); // Arrays to contain cross-correlations for three consecutive lags. float[][][] c = new float[3][n2][n1]; // Array for current correlation maximum values. float[][] cmax = new float[n2][n1]; // Correlate for min lag. LocalCorrelationFilter lcf = _lcfSimple; lcf.setInputs(f,g); int lag1 = (dim==1)?min:0; int lag2 = (dim==2)?min:0; lcf.correlate(lag1,lag2,c[1]); lcf.normalize(lag1,lag2,c[1]); // For all lags in range [min,max], ... for (int lag=min; lag<=max; ++lag) { // Arrays ca, cb, and cc will contain three cross-correlations. For // first and last lags, buffers a and c are the same. In other words, // assume that correlation values are symmetric about the min and max // lags scanned. This assumption enables local maxima to occur at the // specified min and max lags, but forces displacements to lie within // the range [min,max]. int i = lag-min; float[][] ca = (lag>min)?c[(i )%3]:c[(i+2)%3]; float[][] cb = c[(i+1)%3]; float[][] cc = (lag=ai && bi>=ci) { double c0 = bi; double c1 = 0.5*(ci-ai); double c2 = 0.5*(ci+ai)-bi; double up = (c2<0.0)?-0.5*c1/c2:0.0; double cp = c0+up*(c1+up*c2); if (cp>cmax[i2][i1]) { cmax[i2][i1] = (float)cp; u[i2][i1] = (float)(lag+up); } } } } } } private void findShifts( int dim, int min, int max, float[][][] f, float[][][] g, float[][][] u) { int n1 = f[0][0].length; int n2 = f[0].length; int n3 = f.length; // Default shifts are zero. zero(u); // Arrays to contain cross-correlations for three consecutive lags. float[][][][] c = new float[3][n3][n2][n1]; // Array for current correlation maximum values. float[][][] cmax = new float[n3][n2][n1]; // Correlate for min lag. LocalCorrelationFilter lcf = _lcfSimple; lcf.setInputs(f,g); int lag1 = (dim==1)?min:0; int lag2 = (dim==2)?min:0; int lag3 = (dim==3)?min:0; lcf.correlate(lag1,lag2,lag3,c[1]); lcf.normalize(lag1,lag2,lag3,c[1]); // For all lags in range [min,max], ... for (int lag=min; lag<=max; ++lag) { // Arrays ca, cb, and cc will contain three cross-correlations. For // first and last lags, buffers a and c are the same. In other words, // assume that correlation values are symmetric about the min and max // lags scanned. This assumption enables local maxima to occur at the // specified min and max lags, but forces displacements to lie within // the range [min,max]. int i = lag-min; float[][][] ca = (lag>min)?c[(i )%3]:c[(i+2)%3]; float[][][] cb = c[(i+1)%3]; float[][][] cc = (lag=ai && bi>=ci) { double c0 = bi; double c1 = 0.5*(ci-ai); double c2 = 0.5*(ci+ai)-bi; double up = (c2<0.0)?-0.5*c1/c2:0.0; double cp = c0+up*(c1+up*c2); if (cp>cmax[i3][i2][i1]) { cmax[i3][i2][i1] = (float)cp; u[i3][i2][i1] = (float)(lag+up); } } } } } } } }