001 package util.wavelet; 002 003 import static java.lang.Math.*; 004 import java.io.PrintStream; 005 006 import java.util.List; 007 import util.sdss.Quasar; 008 009 /** 010 * Discrete wavelet transform of a signal using the 1D A-Trous method with a b3-spline smoothing function. 011 * The noise for each position in the signal is also required. The WaveletTransform class computes the 012 * wavelet coefficients, the 1-sigma threshold for wavelet coefficients, the multiresolution support and 013 * it associated fields, structures and object trees for feature extraction and finally a set of signals 014 * corresponding to each individual object detected. 015 * Converted from the Feb 2004 distribution of the 016 * <a href="http://www.eso.org/projects/esomidas/">ESO/MIDAS</a> wavelet code 017 * originally written by Jean-Luc Starck in 25-Feb-2003. In particular version 19_1 of functions 018 * <b>wave_1d_trou()</b> and <b>wave_1d_algo_trou_rec()</b> 019 * located in the file <b>contrib/wavelet/libsrc/wave1d.c</b> available by FTP inside 020 * the gzip file <a href="ftp://ftphost.hq.eso.org/pub/midaspub/04FEB/04FEBpl1.1.tar.gz"> 021 * ftp://ftphost.hq.eso.org/pub/midaspub/04FEB/04FEBpl1.1.tar.gz</a>. 022 * For those interested in extending this class to support 2D DWT look into <b>pave_2d_tfo()</b> 023 * and <b>pave_2d_build()</b> located in the file <b>contrib/wavelet/libsrc/pave.c</b> 024 * <b>Reference:</b> Starck,J.-L., Murtagh, F., Bijaoui,A.: 1998, <i>Image Processing and Data Analysis</i>, 025 * ISBN 0521599148 Cambridge Univ. Press. 026 * 027 * @author John Talbot 028 */ 029 030 public class WaveletTransform { 031 032 /** Wavelet coefficients */ 033 public float[][] w; 034 /** The 1-sigma threshold for wavelet coefficients */ 035 public float[][] threshold; 036 037 /** Field labels for contiguous sets of above threshold wavelet coefficients. */ 038 public int[][] fieldLabel = null; 039 040 /** A list of Structures in this wavelet transform */ 041 public java.util.List<Structure> structures = new java.util.ArrayList<Structure>(300); 042 043 /** 044 * Construct a wavelet transform from a signal with non-uniform noise using 045 * the 1D A-Trous method with a b3-spline smoothing function. <b>Output</b> : 046 * The coefficients of the wavelet transform is in w[0][] to w[scales-2][]. 047 * The last row of this array is the smoothed array (w[scales-1][]). 048 * The threshold[][] array is the 1-sigma threshold. 049 * The support[][] array is the multiresolution support. 050 * 051 * @param signal array of float values representing a discrete signal 052 * @param noise the standard deviation of the signal 053 * @param scales number of wavelet scales (if scales==0 then the following equation is solved 2^(scales+1) = 3/4 signal array length) 054 * @param boundaryCondition treatment of boundary conditions 055 */ 056 public WaveletTransform(float[] signal, float[] noise, int scales, BoundaryCondition boundaryCondition) { 057 int n = signal.length; 058 if (scales == 0) scales = 1 + (int) (log( 3.0d * ((double)n) / 4.0d) / log(2.0d)); 059 if (scales < 1) scales = 1; 060 w = new float[scales][n]; 061 threshold = new float[scales][n]; 062 fieldLabel= new int[scales][n]; 063 064 float[] smooth = new float[n]; 065 float[] sigma = getStandardDeviation(n); 066 System.arraycopy(signal, 0, smooth, 0, n); 067 for (int scale = 0; scale < scales - 1; scale++) { 068 System.arraycopy(smooth, 0, w[scale], 0, n); 069 int step = (int)(pow(2.0d, (double) scale) + 0.5d); 070 int step2= step * 2; 071 for (int i = 0; i < n; i++) { 072 int indexMinus2 = boundaryCondition.test(i - step2, n); 073 int indexMinus1 = boundaryCondition.test(i - step, n); 074 int indexPlus1 = boundaryCondition.test(i + step, n); 075 int indexPlus2 = boundaryCondition.test(i + step2, n); 076 // smooth with kernel (1/16, 1/4, 3/8, 1/4, 1/16) 077 smooth[i] = 0.375f * w[scale][i] 078 + 0.25f * ( w[scale][indexMinus1] + w[scale][indexPlus1]) 079 + 0.0625f * ( w[scale][indexMinus2] + w[scale][indexPlus2]); 080 // TODO: Create an enum of both methods for computing the noise 081 float maxNoise = 0.0f; 082 for (int j = i - step2; j <= i + step2; j++) // find maximum noise in signal 083 maxNoise = max(maxNoise, noise[boundaryCondition.test(j, n)]); 084 085 threshold[scale][i] = sigma[scale] * maxNoise; 086 } 087 for (int i = 0; i < n; i++) w[scale][i] -= smooth[i]; 088 } 089 System.arraycopy(smooth, 0, w[scales - 1], 0, n); // copy smoothed array into last row 090 } 091 092 /** 093 * Reconstruct a signal from a wavelet transform. (see Starck 1998 p.24) 094 * @param nsigma the threshold in units of standard deviation (e.g. 3 is typical, 0 is equivalent to keeping all data) 095 * @param positive if true extract objects that have wavelets coefficients exceeding the threshold, otherwise extract negative features only 096 * @return a reconstructed signal array of float values 097 */ 098 public float[] reconstruct(float nsigma, boolean positive) { 099 getFields(nsigma, positive); 100 int n = w[0].length; 101 float[] signal = new float[n]; // automatically filled with 0 in constructor 102 for (int scale = w.length - 1; scale > 0; scale--) // never include the first wavelet scale, its mostly noise 103 for (int i = 0; i < n; i++) 104 if (scale == w.length - 1 || abs(w[scale][i]) > (nsigma * threshold[scale][i])) 105 signal[i] += w[scale][i]; 106 107 return signal; 108 } 109 110 // each scale has a List of Structure instances 111 112 /** 113 * Computes the field labels for contiguous sets of above threshold wavelet coefficients. 114 * @param nsigma the significance level in number of sigma units 115 * @param positive restriction to positive significant wavelet coefficients otherwise negative only 116 */ 117 protected void getFields(float nsigma, boolean positive) { 118 int scales = w.length; 119 int n = w[0].length; 120 int currentLabel = 1; 121 for (int scale = 0; scale < scales - 1; scale++) { 122 boolean inField = false; 123 boolean fields = false; 124 float extremum = 0.0f; 125 int extremumPosition = 0; 126 for (int i = 0; i < n; i++) { 127 int start = 0; 128 if (w[scale][i] > nsigma * threshold[scale][i]) { 129 if (inField == false) { 130 start = i; 131 inField = true; 132 extremum = w[scale][i]; 133 extremumPosition = i; 134 fields = true; // indicates that there are fields at this scale 135 } 136 fieldLabel[scale][i] = currentLabel; 137 float currentValue = w[scale][i]; 138 if (currentValue > extremum) { 139 extremum = currentValue; 140 extremumPosition = i; 141 } 142 } else if (!positive && w[scale][i] < -nsigma * threshold[scale][i]) { 143 if (inField == false) { 144 start = i; 145 inField = true; 146 extremum = w[scale][i]; 147 extremumPosition = i; 148 fields = true; // indicates that there are fields at this scale 149 } 150 fieldLabel[scale][i] = currentLabel; 151 float currentValue = w[scale][i]; 152 if (currentValue < extremum) extremum = currentValue; 153 } else { 154 fieldLabel[scale][i] = 0; 155 if (inField) { 156 int end = i - 1; 157 //structures.add(new Structure(scale, start, end, currentLabel, extremumPosition)); 158 currentLabel++; 159 inField = false; 160 } 161 } 162 } 163 // if there were fields at this scale ensure that we start with a fresh label 164 if (fields) currentLabel++; 165 } 166 } 167 168 /** 169 * Get the multiresolution support for this wavelet transform by setting 170 * an array of integers set to 1 for significant wavelet coefficients 171 * (those which exceed the noise) 0 otherwise. (see Starck 1998 p.55). 172 * threshold wavelet coefficients. 173 * @param positive restriction to positive significant wavelet coefficients otherwise negative only 174 */ 175 protected float[][] getSupport(float nsigma, boolean positive) { 176 int scales = w.length; 177 int n = w[0].length; 178 float[][] support = new float[scales][n]; 179 for (int scale = 0; scale < scales - 1; scale++) 180 for (int i = 0; i < n; i++) 181 if (w[scale][i] > nsigma * threshold[scale][i]) 182 support[scale][i] = 1; 183 else if (!positive && w[scale][i] < -nsigma * threshold[scale][i]) 184 support[scale][i] = 1; 185 else 186 support[scale][i] = 0; 187 return support; 188 } 189 190 191 /** 192 * Get the multiresolution support visualization for this wavelet transform 193 * as a 1D GIF image. (see Starck 1998 p.56) 194 * Optionally allow negative mr values to produce a reduction in grey intensity. 195 * 196 * @param positive only check for positive significant wavelet coefficients 197 * @return multiresolution support 198 */ 199 public static int[] getMultiResolutionSupportVisualization(float[][] support, boolean positive) { 200 int scales = support.length; 201 int n = support[0].length; 202 int[] visual = new int[n]; // automatically set to zero by Java 203 int power; 204 if (positive) power = 1; 205 else power = (int)pow(2.0d, (double)scales); 206 207 for (int scale = 0; scale < scales - 1; scale++) { 208 for (int i = 0; i < n; i++) 209 visual[i] += power * support[scale][i]; 210 211 power *= 2; 212 } 213 return visual; 214 } 215 216 /** 217 * Discrete wavelet transform using the 1D A-Trous method with a b3-spline smoothing function. 218 * (see Starck 1998 p.21-26) 219 * 220 * @param signal array of float values representing a discrete signal 221 * @param scales number of wavelet scales (if scales==0 then the following equation is solved 2^(scales+1) = 3/4 signal array length) 222 * @param boundaryCondition treatment of boundary conditions 223 * @return the wavelet transform as a 2-dimensional array of floats with the last row equal to the smoothed array 224 */ 225 public static float[][] transform(float[] signal, int scales, BoundaryCondition boundaryCondition) { 226 int n = signal.length; 227 if (scales == 0) scales = 1 + (int) (log( 3.0d * ((double)n) / 4.0d) / log(2.0d)); 228 if (scales < 1) scales = 1; 229 float[][] wavelet = new float[scales][n]; 230 float[] smooth = new float[n]; 231 System.arraycopy(signal, 0, smooth, 0, n); 232 for (int scale = 0; scale < scales - 1; scale++) { 233 System.arraycopy(smooth, 0, wavelet[scale], 0, n); 234 int step = (int)(pow(2.0d, (double) scale) + 0.5d); 235 int step2= step * 2; 236 for (int i = 0; i < n; i++) { 237 int indexMinus2 = boundaryCondition.test(i - step2, n); 238 int indexMinus1 = boundaryCondition.test(i - step, n); 239 int indexPlus1 = boundaryCondition.test(i + step, n); 240 int indexPlus2 = boundaryCondition.test(i + step2, n); 241 // smooth with kernel (1/16, 1/4, 3/8, 1/4, 1/16) 242 smooth[i] = 0.375f * wavelet[scale][i] 243 + 0.25f * ( wavelet[scale][indexMinus1] + wavelet[scale][indexPlus1]) 244 + 0.0625f * ( wavelet[scale][indexMinus2] + wavelet[scale][indexPlus2]); 245 } 246 for (int i = 0; i < n; i++) wavelet[scale][i] -= smooth[i]; 247 } 248 System.arraycopy(smooth, 0, wavelet[scales - 1], 0, n); // copy smoothed array into last row 249 return wavelet; 250 } 251 252 /** 253 * Get the continuum of a signal. The continuum is computed by iteratively substracting 254 * it from the signal; the iteration is required due to border problems. (see Starck 1998 p.127). 255 * 256 * @param signal array of float values representing a discrete signal 257 * @return the continuum of the signal 258 */ 259 public static float[] getContinuum(float[] signal) { 260 int n = signal.length; 261 float[] continuum = new float[n]; // array automatically zeroed by Java VM 262 float[] flattenedSignal = new float[n]; 263 for (int iteration = 0; iteration < 5; iteration++) { 264 for (int i = 0; i < n; i++) flattenedSignal[i] = signal[i] - continuum[i]; 265 float[][] wavelet = transform(flattenedSignal, 0, BoundaryCondition.Mirror); 266 int scales = wavelet.length; 267 for (int i = 0; i < n; i++) continuum[i] += wavelet[scales - 1][i]; 268 } 269 return continuum; 270 } 271 272 public static void main(String[] args) { 273 float[] signal = null; 274 float[] noise = null; 275 Quasar quasar = null; 276 try { 277 List quasars = Quasar.readQuasars(); 278 quasar = (Quasar) quasars.get(3); 279 //quasar.showSpectra(); 280 signal = quasar.getSpectra(); 281 noise = quasar.getNoise(); 282 for (int i = 0; i < signal.length; i++) if (signal[i] < 0f) signal[i] = 0f; 283 } catch (java.io.IOException e) { 284 System.out.println(e); 285 System.exit(0); 286 } 287 WaveletTransform wt = new WaveletTransform(signal, noise, 0, BoundaryCondition.Mirror); 288 //outputSignalAndWaveletTransform(System.out, noise, wt.s); 289 float[][] support = wt.getSupport(2f, true); 290 int[] visual = getMultiResolutionSupportVisualization(support, true); 291 float[] reconstruction = wt.reconstruct(2f, true); 292 float[] continuum = getContinuum(signal); 293 int n = visual.length; 294 int signals = wt.w.length; 295 for (int i = 0; i < n; i++) { 296 System.out.print(quasar.getWavelength(i) + " " + visual[i] + " " + signal[i] + " " + reconstruction[i] + " " + continuum[i] + " " + noise[i]); 297 //for (int j = 0; j < signals - 1; j++) System.out.print(mr[j][i] + " "); 298 System.out.println(); 299 } 300 301 /* 302 int n = 1200; 303 float[] signal = new float[n]; 304 for (int i = 0; i < n; i++) { 305 //Chirp 306 //signal[i] = (float) sin((double)PI * (double) (i*i) / (double) (40*signal.length)); 307 //pulse 308 //signal[i] = 0.0f; 309 //signal[n/2] = 1.0f; 310 // emission line 311 int peak = n/2; 312 int x = i - n/2; 313 signal[i] = (float) exp(- (double) (x*x) / (double) (50*50)); 314 } 315 316 float[][] wavelet = transform(signal, 0, BoundaryCondition.Mirror); 317 outputSignalAndWaveletTransform(System.out, signal, wavelet); 318 */ 319 320 321 } 322 323 //------------------------------------stats------------------------------------------- 324 325 static int iset = 0; 326 static float gset; 327 /** 328 * Get a normally distributed gaussian deviate with zero mean and unit variance. 329 * (see function gasdev(long *idnum) in Press (1992) Numerical Recipes in C, 2nd ed. page 289). 330 * 331 * @return normally distributed floating point number 332 */ 333 public static float getGaussianDeviate() { 334 if (iset == 0) { 335 float v1, v2, rsq; 336 do { 337 //TODO: use better random number generator like ran1(idnum) from Num.Rec.C 338 v1 = 2f * (float) random() - 1f; 339 v2 = 2f * (float) random() - 1f; 340 rsq = v1*v1 + v2*v2; 341 } while (rsq >= 1f || rsq == 0f); 342 float fac = (float) sqrt(-2d * log(rsq)/rsq); 343 gset = v1 * fac; 344 iset = 1; 345 return v2 * fac; 346 } else { 347 iset = 0; 348 return gset; 349 } 350 } 351 352 /** 353 * Get the a wavelet transform of a signal comprised of a normally distributed gaussian deviate 354 * @param n the size of the random signal 355 */ 356 public static float[][] getGaussianWaveletTransform(int n) { 357 float[] noise = new float[n]; 358 for (int i = 0; i < n; i++) { 359 noise[i] = getGaussianDeviate(); 360 } 361 float[][] wavelet = transform(noise, 0, BoundaryCondition.Mirror); 362 //outputSignalAndWaveletTransform(System.out, noise, wavelet); 363 return wavelet; 364 } 365 366 /** 367 * Get the standard deviation (sigma) at each scale in the wavelet transform 368 * of a random noise signal comprised of a normally distributed gaussian deviates. 369 * These sigmaNoise values can be used to estimate the sigma of a signal using : 370 * <ul> 371 * <li><b>sigma[scale] = sigma[0] * sigmaNoise[scale] / sigmaNoise[0]</b></li> 372 * </ul> 373 * where sigma[0] is estimated by equating it with the standard deviation of scale 0 374 * of the wavelet transform of a signal (i.e signalWavelet[0][i]). 375 * Then when the absolute value of signalWavelet[scale][i] is greater than 376 * 3*sigma[scale] we have a significant coefficient. 377 * If the signal to noise is non-uniform then another technique is used. 378 * 379 * @param size the size of the random signal 380 * @return a list of standard deviations at each scale of the wavelet transform of the noise signal 381 */ 382 public static float[] getStandardDeviation(int size) { 383 float[][] wavelet = getGaussianWaveletTransform(size); 384 385 int scales = wavelet.length; 386 float[] sigma = new float[scales]; 387 for (int j = 0; j < scales; j++) { 388 float sum = 0f; 389 float sum2 = 0f; 390 for (int i = 0; i < size; i++) { 391 float f = wavelet[j][i]; 392 sum += f; 393 sum2 += f*f; 394 } 395 sigma[j] = (float) sqrt((sum2 - sum * sum / (float)size )/ ((float)size - 1f) ); 396 } 397 //System.out.printf("%+10.4f", aFloatNumber); // BUG: this fails in JDK1.5.0 beta2! 398 //for (float s : sigma) System.out.println(" " + s); 399 //when n=1200 sigma[] = {0.750, 0.304, 0.177, 0.112, 0.077, 0.062, 0.045, 0.030, 0.039); 400 return sigma; 401 } 402 403 public static void outputSignalAndWaveletTransform(PrintStream out, float[] signal, float[][]wavelet) { 404 int n = wavelet[0].length; 405 for (int i = 0; i < n; i++) { 406 out.print(signal[i]); 407 for (int j = 0; j < wavelet.length; j++) out.print(" " + wavelet[j][i]); 408 out.println(); 409 } 410 } 411 412 public static void outputWaveletTransform(PrintStream out, float[][]wavelet) { 413 int n = wavelet[0].length; 414 for (int i = 0; i < n; i++) { 415 for (int j = 0; j < wavelet.length; j++) out.print(" " + wavelet[j][i]); 416 out.println(); 417 } 418 } 419 420 421 }