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