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