001/***************************************************************************** 002 * Copyright by The HDF Group. * 003 * Copyright by the Board of Trustees of the University of Illinois. * 004 * All rights reserved. * 005 * * 006 * This file is part of the HDF Java Products distribution. * 007 * The full copyright notice, including terms governing use, modification, * 008 * and redistribution, is contained in the files COPYING and Copyright.html. * 009 * COPYING can be found at the root of the source code distribution tree. * 010 * Or, see http://hdfgroup.org/products/hdf-java/doc/Copyright.html. * 011 * If you do not have access to either file, you may request a copy from * 012 * help@hdfgroup.org. * 013 ****************************************************************************/ 014 015package hdf.view; 016 017import java.awt.Image; 018import java.awt.Toolkit; 019import java.awt.image.BufferedImage; 020import java.awt.image.ColorModel; 021import java.awt.image.DataBufferInt; 022import java.awt.image.DirectColorModel; 023import java.awt.image.MemoryImageSource; 024import java.awt.image.PixelGrabber; 025import java.io.BufferedInputStream; 026import java.io.BufferedReader; 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.FileReader; 030import java.lang.reflect.Array; 031import java.lang.reflect.Constructor; 032import java.lang.reflect.Method; 033import java.math.BigInteger; 034import java.util.BitSet; 035import java.util.List; 036import java.util.StringTokenizer; 037 038import javax.imageio.ImageIO; 039import javax.swing.tree.DefaultMutableTreeNode; 040 041import hdf.object.Datatype; 042import hdf.object.FileFormat; 043import hdf.object.Group; 044import hdf.object.ScalarDS; 045import hdf.view.ViewProperties.BITMASK_OP; 046 047/** 048 * The "Tools" class contains various of tools for HDF files such as jpeg to HDF 049 * converter. 050 * 051 * @author Peter X. Cao 052 * @version 2.4 9/6/2007 053 */ 054public final class Tools { 055 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Tools.class); 056 057 public static final long MAX_INT8 = 127; 058 public static final long MAX_UINT8 = 255; 059 public static final long MAX_INT16 = 32767; 060 public static final long MAX_UINT16 = 65535; 061 public static final long MAX_INT32 = 2147483647; 062 public static final long MAX_UINT32 = 4294967295L; 063 public static final long MAX_INT64 = 9223372036854775807L; 064 public static final BigInteger MAX_UINT64 = new BigInteger("18446744073709551615"); 065 066 /** Key for JPEG image file type. */ 067 public static final String FILE_TYPE_JPEG = "JPEG"; 068 069 /** Key for TIFF image file type. */ 070 public static final String FILE_TYPE_TIFF = "TIFF"; 071 072 /** Key for PNG image file type. */ 073 public static final String FILE_TYPE_PNG = "PNG"; 074 075 /** Key for GIF image file type. */ 076 public static final String FILE_TYPE_GIF = "GIF"; 077 078 /** Key for BMP image file type. */ 079 public static final String FILE_TYPE_BMP = "BMP"; 080 081 /** Key for all image file type. */ 082 public static final String FILE_TYPE_IMAGE = "IMG"; 083 084 /** Print out debug information */ 085 public static final void debug(Object caller, Object msg) { 086 if (caller != null) System.out.println("*** " + caller.getClass().getName() + ": " + msg); 087 } 088 089 /** 090 * Converts an image file into HDF4/5 file. 091 * 092 * @param imgFileName 093 * the input image file. 094 * @param hFileName 095 * the name of the HDF4/5 file. 096 * @param fromType 097 * the type of image. 098 * @param toType 099 * the type of file converted to. 100 */ 101 public static void convertImageToHDF(String imgFileName, String hFileName, String fromType, String toType) 102 throws Exception { 103 File imgFile = null; 104 105 if (imgFileName == null) { 106 throw new NullPointerException("The source image file is null."); 107 } 108 else if (!(imgFile = new File(imgFileName)).exists()) { 109 throw new NullPointerException("The source image file does not exist."); 110 } 111 else if (hFileName == null) { 112 throw new NullPointerException("The target HDF file is null."); 113 } 114 115 if (!fromType.equals(FILE_TYPE_IMAGE)) { 116 throw new UnsupportedOperationException("Unsupported image type."); 117 } 118 else if (!(toType.equals(FileFormat.FILE_TYPE_HDF4) || toType.equals(FileFormat.FILE_TYPE_HDF5))) { 119 throw new UnsupportedOperationException("Unsupported destination file type."); 120 } 121 122 BufferedImage image = null; 123 try { 124 BufferedInputStream in = new BufferedInputStream(new FileInputStream(imgFileName)); 125 image = ImageIO.read(in); 126 in.close(); 127 } 128 catch (Throwable err) { 129 image = null; 130 } 131 132 if (image == null) throw new UnsupportedOperationException("Failed to read image: " + imgFileName); 133 134 int h = image.getHeight(); 135 int w = image.getWidth(); 136 byte[] data = null; 137 138 try { 139 data = new byte[3 * h * w]; 140 } 141 catch (OutOfMemoryError err) { 142 err.printStackTrace(); 143 throw new RuntimeException("Out of memory error."); 144 } 145 146 int idx = 0; 147 int rgb = 0; 148 for (int i = 0; i < h; i++) { 149 for (int j = 0; j < w; j++) { 150 rgb = image.getRGB(j, i); 151 data[idx++] = (byte) (rgb >> 16); 152 data[idx++] = (byte) (rgb >> 8); 153 data[idx++] = (byte) rgb; 154 } 155 } 156 157 long[] dims = null; 158 Datatype type = null; 159 Group pgroup = null; 160 String imgName = imgFile.getName(); 161 FileFormat newfile = null, thefile = null; 162 if (toType.equals(FileFormat.FILE_TYPE_HDF5)) { 163 thefile = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5); 164 long[] h5dims = { h, w, 3 }; // RGB pixel interlace 165 dims = h5dims; 166 } 167 else if (toType.equals(FileFormat.FILE_TYPE_HDF4)) { 168 thefile = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4); 169 long[] h4dims = { w, h, 3 }; // RGB pixel interlace 170 dims = h4dims; 171 } 172 else { 173 thefile = null; 174 } 175 176 if (thefile != null) { 177 newfile = thefile.createInstance(hFileName, FileFormat.CREATE); 178 newfile.open(); 179 pgroup = (Group) ((DefaultMutableTreeNode) newfile.getRootNode()).getUserObject(); 180 type = newfile.createDatatype(Datatype.CLASS_CHAR, 1, Datatype.NATIVE, Datatype.SIGN_NONE); 181 newfile.createImage(imgName, pgroup, type, dims, null, null, -1, 3, ScalarDS.INTERLACE_PIXEL, data); 182 newfile.close(); 183 } 184 185 // clean up memory 186 data = null; 187 image = null; 188 Runtime.getRuntime().gc(); 189 } 190 191 /** 192 * Save a BufferedImage into an image file. 193 * 194 * @param image 195 * the BufferedImage to save. 196 * @param file 197 * the image file. 198 * @param type 199 * the image type. 200 */ 201 public static void saveImageAs(BufferedImage image, File file, String type) throws Exception { 202 if (image == null) { 203 throw new NullPointerException("The source image is null."); 204 } 205 206 ImageIO.write(image, type, file); 207 } 208 209 /** 210 * Creates the gray palette of the indexed 256-color table. 211 * <p> 212 * The palette values are stored in a two-dimensional byte array and arrange 213 * by color components of red, green and blue. palette[][] = byte[3][256], 214 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 215 * blue components respectively. 216 * 217 * @return the gray palette in the form of byte[3][256] 218 */ 219 public static final byte[][] createGrayPalette() { 220 byte[][] p = new byte[3][256]; 221 222 for (int i = 0; i < 256; i++) { 223 p[0][i] = p[1][i] = p[2][i] = (byte) (i); 224 } 225 226 return p; 227 } 228 229 /** 230 * Creates the reverse gray palette of the indexed 256-color table. 231 * <p> 232 * The palette values are stored in a two-dimensional byte array and arrange 233 * by color components of red, green and blue. palette[][] = byte[3][256], 234 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 235 * blue components respectively. 236 * 237 * @return the gray palette in the form of byte[3][256] 238 */ 239 public static final byte[][] createReverseGrayPalette() { 240 byte[][] p = new byte[3][256]; 241 242 for (int i = 0; i < 256; i++) { 243 p[0][i] = p[1][i] = p[2][i] = (byte) (255 - i); 244 } 245 246 return p; 247 } 248 249 /** 250 * Creates the gray wave palette of the indexed 256-color table. 251 * <p> 252 * The palette values are stored in a two-dimensional byte array and arrange 253 * by color components of red, green and blue. palette[][] = byte[3][256], 254 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 255 * blue components respectively. 256 * 257 * @return the gray palette in the form of byte[3][256] 258 */ 259 public static final byte[][] createGrayWavePalette() { 260 byte[][] p = new byte[3][256]; 261 262 for (int i = 0; i < 256; i++) { 263 p[0][i] = p[1][i] = p[2][i] = (byte) (255 / 2 + (255 / 2) * Math.sin((i - 32) / 20.3)); 264 } 265 266 return p; 267 } 268 269 /** 270 * Creates the rainbow palette of the indexed 256-color table. 271 * <p> 272 * The palette values are stored in a two-dimensional byte array and arrange 273 * by color components of red, green and blue. palette[][] = byte[3][256], 274 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 275 * blue components respectively. 276 * 277 * @return the rainbow palette in the form of byte[3][256] 278 */ 279 public static final byte[][] createRainbowPalette() { 280 byte r, g, b; 281 byte[][] p = new byte[3][256]; 282 283 for (int i = 1; i < 255; i++) { 284 if (i <= 29) { 285 r = (byte) (129.36 - i * 4.36); 286 g = 0; 287 b = (byte) 255; 288 } 289 else if (i <= 86) { 290 r = 0; 291 g = (byte) (-133.54 + i * 4.52); 292 b = (byte) 255; 293 } 294 else if (i <= 141) { 295 r = 0; 296 g = (byte) 255; 297 b = (byte) (665.83 - i * 4.72); 298 } 299 else if (i <= 199) { 300 r = (byte) (-635.26 + i * 4.47); 301 g = (byte) 255; 302 b = 0; 303 } 304 else { 305 r = (byte) 255; 306 g = (byte) (1166.81 - i * 4.57); 307 b = 0; 308 } 309 310 p[0][i] = r; 311 p[1][i] = g; 312 p[2][i] = b; 313 } 314 315 p[0][0] = p[1][0] = p[2][0] = 0; 316 p[0][255] = p[1][255] = p[2][255] = (byte) 255; 317 318 return p; 319 } 320 321 /** 322 * Creates the nature palette of the indexed 256-color table. 323 * <p> 324 * The palette values are stored in a two-dimensional byte array and arrange 325 * by color components of red, green and blue. palette[][] = byte[3][256], 326 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 327 * blue components respectively. 328 * 329 * @return the nature palette in the form of byte[3][256] 330 */ 331 public static final byte[][] createNaturePalette() { 332 byte[][] p = new byte[3][256]; 333 334 for (int i = 1; i < 210; i++) { 335 p[0][i] = (byte) ((Math.sin((double) (i - 5) / 16) + 1) * 90); 336 p[1][i] = (byte) ((1 - Math.sin((double) (i - 30) / 12)) * 64 * (1 - (double) i / 255) + 128 - i / 2); 337 p[2][i] = (byte) ((1 - Math.sin((double) (i - 8) / 9)) * 110 + 30); 338 } 339 340 for (int i = 210; i < 255; i++) { 341 p[0][i] = (byte) 80; 342 p[1][i] = (byte) 0; 343 p[2][i] = (byte) 200; 344 } 345 346 p[0][0] = p[1][0] = p[2][0] = 0; 347 p[0][255] = p[1][255] = p[2][255] = (byte) 255; 348 349 return p; 350 } 351 352 /** 353 * Creates the wave palette of the indexed 256-color table. 354 * <p> 355 * The palette values are stored in a two-dimensional byte array and arrange 356 * by color components of red, green and blue. palette[][] = byte[3][256], 357 * where, palette[0][], palette[1][] and palette[2][] are the red, green and 358 * blue components respectively. 359 * 360 * @return the wave palette in the form of byte[3][256] 361 */ 362 public static final byte[][] createWavePalette() { 363 byte[][] p = new byte[3][256]; 364 365 for (int i = 1; i < 255; i++) { 366 p[0][i] = (byte) ((Math.sin(((double) i / 40 - 3.2)) + 1) * 128); 367 p[1][i] = (byte) ((1 - Math.sin((i / 2.55 - 3.1))) * 70 + 30); 368 p[2][i] = (byte) ((1 - Math.sin(((double) i / 40 - 3.1))) * 128); 369 } 370 371 p[0][0] = p[1][0] = p[2][0] = 0; 372 p[0][255] = p[1][255] = p[2][255] = (byte) 255; 373 374 return p; 375 } 376 377 /** 378 * read an image palette from a file. 379 * 380 * A palette file has format of (value, red, green, blue). The color value 381 * in palette file can be either unsigned char [0..255] or float [0..1]. 382 * Float value will be converted to [0..255]. 383 * 384 * The color table in file can have any number of entries between 2 to 256. 385 * It will be converted to a color table of 256 entries. Any missing index 386 * will calculated by linear interpolation between the neighboring index 387 * values. For example, index 11 is missing in the following table 10 200 60 388 * 20 12 100 100 60 Index 11 will be calculated based on index 10 and index 389 * 12, i.e. 11 150 80 40 390 * 391 * @param filename 392 * the name of the palette file. 393 * 394 * @return the wave palette in the form of byte[3][256] 395 */ 396 public static final byte[][] readPalette(String filename) { 397 final int COLOR256 = 256; 398 BufferedReader in = null; 399 String line = null; 400 int nentries = 0, i, j, idx; 401 float v, r, g, b, ratio, max_v, min_v, max_color, min_color; 402 float[][] tbl = new float[COLOR256][4]; /* value, red, green, blue */ 403 404 if (filename == null) return null; 405 406 try { 407 in = new BufferedReader(new FileReader(filename)); 408 } 409 catch (Exception ex) { 410 log.debug("input file:", ex); 411 in = null; 412 } 413 414 if (in == null) return null; 415 416 idx = 0; 417 v = r = g = b = ratio = max_v = min_v = max_color = min_color = 0; 418 do { 419 try { 420 line = in.readLine(); 421 } 422 catch (Exception ex) { 423 log.debug("input file:", ex); 424 line = null; 425 } 426 427 if (line == null) continue; 428 429 StringTokenizer st = new StringTokenizer(line); 430 431 // invalid line 432 if (st.countTokens() != 4) { 433 continue; 434 } 435 436 try { 437 v = Float.valueOf(st.nextToken()); 438 r = Float.valueOf(st.nextToken()); 439 g = Float.valueOf(st.nextToken()); 440 b = Float.valueOf(st.nextToken()); 441 } 442 catch (NumberFormatException ex) { 443 log.debug("input file:", ex); 444 continue; 445 } 446 447 tbl[idx][0] = v; 448 tbl[idx][1] = r; 449 tbl[idx][2] = g; 450 tbl[idx][3] = b; 451 452 if (idx == 0) { 453 max_v = min_v = v; 454 max_color = min_color = r; 455 } 456 457 max_v = Math.max(max_v, v); 458 max_color = Math.max(max_color, r); 459 max_color = Math.max(max_color, g); 460 max_color = Math.max(max_color, b); 461 462 min_v = Math.min(min_v, v); 463 min_color = Math.min(min_color, r); 464 min_color = Math.min(min_color, g); 465 min_color = Math.min(min_color, b); 466 467 idx++; 468 if (idx >= COLOR256) break; /* only support to 256 colors */ 469 } while (line != null); 470 471 try { 472 in.close(); 473 } 474 catch (Exception ex) { 475 log.debug("input file:", ex); 476 } 477 478 nentries = idx; 479 if (nentries <= 1) // must have more than one entries 480 return null; 481 482 // convert color table to byte 483 nentries = idx; 484 if (max_color <= 1) { 485 ratio = (min_color == max_color) ? 1.0f : ((COLOR256 - 1.0f) / (max_color - min_color)); 486 487 for (i = 0; i < nentries; i++) { 488 for (j = 1; j < 4; j++) 489 tbl[i][j] = (tbl[i][j] - min_color) * ratio; 490 } 491 } 492 493 // convert table to 256 entries 494 idx = 0; 495 ratio = (min_v == max_v) ? 1.0f : ((COLOR256 - 1.0f) / (max_v - min_v)); 496 497 int[][] p = new int[3][COLOR256]; 498 for (i = 0; i < nentries; i++) { 499 idx = (int) ((tbl[i][0] - min_v) * ratio); 500 for (j = 0; j < 3; j++) 501 p[j][idx] = (int) tbl[i][j + 1]; 502 } 503 504 /* linear interpolating missing values in the color table */ 505 for (i = 1; i < COLOR256; i++) { 506 if ((p[0][i] + p[1][i] + p[2][i]) == 0) { 507 j = i + 1; 508 509 // figure out number of missing points between two given points 510 while (j < COLOR256 && (p[0][j] + p[1][j] + p[2][j]) == 0) 511 j++; 512 513 if (j >= COLOR256) break; // nothing in the table to interpolating 514 515 float d1 = (p[0][j] - p[0][i - 1]) / (j - i); 516 float d2 = (p[1][j] - p[1][i - 1]) / (j - i); 517 float d3 = (p[2][j] - p[2][i - 1]) / (j - i); 518 519 for (int k = i; k <= j; k++) { 520 p[0][k] = (int) (p[0][i - 1] + d1 * (k - i + 1)); 521 p[1][k] = (int) (p[1][i - 1] + d2 * (k - i + 1)); 522 p[2][k] = (int) (p[2][i - 1] + d3 * (k - i + 1)); 523 } 524 i = j + 1; 525 } // if ((p[0][i] + p[1][i] + p[2][i]) == 0) 526 } // for (i = 1; i < COLOR256; i++) { 527 528 byte[][] pal = new byte[3][COLOR256]; 529 for (i = 1; i < COLOR256; i++) { 530 for (j = 0; j < 3; j++) 531 pal[j][i] = (byte) (p[j][i]); 532 } 533 534 return pal; 535 } 536 537 /** 538 * This method returns true if the specified image has transparent pixels. 539 * 540 * @param image 541 * the image to be check if has alpha. 542 * @return true if the image has alpha setting. 543 */ 544 public static boolean hasAlpha(Image image) { 545 if (image == null) { 546 return false; 547 } 548 549 // If buffered image, the color model is readily available 550 if (image instanceof BufferedImage) { 551 BufferedImage bimage = (BufferedImage) image; 552 return bimage.getColorModel().hasAlpha(); 553 } 554 555 // Use a pixel grabber to retrieve the image's color model; 556 // grabbing a single pixel is usually sufficient 557 PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); 558 try { 559 pg.grabPixels(); 560 } 561 catch (InterruptedException e) { 562 log.debug("transparent pixels:", e); 563 } 564 ColorModel cm = pg.getColorModel(); 565 566 return cm.hasAlpha(); 567 } 568 569 /** 570 * Creates a RGB indexed image of 256 colors. 571 * 572 * @param imageData 573 * the byte array of the image data. 574 * @param palette 575 * the color lookup table. 576 * @param w 577 * the width of the image. 578 * @param h 579 * the height of the image. 580 * @return the image. 581 */ 582 public static Image createIndexedImage(BufferedImage bufferedImage, byte[] imageData, byte[][] palette, int w, int h) 583 { 584 if (imageData==null || w<=0 || h<=0) 585 return null; 586 587 if (palette==null) 588 palette = Tools.createGrayPalette(); 589 590 if (bufferedImage == null) 591 bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 592 593 final int[] pixels = ( (DataBufferInt) bufferedImage.getRaster().getDataBuffer() ).getData(); 594 int len = pixels.length; 595 596 for (int i=0; i<len; i++) { 597 int idx = imageData[i] & 0xff; 598 int r = ((int)(palette[0][idx] & 0xff))<<16; 599 int g = ((int)(palette[1][idx] & 0xff))<<8; 600 int b = palette[2][idx] & 0xff; 601 602 pixels[i] = 0xff000000 | r | g | b; 603 } 604 605 return bufferedImage; 606 } 607 608 /** 609 * Creates a true color image. 610 * <p> 611 * DirectColorModel is used to construct the image from raw data. The 612 * DirectColorModel model is similar to an X11 TrueColor visual, which has 613 * the following parameters: <br> 614 * 615 * <pre> 616 * Number of bits: 32 617 * Red mask: 0x00ff0000 618 * Green mask: 0x0000ff00 619 * Blue mask: 0x000000ff 620 * Alpha mask: 0xff000000 621 * Color space: sRGB 622 * isAlphaPremultiplied: False 623 * Transparency: Transparency.TRANSLUCENT 624 * transferType: DataBuffer.TYPE_INT 625 * </pre> 626 * <p> 627 * The data may be arranged in one of two ways: by pixel or by plane. In 628 * both cases, the dataset will have a dataspace with three dimensions, 629 * height, width, and components. 630 * <p> 631 * For HDF4, the interlace modes specify orders for the dimensions as: 632 * 633 * <pre> 634 * INTERLACE_PIXEL = [width][height][pixel components] 635 * INTERLACE_PLANE = [pixel components][width][height] 636 * </pre> 637 * <p> 638 * For HDF5, the interlace modes specify orders for the dimensions as: 639 * 640 * <pre> 641 * INTERLACE_PIXEL = [height][width][pixel components] 642 * INTERLACE_PLANE = [pixel components][height][width] 643 * </pre> 644 * <p> 645 * 646 * @param imageData 647 * the byte array of the image data. 648 * @param planeInterlace 649 * flag if the image is plane intelace. 650 * @param w 651 * the width of the image. 652 * @param h 653 * the height of the image. 654 * @return the image. 655 */ 656 public static Image createTrueColorImage(byte[] imageData, boolean planeInterlace, int w, int h) { 657 Image theImage = null; 658 int imgSize = w * h; 659 int packedImageData[] = new int[imgSize]; 660 int pixel = 0, idx = 0, r = 0, g = 0, b = 0; 661 for (int i = 0; i < h; i++) { 662 for (int j = 0; j < w; j++) { 663 pixel = r = g = b = 0; 664 if (planeInterlace) { 665 r = imageData[idx]; 666 g = imageData[imgSize + idx]; 667 b = imageData[imgSize * 2 + idx]; 668 } 669 else { 670 r = imageData[idx * 3]; 671 g = imageData[idx * 3 + 1]; 672 b = imageData[idx * 3 + 2]; 673 } 674 675 r = (r << 16) & 0x00ff0000; 676 g = (g << 8) & 0x0000ff00; 677 b = b & 0x000000ff; 678 679 // bits packed into alpha (1), red (r), green (g) and blue (b) 680 // as 11111111rrrrrrrrggggggggbbbbbbbb 681 pixel = 0xff000000 | r | g | b; 682 packedImageData[idx++] = pixel; 683 } // for (int j=0; j<w; j++) 684 } // for (int i=0; i<h; i++) 685 686 DirectColorModel dcm = (DirectColorModel) ColorModel.getRGBdefault(); 687 theImage = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(w, h, dcm, packedImageData, 0, w)); 688 689 packedImageData = null; 690 691 return theImage; 692 } 693 694 /** 695 * Convert an array of raw data into array of a byte data. 696 * <p> 697 * 698 * @param rawData 699 * The input raw data. 700 * @param minmax 701 * the range of the raw data. 702 * @return the byte array of pixel data. 703 */ 704 public static byte[] getBytes(Object rawData, double[] minmax, int w, int h, boolean isTransposed, byte[] byteData) { 705 return Tools.getBytes(rawData, minmax, w, h, isTransposed, null, false, byteData); 706 } 707 708 public static byte[] getBytes(Object rawData, double[] minmax, int w, int h, boolean isTransposed, 709 List<Number> invalidValues, byte[] byteData) { 710 return getBytes(rawData, minmax, w, h, isTransposed, invalidValues, false, byteData); 711 } 712 713 public static byte[] getBytes(Object rawData, double[] minmax, int w, int h, boolean isTransposed, 714 List<Number> invalidValues, boolean convertByteData, byte[] byteData) { 715 return getBytes(rawData, minmax, w, h, isTransposed,invalidValues, convertByteData, byteData, null); 716 } 717 718 /** 719 * Convert an array of raw data into array of a byte data. 720 * <p> 721 * 722 * @param rawData 723 * The input raw data. 724 * @param minmax 725 * the range of the raw data. 726 * @param isTransposed 727 * if the data is transposeed 728 * @return the byte array of pixel data. 729 */ 730 public static byte[] getBytes(Object rawData, double[] minmax, int w, int h, boolean isTransposed, 731 List<Number> invalidValues, boolean convertByteData, byte[] byteData, List<Integer> list) 732 { 733 double fillValue[] = null; 734 735 // no input data 736 if (rawData == null || w<=0 || h<=0) { 737 return null; 738 } 739 740 // input data is not an array 741 if (!rawData.getClass().isArray()) { 742 return null; 743 } 744 745 double min = Double.MAX_VALUE, max = -Double.MAX_VALUE, ratio = 1.0d; 746 String cname = rawData.getClass().getName(); 747 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 748 int size = Array.getLength(rawData); 749 750 if (minmax == null) { 751 minmax = new double[2]; 752 minmax[0] = minmax[1] = 0; 753 } 754 755 if (dname == 'B') { 756 return convertByteData((byte[]) rawData, minmax, w, h, isTransposed, fillValue, convertByteData, byteData, list); 757 } 758 759 if ((byteData == null) || (size != byteData.length)) { 760 byteData = new byte[size]; // reuse the old buffer 761 } 762 763 if (minmax[0] == minmax[1]) { 764 Tools.findMinMax(rawData, minmax, fillValue); 765 } 766 767 min = minmax[0]; 768 max = minmax[1]; 769 770 if (invalidValues!=null && invalidValues.size()>0) { 771 int n = invalidValues.size(); 772 fillValue = new double[n]; 773 for (int i=0; i<n; i++) { 774 fillValue[i] = invalidValues.get(i).doubleValue(); 775 } 776 } 777 ratio = (min == max) ? 1.00d : (double) (255.00 / (max - min)); 778 int idxSrc = 0, idxDst = 0; 779 switch (dname) { 780 case 'S': 781 short[] s = (short[]) rawData; 782 for (int i = 0; i < h; i++) { 783 for (int j = 0; j < w; j++) { 784 idxSrc = idxDst =j * h + i; 785 if (isTransposed) idxDst = i * w + j; 786 byteData[idxDst] = toByte(s[idxSrc], ratio, min, max, fillValue, idxSrc, list); 787 } 788 } 789 break; 790 791 case 'I': 792 int[] ia = (int[]) rawData; 793 for (int i = 0; i < h; i++) { 794 for (int j = 0; j < w; j++) { 795 idxSrc = idxDst =j * h + i; 796 if (isTransposed) idxDst = i * w + j; 797 byteData[idxDst] = toByte(ia[idxSrc], ratio, min, max, fillValue, idxSrc, list); 798 } 799 } 800 break; 801 802 case 'J': 803 long[] l = (long[]) rawData; 804 for (int i = 0; i < h; i++) { 805 for (int j = 0; j < w; j++) { 806 idxSrc = idxDst =j * h + i; 807 if (isTransposed) idxDst = i * w + j; 808 byteData[idxDst] = toByte(l[idxSrc], ratio, min, max, fillValue, idxSrc, list); 809 } 810 } 811 break; 812 813 case 'F': 814 float[] f = (float[]) rawData; 815 for (int i = 0; i < h; i++) { 816 for (int j = 0; j < w; j++) { 817 idxSrc = idxDst =j * h + i; 818 if (isTransposed) idxDst = i * w + j; 819 byteData[idxDst] = toByte(f[idxSrc], ratio, min, max, fillValue, idxSrc, list); 820 } 821 } 822 break; 823 824 case 'D': 825 double[] d = (double[]) rawData; 826 for (int i = 0; i < h; i++) { 827 for (int j = 0; j < w; j++) { 828 idxSrc = idxDst =j * h + i; 829 if (isTransposed) idxDst = i * w + j; 830 byteData[idxDst] = toByte(d[idxSrc], ratio, min, max, fillValue, idxSrc, list); 831 } 832 } 833 break; 834 835 default: 836 byteData = null; 837 break; 838 } // switch (dname) 839 840 return byteData; 841 } 842 843 private static byte toByte(double in, double ratio, double min, double max, double[] fill, int idx, List<Integer> list) 844 { 845 byte out = 0; 846 847 if (in < min || in > max || isFillValue(in, fill) || isNaNINF(in)) { 848 out = 0; 849 if (list!=null) 850 list.add(idx); 851 } 852 else 853 out = (byte) ((in-min)*ratio); 854 855 return out; 856 } 857 858 private static boolean isFillValue(double in, double[] fill) { 859 860 if (fill==null) 861 return false; 862 863 for (int i=0; i<fill.length; i++) { 864 if (fill[i] == in) 865 return true; 866 } 867 868 return false; 869 } 870 871 private static byte[] convertByteData(byte[] rawData, double[] minmax, int w, int h, boolean isTransposed, 872 Object fillValue, boolean convertByteData, byte[] byteData, List<Integer> list) { 873 double min = Double.MAX_VALUE, max = -Double.MAX_VALUE, ratio = 1.0d; 874 875 if (rawData == null) return null; 876 877 if (convertByteData) { 878 if (minmax[0] == minmax[1]) { 879 Tools.findMinMax(rawData, minmax, fillValue); 880 } 881 } 882 883 if (minmax[0] == 0 && minmax[1] == 255) convertByteData = false; // no need to convert data 884 885 // no conversion and no transpose 886 if (!convertByteData && !isTransposed) { 887 if (byteData != null && byteData.length == rawData.length) { 888 System.arraycopy(rawData, 0, byteData, 0, rawData.length); 889 return byteData; 890 } 891 892 return rawData; 893 } 894 895 // don't want to change the original raw data 896 if (byteData == null || rawData == byteData) byteData = new byte[rawData.length]; 897 898 if (!convertByteData) { 899 // do not convert data, just transpose the data 900 minmax[0] = 0; 901 minmax[1] = 255; 902 if (isTransposed) { 903 for (int i = 0; i < h; i++) { 904 for (int j = 0; j < w; j++) { 905 byteData[i * w + j] = rawData[j * h + i]; 906 } 907 } 908 } 909 return byteData; 910 } 911 912 // special data range used, must convert the data 913 min = minmax[0]; 914 max = minmax[1]; 915 ratio = (min == max) ? 1.00d : (double) (255.00 / (max - min)); 916 int idxSrc = 0, idxDst = 0; 917 for (int i = 0; i < h; i++) { 918 for (int j = 0; j < w; j++) { 919 idxSrc = idxDst =j * h + i; 920 if (isTransposed) idxDst = i * w + j; 921 922 if (rawData[idxSrc] > max || rawData[idxSrc] < min) { 923 byteData[idxDst] = (byte) 0; 924 if (list!=null) 925 list.add(idxSrc); 926 } 927 else 928 byteData[idxDst] = (byte) ((rawData[idxSrc] - min) * ratio); 929 } 930 } 931 932 return byteData; 933 } 934 935 /** 936 * Create and initialize a new instance of the given class. 937 * 938 * @param initargs 939 * - array of objects to be passed as arguments 940 * @return a new instance of the given class. 941 */ 942 public static Object newInstance(Class<?> cls, Object[] initargs) throws Exception { 943 Object instance = null; 944 log.trace("newInstance: start"); 945 946 if (cls == null) { 947 return null; 948 } 949 950 if ((initargs == null) || (initargs.length == 0)) { 951 instance = cls.newInstance(); 952 } 953 else { 954 Constructor<?>[] constructors = cls.getConstructors(); 955 if ((constructors == null) || (constructors.length == 0)) { 956 return null; 957 } 958 959 boolean isConstructorMatched = false; 960 Constructor<?> constructor = null; 961 Class<?>[] params = null; 962 int m = constructors.length; 963 int n = initargs.length; 964 for (int i = 0; i < m; i++) { 965 constructor = constructors[i]; 966 params = constructor.getParameterTypes(); 967 if (params.length == n) { 968 // check if all the parameters are matched 969 isConstructorMatched = params[0].isInstance(initargs[0]); 970 for (int j = 1; j < n; j++) { 971 isConstructorMatched = isConstructorMatched && params[j].isInstance(initargs[j]); 972 } 973 974 if (isConstructorMatched) { 975 instance = constructor.newInstance(initargs); 976 break; 977 } 978 } 979 } // for (int i=0; i<m; i++) { 980 } 981 log.trace("newInstance: finish"); 982 983 return instance; 984 } 985 986 /** 987 * Computes autocontrast parameters (gain equates to contrast and bias 988 * equates to brightness) for integers. 989 * <p> 990 * The computation is based on the following scaling 991 * 992 * <pre> 993 * int_8 [0, 127] 994 * uint_8 [0, 255] 995 * int_16 [0, 32767] 996 * uint_16 [0, 65535] 997 * int_32 [0, 2147483647] 998 * uint_32 [0, 4294967295] 999 * int_64 [0, 9223372036854775807] 1000 * uint_64 [0, 18446744073709551615] // Not supported. 1001 * </pre> 1002 * 1003 * @param data 1004 * the raw data array of signed/unsigned integers 1005 * @param params 1006 * the auto gain parameter. params[0]=gain, params[1]=bias, 1007 * @param isUnsigned 1008 * the flag to indicate if the data array is unsigned integer 1009 * @return non-negative if successful; otherwise, returns negative 1010 */ 1011 public static int autoContrastCompute(Object data, double[] params, boolean isUnsigned) { 1012 int retval = 1; 1013 long maxDataValue = 255; 1014 double[] minmax = new double[2]; 1015 1016 // check parameters 1017 if ((data == null) || (params == null) || (Array.getLength(data) <= 0) || (params.length < 2)) { 1018 return -1; 1019 } 1020 1021 retval = autoContrastComputeMinMax(data, minmax); 1022 1023 // force the min_max method so we can look at the target grids data sets 1024 if ((retval < 0) || (minmax[1] - minmax[0] < 10)) { 1025 retval = findMinMax(data, minmax, null); 1026 } 1027 1028 if (retval < 0) { 1029 return -1; 1030 } 1031 1032 String cname = data.getClass().getName(); 1033 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1034 switch (dname) { 1035 case 'B': 1036 maxDataValue = MAX_INT8; 1037 break; 1038 case 'S': 1039 maxDataValue = MAX_INT16; 1040 if (isUnsigned) { 1041 maxDataValue = MAX_UINT8; // data was upgraded from unsigned byte 1042 } 1043 break; 1044 case 'I': 1045 maxDataValue = MAX_INT32; 1046 if (isUnsigned) { 1047 maxDataValue = MAX_UINT16; // data was upgraded from unsigned short 1048 } 1049 break; 1050 case 'J': 1051 maxDataValue = MAX_INT64; 1052 if (isUnsigned) { 1053 maxDataValue = MAX_UINT32; // data was upgraded from unsigned int 1054 } 1055 break; 1056 default: 1057 retval = -1; 1058 break; 1059 } // switch (dname) 1060 1061 if (minmax[0] == minmax[1]) { 1062 params[0] = 1.0; 1063 params[1] = 0.0; 1064 } 1065 else { 1066 // This histogram method has a tendency to stretch the 1067 // range of values to be a bit too big, so we can 1068 // account for this by adding and subtracting some percent 1069 // of the difference to the max/min values 1070 // to prevent the gain from going too high. 1071 double diff = minmax[1] - minmax[0]; 1072 double newmax = (minmax[1] + (diff * 0.1)); 1073 double newmin = (minmax[0] - (diff * 0.1)); 1074 1075 if (newmax <= maxDataValue) { 1076 minmax[1] = newmax; 1077 } 1078 1079 if (newmin >= 0) { 1080 minmax[0] = newmin; 1081 } 1082 1083 params[0] = maxDataValue / (minmax[1] - minmax[0]); 1084 params[1] = -minmax[0]; 1085 } 1086 1087 return retval; 1088 } 1089 1090 /** 1091 * Apply autocontrast parameters to the original data in place (destructive) 1092 * 1093 * @param data_in 1094 * the original data array of signed/unsigned integers 1095 * @param data_out 1096 * the converted data array of signed/unsigned integers 1097 * @param params 1098 * the auto gain parameter. params[0]=gain, params[1]=bias 1099 * @param minmax 1100 * the data range. minmax[0]=min, minmax[1]=max 1101 * @param isUnsigned 1102 * the flag to indicate if the data array is unsigned integer 1103 * 1104 * @return the data array with the auto contrast conversion; otherwise, 1105 * returns null 1106 */ 1107 public static Object autoContrastApply(Object data_in, Object data_out, double[] params, double[] minmax, 1108 boolean isUnsigned) { 1109 int size = 0; 1110 double min = -MAX_INT64, max = MAX_INT64; 1111 1112 if ((data_in == null) || (params == null) || (params.length < 2)) { 1113 return null; 1114 } 1115 1116 if (minmax != null) { 1117 min = minmax[0]; 1118 max = minmax[1]; 1119 } 1120 // input and output array must be the same size 1121 size = Array.getLength(data_in); 1122 if ((data_out != null) && (size != Array.getLength(data_out))) { 1123 return null; 1124 } 1125 1126 double gain = params[0]; 1127 double bias = params[1]; 1128 double value_out, value_in; 1129 String cname = data_in.getClass().getName(); 1130 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1131 1132 switch (dname) { 1133 case 'B': 1134 byte[] b_in = (byte[]) data_in; 1135 if (data_out == null) { 1136 data_out = new byte[size]; 1137 } 1138 byte[] b_out = (byte[]) data_out; 1139 byte b_max = (byte) MAX_INT8; 1140 1141 for (int i = 0; i < size; i++) { 1142 value_in = Math.max(b_in[i], min); 1143 value_in = Math.min(b_in[i], max); 1144 value_out = (value_in + bias) * gain; 1145 value_out = Math.max(value_out, 0.0); 1146 value_out = Math.min(value_out, b_max); 1147 b_out[i] = (byte) value_out; 1148 } 1149 break; 1150 case 'S': 1151 short[] s_in = (short[]) data_in; 1152 if (data_out == null) { 1153 data_out = new short[size]; 1154 } 1155 short[] s_out = (short[]) data_out; 1156 short s_max = (short) MAX_INT16; 1157 1158 if (isUnsigned) { 1159 s_max = (short) MAX_UINT8; // data was upgraded from unsigned byte 1160 } 1161 1162 for (int i = 0; i < size; i++) { 1163 value_in = Math.max(s_in[i], min); 1164 value_in = Math.min(s_in[i], max); 1165 value_out = (value_in + bias) * gain; 1166 value_out = Math.max(value_out, 0.0); 1167 value_out = Math.min(value_out, s_max); 1168 s_out[i] = (byte) value_out; 1169 } 1170 break; 1171 case 'I': 1172 int[] i_in = (int[]) data_in; 1173 if (data_out == null) { 1174 data_out = new int[size]; 1175 } 1176 int[] i_out = (int[]) data_out; 1177 int i_max = (int) MAX_INT32; 1178 if (isUnsigned) { 1179 i_max = (int) MAX_UINT16; // data was upgraded from unsigned short 1180 } 1181 1182 for (int i = 0; i < size; i++) { 1183 value_in = Math.max(i_in[i], min); 1184 value_in = Math.min(i_in[i], max); 1185 value_out = (value_in + bias) * gain; 1186 value_out = Math.max(value_out, 0.0); 1187 value_out = Math.min(value_out, i_max); 1188 i_out[i] = (byte) value_out; 1189 } 1190 break; 1191 case 'J': 1192 long[] l_in = (long[]) data_in; 1193 if (data_out == null) { 1194 data_out = new long[size]; 1195 } 1196 long[] l_out = (long[]) data_out; 1197 long l_max = MAX_INT64; 1198 if (isUnsigned) { 1199 l_max = MAX_UINT32; // data was upgraded from unsigned int 1200 } 1201 1202 for (int i = 0; i < size; i++) { 1203 value_in = Math.max(l_in[i], min); 1204 value_in = Math.min(l_in[i], max); 1205 value_out = (value_in + bias) * gain; 1206 value_out = Math.max(value_out, 0.0); 1207 value_out = Math.min(value_out, l_max); 1208 l_out[i] = (byte) value_out; 1209 } 1210 break; 1211 default: 1212 break; 1213 } // switch (dname) 1214 1215 return data_out; 1216 } 1217 1218 /** 1219 * Converts image raw data to bytes. 1220 * <p> 1221 * The integer data is converted to byte data based on the following rule 1222 * 1223 * <pre> 1224 * uint_8 x 1225 * int_8 (x & 0x7F) << 1 1226 * uint_16 (x >> 8) & 0xFF 1227 * int_16 (x >> 7) & 0xFF 1228 * uint_32 (x >> 24) & 0xFF 1229 * int_32 (x >> 23) & 0xFF 1230 * uint_64 (x >> 56) & 0xFF 1231 * int_64 (x >> 55) & 0xFF 1232 * </pre> 1233 * 1234 * @param src 1235 * the source data array of signed integers or unsigned shorts 1236 * @param dst 1237 * the destination data array of bytes 1238 * @param isUnsigned 1239 * the flag to indicate if the data array is unsigned integer 1240 * @return non-negative if successful; otherwise, returns negative 1241 */ 1242 public static int autoContrastConvertImageBuffer(Object src, byte[] dst, boolean isUnsigned) { 1243 int retval = 0; 1244 1245 if ((src == null) || (dst == null) || (dst.length != Array.getLength(src))) { 1246 return -1; 1247 } 1248 1249 int size = dst.length; 1250 String cname = src.getClass().getName(); 1251 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1252 switch (dname) { 1253 case 'B': 1254 byte[] b_src = (byte[]) src; 1255 if (isUnsigned) { 1256 for (int i = 0; i < size; i++) { 1257 dst[i] = b_src[i]; 1258 } 1259 } 1260 else { 1261 for (int i = 0; i < size; i++) { 1262 dst[i] = (byte) ((b_src[i] & 0x7F) << 1); 1263 } 1264 } 1265 break; 1266 case 'S': 1267 short[] s_src = (short[]) src; 1268 if (isUnsigned) { // data was upgraded from unsigned byte 1269 for (int i = 0; i < size; i++) { 1270 dst[i] = (byte) s_src[i]; 1271 } 1272 } 1273 else { 1274 for (int i = 0; i < size; i++) { 1275 dst[i] = (byte) ((s_src[i] >> 7) & 0xFF); 1276 } 1277 } 1278 break; 1279 case 'I': 1280 int[] i_src = (int[]) src; 1281 if (isUnsigned) { // data was upgraded from unsigned short 1282 for (int i = 0; i < size; i++) { 1283 dst[i] = (byte) ((i_src[i] >> 8) & 0xFF); 1284 } 1285 } 1286 else { 1287 for (int i = 0; i < size; i++) { 1288 dst[i] = (byte) ((i_src[i] >> 23) & 0xFF); 1289 } 1290 } 1291 break; 1292 case 'J': 1293 long[] l_src = (long[]) src; 1294 if (isUnsigned) { // data was upgraded from unsigned int 1295 for (int i = 0; i < size; i++) { 1296 dst[i] = (byte) ((l_src[i] >> 24) & 0xFF); 1297 } 1298 } 1299 else { 1300 for (int i = 0; i < size; i++) { 1301 dst[i] = (byte) ((l_src[i] >> 55) & 0xFF); 1302 } 1303 } 1304 break; 1305 default: 1306 retval = -1; 1307 break; 1308 } // switch (dname) 1309 1310 return retval; 1311 } 1312 1313 /** 1314 * Computes autocontrast parameters by 1315 * 1316 * <pre> 1317 * min = mean - 3 * std.dev 1318 * max = mean + 3 * std.dev 1319 * </pre> 1320 * 1321 * @param data 1322 * the raw data array 1323 * @param minmax 1324 * the min and max values. 1325 * @return non-negative if successful; otherwise, returns negative 1326 */ 1327 public static int autoContrastComputeMinMax(Object data, double[] minmax) { 1328 int retval = 1; 1329 1330 if ((data == null) || (minmax == null) || (Array.getLength(data) <= 0) || (Array.getLength(minmax) < 2)) { 1331 return -1; 1332 } 1333 1334 double[] avgstd = { 0, 0 }; 1335 retval = computeStatistics(data, avgstd, null); 1336 if (retval < 0) { 1337 return retval; 1338 } 1339 1340 minmax[0] = avgstd[0] - 3.0 * avgstd[1]; 1341 minmax[1] = avgstd[0] + 3.0 * avgstd[1]; 1342 1343 return retval; 1344 } 1345 1346 /** 1347 * Finds the min and max values of the data array 1348 * 1349 * @param data 1350 * the raw data array 1351 * @param minmax 1352 * the mmin and max values of the array. 1353 * @param fillValue 1354 * the missing value or fill value. Exclude this value when check 1355 * for min/max 1356 * @return non-negative if successful; otherwise, returns negative 1357 */ 1358 public static int findMinMax(Object data, double[] minmax, Object fillValue) { 1359 int retval = 1; 1360 1361 if ((data == null) || (minmax == null) || (Array.getLength(data) <= 0) || (Array.getLength(minmax) < 2)) { 1362 return -1; 1363 } 1364 1365 int n = Array.getLength(data); 1366 double fill = 0.0; 1367 boolean hasFillValue = (fillValue != null && fillValue.getClass().isArray()); 1368 1369 String cname = data.getClass().getName(); 1370 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1371 log.trace("findMinMax() cname={} : dname={}", cname, dname); 1372 1373 minmax[0] = Float.MAX_VALUE; 1374 minmax[1] = -Float.MAX_VALUE; 1375 1376 switch (dname) { 1377 case 'B': 1378 byte[] b = (byte[]) data; 1379 minmax[0] = minmax[1] = b[0]; 1380 1381 if (hasFillValue) fill = ((byte[]) fillValue)[0]; 1382 for (int i = 0; i < n; i++) { 1383 if (hasFillValue && b[i] == fill) continue; 1384 if (minmax[0] > b[i]) { 1385 minmax[0] = b[i]; 1386 } 1387 if (minmax[1] < b[i]) { 1388 minmax[1] = b[i]; 1389 } 1390 } 1391 break; 1392 case 'S': 1393 short[] s = (short[]) data; 1394 minmax[0] = minmax[1] = s[0]; 1395 1396 if (hasFillValue) fill = ((short[]) fillValue)[0]; 1397 1398 for (int i = 0; i < n; i++) { 1399 if (hasFillValue && s[i] == fill) continue; 1400 if (minmax[0] > s[i]) { 1401 minmax[0] = s[i]; 1402 } 1403 if (minmax[1] < s[i]) { 1404 minmax[1] = s[i]; 1405 } 1406 } 1407 break; 1408 case 'I': 1409 int[] ia = (int[]) data; 1410 minmax[0] = minmax[1] = ia[0]; 1411 1412 if (hasFillValue) fill = ((int[]) fillValue)[0]; 1413 1414 for (int i = 0; i < n; i++) { 1415 if (hasFillValue && ia[i] == fill) continue; 1416 if (minmax[0] > ia[i]) { 1417 minmax[0] = ia[i]; 1418 } 1419 if (minmax[1] < ia[i]) { 1420 minmax[1] = ia[i]; 1421 } 1422 } 1423 break; 1424 case 'J': 1425 long[] l = (long[]) data; 1426 minmax[0] = minmax[1] = l[0]; 1427 1428 if (hasFillValue) fill = ((long[]) fillValue)[0]; 1429 for (int i = 0; i < n; i++) { 1430 if (hasFillValue && l[i] == fill) continue; 1431 if (minmax[0] > l[i]) { 1432 minmax[0] = l[i]; 1433 } 1434 if (minmax[1] < l[i]) { 1435 minmax[1] = l[i]; 1436 } 1437 } 1438 break; 1439 case 'F': 1440 float[] f = (float[]) data; 1441 minmax[0] = minmax[1] = f[0]; 1442 1443 if (hasFillValue) fill = ((float[]) fillValue)[0]; 1444 for (int i = 0; i < n; i++) { 1445 if ((hasFillValue && f[i] == fill) || isNaNINF((double) f[i])) continue; 1446 if (minmax[0] > f[i]) { 1447 minmax[0] = f[i]; 1448 } 1449 if (minmax[1] < f[i]) { 1450 minmax[1] = f[i]; 1451 } 1452 } 1453 1454 break; 1455 case 'D': 1456 double[] d = (double[]) data; 1457 minmax[0] = minmax[1] = d[0]; 1458 1459 if (hasFillValue) fill = ((double[]) fillValue)[0]; 1460 for (int i = 0; i < n; i++) { 1461 if ((hasFillValue && d[i] == fill) || isNaNINF(d[i])) continue; 1462 1463 if (minmax[0] > d[i]) { 1464 minmax[0] = d[i]; 1465 } 1466 if (minmax[1] < d[i]) { 1467 minmax[1] = d[i]; 1468 } 1469 } 1470 break; 1471 default: 1472 retval = -1; 1473 break; 1474 } // switch (dname) 1475 1476 return retval; 1477 } 1478 1479 /** 1480 * Finds the distribution of data values 1481 * 1482 * @param data 1483 * the raw data array 1484 * @param dataDist 1485 * the data distirbution. 1486 * @param minmax 1487 * the data range 1488 * @return non-negative if successful; otherwise, returns negative 1489 */ 1490 public static int findDataDist(Object data, int[] dataDist, double[] minmax) { 1491 int retval = 0; 1492 double delt = 1; 1493 1494 if ((data == null) || (minmax == null) || dataDist == null) return -1; 1495 1496 int n = Array.getLength(data); 1497 1498 if (minmax[1] != minmax[0]) delt = (dataDist.length - 1) / (minmax[1] - minmax[0]); 1499 1500 for (int i = 0; i < dataDist.length; i++) 1501 dataDist[i] = 0; 1502 1503 int idx; 1504 double val; 1505 for (int i = 0; i < n; i++) { 1506 val = ((Number) Array.get(data, i)).doubleValue(); 1507 if (val>=minmax[0] && val <=minmax[1]) { 1508 idx = (int) ((val - minmax[0]) * delt); 1509 dataDist[idx]++; 1510 } // don't count invalid values 1511 } 1512 1513 return retval; 1514 } 1515 1516 /** 1517 * Computes mean and standard deviation of a data array 1518 * 1519 * @param data 1520 * the raw data array 1521 * @param avgstd 1522 * the statistics: avgstd[0]=mean and avgstd[1]=stdev. 1523 * @param fillValue 1524 * the missing value or fill value. Exclude this value when 1525 * compute statistics 1526 * @return non-negative if successful; otherwise, returns negative 1527 */ 1528 public static int computeStatistics(Object data, double[] avgstd, Object fillValue) { 1529 int retval = 1, npoints = 0; 1530 double sum = 0, avg = 0.0, var = 0.0, diff = 0.0, fill = 0.0; 1531 1532 if ((data == null) || (avgstd == null) || (Array.getLength(data) <= 0) || (Array.getLength(avgstd) < 2)) { 1533 return -1; 1534 } 1535 1536 int n = Array.getLength(data); 1537 boolean hasFillValue = (fillValue != null && fillValue.getClass().isArray()); 1538 1539 String cname = data.getClass().getName(); 1540 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1541 log.trace("computeStatistics() cname={} : dname={}", cname, dname); 1542 1543 npoints = 0; 1544 switch (dname) { 1545 case 'B': 1546 byte[] b = (byte[]) data; 1547 if (hasFillValue) fill = ((byte[]) fillValue)[0]; 1548 for (int i = 0; i < n; i++) { 1549 if (hasFillValue && b[i] == fill) continue; 1550 sum += b[i]; 1551 npoints++; 1552 } 1553 avg = sum / npoints; 1554 for (int i = 0; i < n; i++) { 1555 if (hasFillValue && b[i] == fill) continue; 1556 diff = b[i] - avg; 1557 var += diff * diff; 1558 } 1559 break; 1560 case 'S': 1561 short[] s = (short[]) data; 1562 if (hasFillValue) fill = ((short[]) fillValue)[0]; 1563 for (int i = 0; i < n; i++) { 1564 if (hasFillValue && s[i] == fill) continue; 1565 sum += s[i]; 1566 npoints++; 1567 } 1568 avg = sum / npoints; 1569 for (int i = 0; i < n; i++) { 1570 if (hasFillValue && s[i] == fill) continue; 1571 diff = s[i] - avg; 1572 var += diff * diff; 1573 } 1574 break; 1575 case 'I': 1576 int[] ia = (int[]) data; 1577 if (hasFillValue) fill = ((int[]) fillValue)[0]; 1578 for (int i = 0; i < n; i++) { 1579 if (hasFillValue && ia[i] == fill) continue; 1580 sum += ia[i]; 1581 npoints++; 1582 } 1583 avg = sum / npoints; 1584 for (int i = 0; i < n; i++) { 1585 if (hasFillValue && ia[i] == fill) continue; 1586 diff = ia[i] - avg; 1587 var += diff * diff; 1588 } 1589 break; 1590 case 'J': 1591 long[] l = (long[]) data; 1592 if (hasFillValue) fill = ((long[]) fillValue)[0]; 1593 for (int i = 0; i < n; i++) { 1594 if (hasFillValue && l[i] == fill) continue; 1595 sum += l[i]; 1596 npoints++; 1597 } 1598 1599 avg = sum / npoints; 1600 for (int i = 0; i < n; i++) { 1601 if (hasFillValue && l[i] == fill) continue; 1602 diff = l[i] - avg; 1603 var += diff * diff; 1604 } 1605 break; 1606 case 'F': 1607 float[] f = (float[]) data; 1608 if (hasFillValue) fill = ((float[]) fillValue)[0]; 1609 for (int i = 0; i < n; i++) { 1610 if (hasFillValue && f[i] == fill) continue; 1611 sum += f[i]; 1612 npoints++; 1613 } 1614 1615 avg = sum / npoints; 1616 for (int i = 0; i < n; i++) { 1617 if (hasFillValue && f[i] == fill) continue; 1618 diff = f[i] - avg; 1619 var += diff * diff; 1620 } 1621 break; 1622 case 'D': 1623 double[] d = (double[]) data; 1624 if (hasFillValue) fill = ((double[]) fillValue)[0]; 1625 for (int i = 0; i < n; i++) { 1626 if (hasFillValue && d[i] == fill) continue; 1627 sum += d[i]; 1628 npoints++; 1629 } 1630 avg = sum / npoints; 1631 for (int i = 0; i < n; i++) { 1632 if (hasFillValue && d[i] == fill) continue; 1633 diff = d[i] - avg; 1634 var += diff * diff; 1635 } 1636 break; 1637 default: 1638 retval = -1; 1639 break; 1640 } // switch (dname) 1641 1642 if (npoints <= 1) { 1643 if (npoints < 1) avgstd[0] = fill; 1644 avgstd[1] = 0; 1645 } 1646 else { 1647 avgstd[0] = avg; 1648 avgstd[1] = Math.sqrt(var / (npoints - 1)); 1649 } 1650 1651 return retval; 1652 } 1653 1654 /** 1655 * Returns a string representation of the long argument as an unsigned 1656 * integer in base 2. This is different from Long.toBinaryString(long i). 1657 * This function add padding (0's) to the string based on the nbytes. For 1658 * example, if v=15, nbytes=1, the string will be "00001111". 1659 * 1660 * @param v 1661 * the long value 1662 * @param nbytes 1663 * number of bytes in the integer 1664 * @return the string representation of the unsigned long value represented 1665 * by the argument in binary (base 2). 1666 */ 1667 public static final String toBinaryString(long v, int nbytes) { 1668 if (nbytes <= 0) return null; 1669 1670 int nhex = nbytes * 2; 1671 short[] hex = new short[nhex]; 1672 1673 for (int i = 0; i < nhex; i++) 1674 hex[i] = (short) (0x0F & (v >> (i * 4))); 1675 1676 StringBuffer sb = new StringBuffer(); 1677 boolean isEven = true; 1678 for (int i = nhex - 1; i >= 0; i--) { 1679 if (isEven && (i < nhex - 1)) sb.append(" "); 1680 isEven = !isEven; // toggle 1681 1682 switch (hex[i]) { 1683 case 0: 1684 sb.append("0000"); 1685 break; 1686 case 1: 1687 sb.append("0001"); 1688 break; 1689 case 2: 1690 sb.append("0010"); 1691 break; 1692 case 3: 1693 sb.append("0011"); 1694 break; 1695 case 4: 1696 sb.append("0100"); 1697 break; 1698 case 5: 1699 sb.append("0101"); 1700 break; 1701 case 6: 1702 sb.append("0110"); 1703 break; 1704 case 7: 1705 sb.append("0111"); 1706 break; 1707 case 8: 1708 sb.append("1000"); 1709 break; 1710 case 9: 1711 sb.append("1001"); 1712 break; 1713 case 10: 1714 sb.append("1010"); 1715 break; 1716 case 11: 1717 sb.append("1011"); 1718 break; 1719 case 12: 1720 sb.append("1100"); 1721 break; 1722 case 13: 1723 sb.append("1101"); 1724 break; 1725 case 14: 1726 sb.append("1110"); 1727 break; 1728 case 15: 1729 sb.append("1111"); 1730 break; 1731 } 1732 } 1733 1734 return sb.toString(); 1735 } 1736 1737 /** 1738 * Returns a string representation of the long argument as an unsigned integer in base 16. This 1739 * is different from Long.toHexString(long i). This function add padding (0's) to the string 1740 * based on the nbytes. For example, if v=42543, nbytes=4, the string will be "0000A62F". 1741 * 1742 * @param v 1743 * the long value 1744 * @param nbytes 1745 * number of bytes in the integer 1746 * @return the string representation of the unsigned long value represented by the argument in 1747 * hexadecimal (base 16). 1748 */ 1749 final static char[] HEXCHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 1750 1751 public static final String toHexString (long v, int nbytes) { 1752 if (nbytes <= 0) return null; 1753 1754 int nhex = nbytes * 2; 1755 short[] hex = new short[nhex]; 1756 1757 for (int i = 0; i < nhex; i++) { 1758 hex[i] = (short) (0x0F & (v >> (i * 4))); 1759 } 1760 1761 StringBuffer sb = new StringBuffer(); 1762 for (int i = nhex - 1; i >= 0; i--) { 1763 sb.append(HEXCHARS[hex[i]]); 1764 } 1765 1766 return sb.toString(); 1767 } 1768 1769 /** 1770 * Apply bitmask to a data array. 1771 * 1772 * @param theData 1773 * the data array which the bitmask is applied to. 1774 * @param theMask 1775 * the bitmask to be applied to the data array. 1776 * @return true if bitmask is applied successfuly; otherwise, false. 1777 */ 1778 public static final boolean applyBitmask(Object theData, BitSet theMask, ViewProperties.BITMASK_OP op) { 1779 if (theData == null || Array.getLength(theData) <= 0 || theMask == null) return false; 1780 1781 char nt = '0'; 1782 String cName = theData.getClass().getName(); 1783 int cIndex = cName.lastIndexOf("["); 1784 if (cIndex >= 0) { 1785 nt = cName.charAt(cIndex + 1); 1786 } 1787 1788 // only deal with 8/16/32/64 bit datasets 1789 if (!(nt == 'B' || nt == 'S' || nt == 'I' || nt == 'J')) return false; 1790 1791 long bmask = 0, theValue = 0, packedValue = 0, bitValue = 0; 1792 1793 int nbits = theMask.length(); 1794 int len = Array.getLength(theData); 1795 1796 for (int i = 0; i < nbits; i++) { 1797 if (theMask.get(i)) bmask += 1 << i; 1798 } 1799 1800 for (int i = 0; i < len; i++) { 1801 if (nt == 'B') 1802 theValue = ((byte[]) theData)[i] & bmask; 1803 else if (nt == 'S') 1804 theValue = ((short[]) theData)[i] & bmask; 1805 else if (nt == 'I') 1806 theValue = ((int[]) theData)[i] & bmask; 1807 else if (nt == 'J') 1808 theValue = ((long[]) theData)[i] & bmask; 1809 1810 // apply bitmask only 1811 if (op == BITMASK_OP.AND) 1812 packedValue = theValue; 1813 else { 1814 // extract bits 1815 packedValue = 0; 1816 int bitPosition = 0; 1817 bitValue = 0; 1818 1819 for (int j = 0; j < nbits; j++) { 1820 if (theMask.get(j)) { 1821 bitValue = (theValue & 1); 1822 packedValue += (bitValue << bitPosition); 1823 bitPosition++; 1824 } 1825 // move to the next bit 1826 theValue = theValue >> 1; 1827 } 1828 } 1829 1830 if (nt == 'B') 1831 ((byte[]) theData)[i] = (byte) packedValue; 1832 else if (nt == 'S') 1833 ((short[]) theData)[i] = (short) packedValue; 1834 else if (nt == 'I') 1835 ((int[]) theData)[i] = (int) packedValue; 1836 else if (nt == 'J') 1837 ((long[]) theData)[i] = packedValue; 1838 } /* for (int i = 0; i < len; i++) */ 1839 1840 return true; 1841 } /* public static final boolean applyBitmask() */ 1842 1843 /** 1844 * Launch default browser for a given URL. 1845 * 1846 * @param url 1847 * -- the URL to open. 1848 * @throws Exception 1849 */ 1850 public static final void launchBrowser(String url) throws Exception { 1851 String os = System.getProperty("os.name"); 1852 Runtime runtime = Runtime.getRuntime(); 1853 1854 // Block for Windows Platform 1855 if (os.startsWith("Windows")) { 1856 String cmd = "rundll32 url.dll,FileProtocolHandler " + url; 1857 1858 if (new File(url).exists()) cmd = "cmd /c start \"\" \"" + url + "\""; 1859 runtime.exec(cmd); 1860 } 1861 // Block for Mac OS 1862 else if (os.startsWith("Mac OS")) { 1863 Class<?> fileMgr = Class.forName("com.apple.eio.FileManager"); 1864 Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class }); 1865 1866 if (new File(url).exists()) { 1867 // local file 1868 url = "file://" + url; 1869 } 1870 openURL.invoke(null, new Object[] { url }); 1871 } 1872 // Block for UNIX Platform 1873 else { 1874 String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" }; 1875 String browser = null; 1876 for (int count = 0; count < browsers.length && browser == null; count++) 1877 if (runtime.exec(new String[] { "which", browsers[count] }).waitFor() == 0) browser = browsers[count]; 1878 if (browser == null) 1879 throw new Exception("Could not find web browser"); 1880 else 1881 runtime.exec(new String[] { browser, url }); 1882 } 1883 } /* public static final void launchBrowser(String url) */ 1884 1885 /** 1886 * Check and find a non-exist file. 1887 * 1888 * @param path 1889 * -- the path that the new file will be checked. 1890 * @param ext 1891 * -- the extention of the new file. 1892 * @return -- the new file. 1893 */ 1894 public static final File checkNewFile(String path, String ext) { 1895 File file = new File(path + "new" + ext); 1896 int i = 1; 1897 1898 while (file.exists()) { 1899 file = new File(path + "new" + i + ext); 1900 i++; 1901 } 1902 1903 return file; 1904 } 1905 1906 /** 1907 * Check if a given number if NaN or INF. 1908 * 1909 * @param val 1910 * the nubmer to be checked 1911 * @return true if the number is Nan or INF; otherwise, false. 1912 */ 1913 public static final boolean isNaNINF(double val) { 1914 if (Double.isNaN(val) || val == Float.NEGATIVE_INFINITY || val == Float.POSITIVE_INFINITY 1915 || val == Double.NEGATIVE_INFINITY || val == Double.POSITIVE_INFINITY) return true; 1916 1917 return false; 1918 } 1919}