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