edu.mines.jtk.dsp.DynamicWarping Maven / Gradle / Ivy
/****************************************************************************
Copyright 2012, Colorado School of Mines and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
****************************************************************************/
package edu.mines.jtk.dsp;
import edu.mines.jtk.util.*;
import static edu.mines.jtk.util.ArrayMath.*;
/**
* Dynamic warping of sequences and images.
*
* For sequences f and g, dynamic warping finds a sequence of
* shifts u such that f[i1] ~ g[i1+u[i1]], subject to a bound b1
* on strain, the rate at which the shifts u[i1] vary with sample
* index i1.
*
* An increasing u[i1] = u[i1-1] + 1 implies that, between indices
* i1-1 and i1, g[i1] is a stretched version of f[i1] ~ g[i1+u[i1]].
* For, in this case, values in f for indices i1 and i1-1 are one
* sample apart, but corresponding values in g are two samples
* apart, which implies stretching by 100%. Likewise, a decreasing
* u[i1] = u[i1-1] - 1 implies squeezing by 100%.
*
* In practice, 100% strain (stretching or squeezing) may be extreme.
* Therefore, the upper bound on strain may be smaller than one. For
* example, if the bound b1 = 0.5, then |u[i1]-u[i1-1]| ≤ 0.5.
*
* For 2D images f and g, dynamic warping finds a 2D array of shifts
* u[i2][i1] such that f[i2][i1] ~ g[i2][i1+u[i2][i1]], subject to
* bounds b1 and b2 on strains, the rates at which shifts u[i2][i1]
* vary with samples indices i1 and i2, respectively.
*
* For 3D images f and g, dynamic warping finds a 3D array of shifts
* u[i3][i2][i1] in a similar way. However, finding shifts for 3D
* images may require an excessive amount of memory. Dynamic image
* warping requires a temporary array of nlag*nsample floats, where
* the number of lags nlag = 1+shiftMax-shiftMin and nsample is the
* number of image samples. For 3D images, the product nlag*nsample
* is likely to be too large for the temporary array to fit in random-
* access memory (RAM). In this case, shifts u are obtained by blending
* together shifts computed from overlapping subsets of the 3D image.
*
* Estimated shifts u can be smoothed, and the extent of smoothing
* along each dimension is inversely proportional to the strain limit
* for that dimension. These extents can be scaled by specified factors
* for more or less smoothing. The default scale factors are zero, for
* no smoothing.
*
* This class provides numerous methods, but typical applications
* require only several of these, usually only the methods that find
* and apply shifts. The many other methods are provided only for
* atypical applications and research.
*
* @author Dave Hale, Colorado School of Mines
* @version 2012.07.05
*/
public class DynamicWarping {
/**
* The method used to extrapolate alignment errors.
* Alignment errors |f[i]-g[i+l]| cannot be computed for indices
* i and lags l for which the sum i+l is out of bounds. For such
* indices and lags, errors are missing and must be extrapolated.
*
* The extrapolation methods provided are designed to work best
* in the case where errors are low for one particular lag l, that
* is, when the sequences f and g are related by a constant shift.
*/
public enum ErrorExtrapolation {
/**
* For each lag, extrapolate alignment errors using the nearest
* error not missing for that lag.
*
* This is the default extrapolation method.
*/
NEAREST,
/**
* For each lag, extrapolate alignment errors using the average
* of all errors not missing for that lag.
*/
AVERAGE,
/**
* For each lag, extrapolate alignment errors using a reflection
* of nearby errors not missing for that lag.
*/
REFLECT
}
/**
* Constructs a dynamic warping for specified bounds on shifts.
* @param shiftMin lower bound on shift u.
* @param shiftMax upper bound on shift u.
*/
public DynamicWarping(int shiftMin, int shiftMax) {
Check.argument(shiftMax-shiftMin>1,"shiftMax-shiftMin>1");
_lmin = shiftMin;
_lmax = shiftMax;
_nl = 1+_lmax-_lmin;
_si = new SincInterpolator();
_extrap = ErrorExtrapolation.NEAREST;
}
/**
* Sets bound on strain for all dimensions. Must be in (0,1].
* The actual bound on strain is 1.0/ceil(1.0/strainMax), which
* is less than the specified strainMax when 1.0/strainMax is not
* an integer. The default bound on strain is 1.0 (100%).
* @param strainMax the bound, a value less than or equal to one.
*/
public void setStrainMax(double strainMax) {
Check.argument(strainMax<=1.0,"strainMax<=1.0");
Check.argument(strainMax>0.0,"strainMax>0.0");
setStrainMax(strainMax,strainMax);
}
/**
* Sets bound on strains in 1st and 2nd dimensions.
* @param strainMax1 bound on strain in the 1st dimension.
* @param strainMax2 bound on strain in the 2nd dimension.
*/
public void setStrainMax(double strainMax1, double strainMax2) {
Check.argument(strainMax1<=1.0,"strainMax1<=1.0");
Check.argument(strainMax2<=1.0,"strainMax2<=1.0");
Check.argument(strainMax1>0.0,"strainMax1>0.0");
Check.argument(strainMax2>0.0,"strainMax2>0.0");
setStrainMax(strainMax1,strainMax2,strainMax2);
}
/**
* Sets bound on strains in 1st, 2nd and 3rd dimensions.
* @param strainMax1 bound on strain in the 1st dimension.
* @param strainMax2 bound on strain in the 2nd dimension.
* @param strainMax3 bound on strain in the 3rd dimension.
*/
public void setStrainMax(
double strainMax1, double strainMax2, double strainMax3)
{
Check.argument(strainMax1<=1.0,"strainMax1<=1.0");
Check.argument(strainMax2<=1.0,"strainMax2<=1.0");
Check.argument(strainMax3<=1.0,"strainMax3<=1.0");
Check.argument(strainMax1>0.0,"strainMax1>0.0");
Check.argument(strainMax2>0.0,"strainMax2>0.0");
Check.argument(strainMax3>0.0,"strainMax3>0.0");
_bstrain1 = (int)ceil(1.0/strainMax1);
_bstrain2 = (int)ceil(1.0/strainMax2);
_bstrain3 = (int)ceil(1.0/strainMax3);
updateSmoothingFilters();
}
/**
* Sets the method used to extrapolate alignment errors.
* Extrapolation is necessary when the sum i+l of sample index
* i and lag l is out of bounds. The default method is to use
* the error computed for the nearest index i and the same lag l.
* @param ee the error extrapolation method.
*/
public void setErrorExtrapolation(ErrorExtrapolation ee) {
_extrap = ee;
}
/**
* Sets the exponent used to compute alignment errors |f-g|^e.
* The default exponent is 2.
* @param e the exponent.
*/
public void setErrorExponent(double e) {
_epow = (float)e;
}
/**
* Sets the number of nonlinear smoothings of alignment errors.
* In dynamic warping, alignment errors are smoothed the specified
* number of times, along all dimensions (in order 1, 2, ...),
* before estimating shifts by accumulating and backtracking along
* only the 1st dimension.
*
* The default number of smoothings is zero, which is best for 1D
* sequences. For 2D and 3D images, two smoothings are recommended.
* @param esmooth number of nonlinear smoothings.
*/
public void setErrorSmoothing(int esmooth) {
_esmooth = esmooth;
}
/**
* Sets extent of smoothing filters used to smooth shifts.
* Half-widths of smoothing filters are inversely proportional to
* strain limits, and are scaled by the specified factor. Default
* factor is zero, for no smoothing.
* @param usmooth extent of smoothing filter in all dimensions.
*/
public void setShiftSmoothing(double usmooth) {
setShiftSmoothing(usmooth,usmooth);
}
/**
* Sets extents of smoothing filters used to smooth shifts.
* Half-widths of smoothing filters are inversely proportional to
* strain limits, and are scaled by the specified factors. Default
* factors are zero, for no smoothing.
* @param usmooth1 extent of smoothing filter in 1st dimension.
* @param usmooth2 extent of smoothing filter in 2nd dimension.
*/
public void setShiftSmoothing(double usmooth1, double usmooth2) {
setShiftSmoothing(usmooth1,usmooth2,usmooth2);
}
/**
* Sets extents of smoothing filters used to smooth shifts.
* Half-widths of smoothing filters are inversely proportional to
* strain limits, and are scaled by the specified factors. Default
* factors are zero, for no smoothing.
* @param usmooth1 extent of smoothing filter in 1st dimension.
* @param usmooth2 extent of smoothing filter in 2nd dimension.
* @param usmooth3 extent of smoothing filter in 3rd dimension.
*/
public void setShiftSmoothing(
double usmooth1, double usmooth2, double usmooth3)
{
_usmooth1 = usmooth1;
_usmooth2 = usmooth2;
_usmooth3 = usmooth3;
updateSmoothingFilters();
}
/**
* Sets the size and overlap of windows used for 3D image warping.
* Window size determines the amount of memory required to store
* a temporary array[l3][l2][n1][nl] of floats used to compute
* shifts. Here, nl is the number of lags and n1, l2 and l3 are
* the numbers of samples in the 1st, 2nd and 3rd dimensions of
* 3D image subsets. Let n1, n2 and n3 denote numbers of samples
* for all three dimensions of a complete 3D image. Typically,
* l2<n2 and l3<n3, because insufficient memory is available
* for a temporary array of nl*n1*n2*n3 floats.
*
* Image subsets overlap in the 2nd and 3rd dimensions by specified
* fractions f2 and f3, which must be less than one. Because window
* sizes are integers, the actual overlap be greater than (but never
* less than) these fractions.
*
* Default window sizes are 50 samples; default overlap fractions
* are 0.5, which corresponds to 50% overlap in both dimensions.
* @param l2 length of window in 2nd dimension.
* @param l3 length of window in 3rd dimension.
* @param f2 fraction of window overlap in 2nd dimension.
* @param f3 fraction of window overlap in 3rd dimension.
*/
public void setWindowSizeAndOverlap(int l2, int l3, double f2, double f3) {
_owl2 = l2;
_owl3 = l3;
_owf2 = f2;
_owf3 = f3;
}
/**
* Computes and returns shifts for specified sequences.
* @param f array for the sequence f.
* @param g array for the sequence g.
* @return array of shifts u.
*/
public float[] findShifts(float[] f, float[] g) {
float[] u = like(f);
findShifts(f,g,u);
return u;
}
/**
* Computes and returns shifts for specified images.
* @param f array for the image f.
* @param g array for the image g.
* @return array of shifts u.
*/
public float[][] findShifts(float[][] f, float[][] g) {
float[][] u = like(f);
findShifts(f,g,u);
return u;
}
/**
* Computes and returns shifts for specified images.
* @param f array for the image f.
* @param g array for the image g.
* @return array of shifts u.
*/
public float[][][] findShifts(float[][][] f, float[][][] g) {
float[][][] u = like(f);
findShifts(f,g,u);
return u;
}
/**
* Computes and returns 1D shifts u for specified 2D images f and g.
* This method is useful in the case that shifts vary only slightly
* (or perhaps not at all) in the 2nd image dimension.
* @param f array[n2][n1] for the image f.
* @param g array[n2][n1] for the image g.
* @return array[n1] of shifts u.
*/
public float[] findShifts1(float[][] f, float[][] g) {
float[] u = like(f[0]);
findShifts1(f,g,u);
return u;
}
/**
* Computes and returns 1D shifts u for specified 3D images f and g.
* This method is useful in the case that shifts vary only slightly
* (or perhaps not at all) in the 2nd and 3rd image dimensions.
* @param f array[n3][n2][n1] for the image f.
* @param g array[n3][n2][n1] for the image g.
* @return array[n1] of shifts u.
*/
public float[] findShifts1(float[][][] f, float[][][] g) {
float[] u = like(f[0][0]);
findShifts1(f,g,u);
return u;
}
/**
* Computes shifts for specified sequences.
* @param f input array for the sequence f.
* @param g input array for the sequence g.
* @param u output array of shifts u.
*/
public void findShifts(float[] f, float[] g, float[] u) {
float[][] e = computeErrors(f,g);
for (int is=0; is<_esmooth; ++is)
smoothErrors(e,e);
float[][] d = accumulateForward(e);
backtrackReverse(d,e,u);
smoothShifts(u,u);
}
/**
* Computes shifts for specified images.
* @param f input array for the image f.
* @param g input array for the image g.
* @param u output array of shifts u.
*/
public void findShifts(float[][] f, float[][] g, float[][] u) {
final float[][][] e = computeErrors(f,g);
final int nl = e[0][0].length;
final int n1 = e[0].length;
final int n2 = e.length;
final float[][] uf = u;
for (int is=0; is<_esmooth; ++is)
smoothErrors(e,e);
final Parallel.Unsafe du = new Parallel.Unsafe();
Parallel.loop(n2,new Parallel.LoopInt() {
public void compute(int i2) {
float[][] d = du.get();
if (d==null) du.set(d=new float[n1][nl]);
accumulateForward(e[i2],d);
backtrackReverse(d,e[i2],uf[i2]);
}});
smoothShifts(u,u);
}
/**
* Computes shifts for specified images.
* @param f input array for the image f.
* @param g input array for the image g.
* @param u output array of shifts u.
*/
public void findShifts(float[][][] f, float[][][] g, float[][][] u) {
int n1 = f[0][0].length;
int n2 = f[0].length;
int n3 = f.length;
OverlappingWindows2 ow =
new OverlappingWindows2(n2,n3,_owl2,_owl3,_owf2,_owf3);
int m2 = ow.getM1();
int m3 = ow.getM2();
int l2 = ow.getL1();
int l3 = ow.getL2();
float[][][] fw = new float[l3][l2][];
float[][][] gw = new float[l3][l2][];
float[][][] uw = new float[l3][l2][n1];
float[][][][] ew = new float[l3][l2][n1][_nl];
for (int k3=0; k3() {
public float[][] compute(int i2) {
float[][] e = new float[n1][nl];
computeErrors(ff[i2],gf[i2],e);
return e;
}
public float[][] combine(float[][] ea, float[][] eb) {
return add(ea,eb);
}});
normalizeErrors(e);
return e;
}
/**
* Returns normalized 1D alignment errors for 3D images.
* The number of lags nl = 1+shiftMax-shiftMin. Lag indices
* il = 0, 1, 2, ..., nl-1 correspond to integer shifts in
* [shiftMin,shiftMax].
* @param f array[n3][n2][n1] for the image f[i3][i2][i1].
* @param g array[n3][n2][n1] for the image g[i3][i2][i1].
* @return array[n1][nl] of alignment errors.
*/
public float[][] computeErrors1(float[][][] f, float[][][] g) {
final float[][][] ff = f;
final float[][][] gf = g;
final int nl = 1+_lmax-_lmin;
final int n1 = f[0][0].length;
final int n2 = f[0].length;
final int n3 = f.length;
float[][] e = Parallel.reduce(n2*n3,new Parallel.ReduceInt() {
public float[][] compute(int i23) {
int i2 = i23%n2;
int i3 = i23/n2;
float[][] e = new float[n1][nl];
computeErrors(ff[i3][i2],gf[i3][i2],e);
return e;
}
public float[][] combine(float[][] ea, float[][] eb) {
return add(ea,eb);
}});
normalizeErrors(e);
return e;
}
/**
* Returns smoothed (and normalized) alignment errors.
* @param e array[n1][nl] of alignment errors.
* @return array[n1][nl] of smoothed errors.
*/
public float[][] smoothErrors(float[][] e) {
float[][] es = like(e);
smoothErrors(e,es);
return es;
}
/**
* Returns smoothed (and normalized) alignment errors.
* @param e array[n2][n1][nl] of alignment errors.
* @return array[n2][n1][nl] of smoothed errors.
*/
public float[][][] smoothErrors(float[][][] e) {
float[][][] es = like(e);
smoothErrors(e,es);
return es;
}
/**
* Smooths (and normalizes) alignment errors.
* Input and output arrays can be the same array.
* @param e input array[n1][nl] of alignment errors.
* @param es output array[n1][nl] of smoothed errors.
*/
public void smoothErrors(float[][] e, float[][] es) {
smoothErrors1(_bstrain1,e,es);
normalizeErrors(es);
}
/**
* Smooths (and normalizes) alignment errors.
* Input and output arrays can be the same array.
* @param e input array[n2][n1][nl] of alignment errors.
* @param es output array[n2][n1][nl] of smoothed errors.
*/
public void smoothErrors(float[][][] e, float[][][] es) {
smoothErrors1(_bstrain1,e,es);
normalizeErrors(es);
smoothErrors2(_bstrain2,es,es);
normalizeErrors(es);
}
/**
* Smooths (and normalizes) alignment errors in only the 1st dimension.
* Input and output arrays can be the same array.
* @param e input array[n2][n1][nl] of alignment errors.
* @param es output array[n2][n1][nl] of smoothed errors.
*/
public void smoothErrors1(float[][][] e, float[][][] es) {
smoothErrors1(_bstrain1,e,es);
normalizeErrors(es);
}
/**
* Returns smoothed shifts.
* @param u array of shifts to be smoothed.
* @return array of smoothed shifts
*/
public float[] smoothShifts(float[] u) {
float[] us = like(u);
smoothShifts(u,us);
return us;
}
/**
* Returns smoothed shifts.
* @param u array of shifts to be smoothed.
* @return array of smoothed shifts
*/
public float[][] smoothShifts(float[][] u) {
float[][] us = like(u);
smoothShifts(u,us);
return us;
}
/**
* Smooths the specified shifts. Smoothing can be performed
* in place; input and output arrays can be the same array.
* @param u input array of shifts to be smoothed.
* @param us output array of smoothed shifts.
*/
public void smoothShifts(float[] u, float[] us) {
if (_ref1!=null) {
_ref1.apply(u,us);
} else if (u!=us) {
copy(u,us);
}
}
/**
* Smooths the specified shifts. Smoothing can be performed
* in place; input and output arrays can be the same array.
* @param u input array of shifts to be smoothed.
* @param us output array of smoothed shifts.
*/
public void smoothShifts(float[][] u, float[][] us) {
if (_ref1!=null) {
_ref1.apply1(u,us);
} else {
copy(u,us);
}
if (_ref2!=null)
_ref2.apply2(us,us);
}
/**
* Returns errors accumulated in forward direction.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][] accumulateForward(float[][] e) {
float[][] d = like(e);
accumulateForward(e,d);
return d;
}
/**
* Returns errors accumulated in reverse direction.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][] accumulateReverse(float[][] e) {
float[][] d = like(e);
accumulateReverse(e,d);
return d;
}
/**
* Returns errors accumulated in forward direction in 1st dimension.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][][] accumulateForward1(float[][][] e) {
float[][][] d = like(e);
accumulateForward1(e,d);
return d;
}
/**
* Returns errors accumulated in reverse direction in 1st dimension.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][][] accumulateReverse1(float[][][] e) {
float[][][] d = like(e);
accumulateReverse1(e,d);
return d;
}
/**
* Returns errors accumulated in forward direction in 2nd dimension.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][][] accumulateForward2(float[][][] e) {
float[][][] d = like(e);
accumulateForward2(e,d);
return d;
}
/**
* Returns errors accumulated in reverse direction in 2nd dimension.
* @param e array of alignment errors.
* @return array of accumulated errors.
*/
public float[][][] accumulateReverse2(float[][][] e) {
float[][][] d = like(e);
accumulateReverse2(e,d);
return d;
}
/**
* Accumulates alignment errors in forward direction.
* @param e input array of alignment errors.
* @param d output array of accumulated errors.
*/
public void accumulateForward(float[][] e, float[][] d) {
accumulate( 1,_bstrain1,e,d);
}
/**
* Accumulates alignment errors in reverse direction.
* @param e input array of alignment errors.
* @param d output array of accumulated errors.
*/
public void accumulateReverse(float[][] e, float[][] d) {
accumulate(-1,_bstrain1,e,d);
}
/**
* Accumulates alignment errors in forward direction in 1st dimension.
* @param e input array of alignment errors.
* @param d output array of accumulated errors.
*/
public void accumulateForward1(float[][][] e, float[][][] d) {
int n2 = e.length;
for (int i2=0; i2emax) emax = ei;
}
}
shiftAndScale(emin,emax,e);
}
/**
* Normalizes alignment errors to be in range [0,1].
* @param e input/output array of alignment errors.
*/
public static void normalizeErrors(float[][][] e) {
final float[][][] ef = e;
int n2 = e.length;
MinMax mm = Parallel.reduce(n2,new Parallel.ReduceInt() {
public MinMax compute(int i2) {
int nl = ef[i2][0].length;
int n1 = ef[i2].length;
float emin = Float.MAX_VALUE;
float emax = -Float.MAX_VALUE;
for (int i1=0; i1emax) emax = ei;
}
}
return new MinMax(emin,emax);
}
public MinMax combine(MinMax mm1, MinMax mm2) {
return new MinMax(min(mm1.emin,mm2.emin),max(mm1.emax,mm2.emax));
}});
shiftAndScale(mm.emin,mm.emax,e);
}
/**
* Returns the sum of errors for specified shifts, rounded to integers.
* @param e array[n1][nl] of errors.
* @param u array[n1] of shifts.
* @return the sum of errors.
*/
public float sumErrors(float[][] e, float[] u) {
int n1 = e.length;
int nl = e[0].length;
float ul = 0.5f-_lmin;
double sum = 0.0;
for (int i1=0; i1 i1 = -lmin-il
// j1 = n1-1 => i1 = n1-1-lmin-il
// Compute errors where indices are in bounds for both f and g.
for (int i1=0; i1emax)
emax = ei;
}
}
// If necessary, complete computation of average errors for each lag.
if (average) {
for (int il=0; il0)
eavg[il] /= navg[il];
}
}
// For indices where errors have not yet been computed, extrapolate.
for (int i1=0; i1=ilhi) {
if (average) {
if (navg[il]>0) {
e[i1][il] = eavg[il];
} else {
e[i1][il] = emax;
}
} else if (nearest || reflect) {
int k1 = (il0)?0:nim1;
int ie = (dir>0)?ni:-1;
int is = (dir>0)?1:-1;
for (int il=0; il0)?0:nim1;
int ie = (dir>0)?nim1:0;
int is = (dir>0)?1:-1;
int ii = ib;
int il = max(0,min(nlm1,-lmin));
float dl = d[ii][il];
for (int jl=1; jlemin)?1.0f/(emax-emin):1.0f;
for (int i1=0; i1emin)?1.0f/(emax-emin):1.0f;
final float[][][] ef = e;
Parallel.loop(n2,new Parallel.LoopInt() {
public void compute(int i2) {
int nl = ef[i2][0].length;
int n1 = ef[i2].length;
for (int i1=0; i1 eeu =
new Parallel.Unsafe();
Parallel.loop(n1,new Parallel.LoopInt() {
public void compute(int i1) {
float[][][] ee = eeu.get();
if (ee==null) eeu.set(ee=new float[4][n2][nl]);
float[][] e1 = ee[0];
float[][] es1 = ee[1];
float[][] ef1 = ee[2];
float[][] er1 = ee[3];
for (int i2=0; i2() {
public MinMax compute(int i3) {
float emin = Float.MAX_VALUE;
float emax = -Float.MAX_VALUE;
for (int i2=0; i2emax) emax = ei;
}
}
}
return new MinMax(emin,emax);
}
public MinMax combine(MinMax mm1, MinMax mm2) {
return new MinMax(min(mm1.emin,mm2.emin),max(mm1.emax,mm2.emax));
}});
shiftAndScale(mm.emin,mm.emax,e);
}
private static void shiftAndScale(float emin, float emax, float[][][][] e) {
final int nl = e[0][0][0].length;
final int n1 = e[0][0].length;
final int n2 = e[0].length;
final int n3 = e.length;
final float eshift = emin;
final float escale = (emax>emin)?1.0f/(emax-emin):1.0f;
final float[][][][] ef = e;
Parallel.loop(n3,new Parallel.LoopInt() {
public void compute(int i3) {
for (int i2=0; i2 du = new Parallel.Unsafe();
Parallel.loop(n3,new Parallel.LoopInt() {
public void compute(int i3) {
float[][] d = du.get();
if (d==null) du.set(d=new float[n1][nl]);
for (int i2=0; i2