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