org.eclipse.dawnsci.remotedataset.test.mock.ImageServiceMock.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.dawnsci.remotedataset.test.mock.ImageServiceMock.java

Source

/*
 * Copyright (c) 2012 Diamond Light Source Ltd.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.dawnsci.remotedataset.test.mock;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;

import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.dawnsci.plotting.api.histogram.HistogramBound;
import org.eclipse.dawnsci.plotting.api.histogram.IImageService;
import org.eclipse.dawnsci.plotting.api.histogram.IPaletteService;
import org.eclipse.dawnsci.plotting.api.histogram.ImageServiceBean;
import org.eclipse.dawnsci.plotting.api.histogram.ImageServiceBean.HistoType;
import org.eclipse.dawnsci.plotting.api.histogram.ImageServiceBean.ImageOrigin;
import org.eclipse.dawnsci.plotting.api.histogram.functions.FunctionContainer;
import org.eclipse.dawnsci.plotting.api.preferences.BasePlottingConstants;
import org.eclipse.january.dataset.BooleanDataset;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.IndexIterator;
import org.eclipse.january.dataset.Maths;
import org.eclipse.january.dataset.RGBDataset;
import org.eclipse.january.dataset.Stats;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.eclipse.ui.services.AbstractServiceFactory;
import org.eclipse.ui.services.IServiceLocator;

/**
 * 
     
   Histogramming Explanation
   ---------------------------
   Image intensity distribution:
    
            ++----------------------**---------------
            +                      *+ *              
            ++                    *    *             
            |                     *    *             
            ++                    *     *            
            *                    *       *            
            +*                   *       *            
            |*                  *        *            
            +*                  *        *           
            |                  *          *         
            ++                 *          *          
            |                  *           *        
            ++                 *           *        
            |                 *            *        
            ++                *            *       
                             *              *      
    Min Cut           Min    *              *      Max                     Max cut
 Red <- |   (min colour)  |    (color range, palette)  |      (max color)      | -> Blue
                            *                 *  
            |              *        +         *  
----------------++------------**---------+----------**----+---------------**+---------------++
    
     
 * @author Matthew Gerring
 *
 */
public class ImageServiceMock extends AbstractServiceFactory implements IImageService {

    static {
        System.out.println("Starting image service");
    }

    public ImageServiceMock() {
        // Important do nothing here, OSGI may start the service more than once.
    }

    /**
     * This method is not thread safe
     */
    public Image getImage(ImageServiceBean bean) {
        final ImageData data = getImageData(bean);
        return new Image(Display.getCurrent(), data);
    }

    private static final int MIN_PIX_INDEX = 253;
    private static final int NAN_PIX_INDEX = 254;
    private static final int MAX_PIX_INDEX = 255;

    private static final byte MIN_PIX_BYTE = (byte) (MIN_PIX_INDEX & 0xFF);
    private static final byte NAN_PIX_BYTE = (byte) (NAN_PIX_INDEX & 0xFF);
    private static final byte MAX_PIX_BYTE = (byte) (MAX_PIX_INDEX & 0xFF);

    /**
     * getImageData(...) provides an image in a given palette data and origin.
     * Faster than getting a resolved image
     * 
     * This method should be thread safe.
     */
    public ImageData getImageData(ImageServiceBean bean) {
        Dataset oImage = DatasetUtils.convertToDataset(bean.getImage());
        Dataset image = oImage;
        ImageOrigin origin = bean.getOrigin();
        if (origin == null)
            origin = ImageOrigin.TOP_LEFT;
        PaletteData palette = bean.getPalette();

        if (image instanceof RGBDataset) {
            switch (origin) {
            case TOP_LEFT:
                break;
            case TOP_RIGHT:
                image = DatasetUtils.transpose(image);
                image = image.getSlice(null, null, new int[] { 1, -1 });
                break;
            case BOTTOM_LEFT:
                image = DatasetUtils.transpose(image);
                image = image.getSlice(null, null, new int[] { -1, 1 });
                break;
            case BOTTOM_RIGHT:
                image = image.getSlice(null, null, new int[] { -1, -1 });
                break;
            }
            RGBDataset rgbImage = (RGBDataset) image;
            return SWTImageUtils.createImageData(rgbImage, 0, 255, null, null, null, false, false, false);
        }

        int depth = bean.getDepth();
        final int size = (int) Math.round(Math.pow(2, depth));

        createMaxMin(bean);
        double max = getMax(bean);
        double min = getMin(bean);

        double maxCut = getMaxCut(bean);
        double minCut = getMinCut(bean);

        // now deal with the log if needed
        if (bean.isLogColorScale()) {
            image = getImageLoggedData(bean);
            max = Math.log10(max);
            // note createMaxMin() -> getFastStatistics() -> getImageLogged() which ensures min >= 0 
            min = Math.log10(min);
            maxCut = Math.log10(maxCut);
            // no guarantees for minCut though
            minCut = minCut <= 0 ? Double.NEGATIVE_INFINITY : Math.log10(minCut);
        }

        if (oImage.isComplex()) { // handle complex datasets by creating RGB dataset
            Dataset hue = Maths.angle(oImage, true);
            Dataset value = getImageLoggedData(bean);
            double maxmax = Math.max(Math.abs(max), Math.abs(min));
            if (max - min > Math.ulp(maxmax)) {
                value.isubtract(min);
                value.imultiply(1. / (max - min));
            } else {
                value.imultiply(1. / maxmax);
            }
            image = RGBDataset.createFromHSV(hue, null, value);
            return SWTImageUtils.createImageData(image, 0, 255, null, null, null, false, false, false);
        }

        if (bean.getFunctionObject() != null && bean.getFunctionObject() instanceof FunctionContainer) {
            final FunctionContainer fc = (FunctionContainer) bean.getFunctionObject();
            // TODO This does not support masking or cut bounds for zingers and dead pixels.
            return SWTImageUtils.createImageData(image, min, max, fc.getRedFunc(), fc.getGreenFunc(),
                    fc.getBlueFunc(), fc.isInverseRed(), fc.isInverseGreen(), fc.isInverseBlue());
        }

        if (depth > 8) { // Depth > 8 will not work properly at the moment.
            throw new RuntimeException(getClass().getSimpleName()
                    + " only supports 8-bit images unless a FunctionContainer has been set!");
            //if (depth == 16) palette = new PaletteData(0x7C00, 0x3E0, 0x1F);
            //if (depth == 24) palette = new PaletteData(0xFF, 0xFF00, 0xFF0000);
            //if (depth == 32) palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
        }

        final int[] shape = image.getShape();
        if (bean.isCancelled())
            return null;

        int len = image.getSize();
        if (len == 0)
            return null;

        // The last three indices of the palette are always taken up with bound colours
        createCutColours(bean); // Modifies the palette data and sets the withheld indices

        double scale;
        double maxPixel;
        if (max > min) {
            // 4 because 1 less than size and then 1 for each bound colour is lost.
            scale = (size - 4) / (max - min);
            maxPixel = max - min;
        } else {
            scale = 1;
            maxPixel = 0xFF;
        }
        if (bean.isCancelled())
            return null;

        BooleanDataset mask = bean.getMask() != null
                ? (BooleanDataset) DatasetUtils.cast(bean.getMask(), Dataset.BOOL)
                : null;

        ImageData imageData = null;

        // We use a byte array directly as this is faster than using setPixel(...)
        // on image data. Set pixel does extra floating point operations. The downside
        // is that by doing this we certainly have to have 8 bit as getPixelColorIndex(...)
        // forces the use of on byte.
        final byte[] scaledImageAsByte = new byte[len];

        if (origin == ImageOrigin.TOP_LEFT) {

            int index = 0;
            // This loop is usually the same as the image is read in but not always depending on loader.
            for (int i = 0; i < shape[0]; ++i) {
                if (bean.isCancelled())
                    return null;
                for (int j = 0; j < shape[1]; ++j) {

                    // This saves a value lookup when the pixel is certainly masked.
                    scaledImageAsByte[index] = mask == null || mask.getBoolean(i, j)
                            ? getPixelColorIndex(image.getDouble(i, j), min, max, scale, maxPixel, minCut, maxCut)
                            : NAN_PIX_BYTE;
                    ++index;
                }
            }
            imageData = new ImageData(shape[1], shape[0], 8, palette, 1, scaledImageAsByte);

        } else if (origin == ImageOrigin.BOTTOM_LEFT) {

            int index = 0;
            // This loop is slower than looping over all data and using image.getElementDoubleAbs(...)
            // However it reorders data for the axes
            for (int i = shape[1] - 1; i >= 0; --i) {
                if (bean.isCancelled())
                    return null;
                for (int j = 0; j < shape[0]; ++j) {

                    // This saves a value lookup when the pixel is certainly masked.
                    scaledImageAsByte[index] = mask == null || mask.getBoolean(j, i)
                            ? getPixelColorIndex(image.getDouble(j, i), min, max, scale, maxPixel, minCut, maxCut)
                            : NAN_PIX_BYTE;
                    index++;
                }
            }
            imageData = new ImageData(shape[0], shape[1], 8, palette, 1, scaledImageAsByte);

        } else if (origin == ImageOrigin.BOTTOM_RIGHT) {

            int index = 0;
            // This loop is slower than looping over all data and using image.getElementDoubleAbs(...)
            // However it reorders data for the axes
            for (int i = shape[0] - 1; i >= 0; --i) {
                if (bean.isCancelled())
                    return null;
                for (int j = shape[1] - 1; j >= 0; --j) {

                    // This saves a value lookup when the pixel is certainly masked.
                    scaledImageAsByte[index] = mask == null || mask.getBoolean(i, j)
                            ? getPixelColorIndex(image.getDouble(i, j), min, max, scale, maxPixel, minCut, maxCut)
                            : NAN_PIX_BYTE;
                    index++;
                }
            }
            imageData = new ImageData(shape[1], shape[0], 8, palette, 1, scaledImageAsByte);

        } else if (origin == ImageOrigin.TOP_RIGHT) {

            int index = 0;
            // This loop is slower than looping over all data and using image.getElementDoubleAbs(...)
            // However it reorders data for the axes
            for (int i = 0; i < shape[1]; ++i) {
                if (bean.isCancelled())
                    return null;
                for (int j = shape[0] - 1; j >= 0; --j) {
                    scaledImageAsByte[index] = mask == null || mask.getBoolean(j, i)
                            ? getPixelColorIndex(image.getDouble(j, i), min, max, scale, maxPixel, minCut, maxCut)
                            : NAN_PIX_BYTE;
                    index++;
                }
            }
            imageData = new ImageData(shape[0], shape[1], 8, palette, 1, scaledImageAsByte);
        }

        imageData.alpha = bean.getAlpha();
        return imageData;
    }

    private double getMax(ImageServiceBean bean) {
        if (bean.getMaximumCutBound() == null || bean.getMaximumCutBound().getBound() == null) {
            return bean.getMax().doubleValue();
        }
        return Math.min(bean.getMax().doubleValue(), bean.getMaximumCutBound().getBound().doubleValue());
    }

    private double getMin(ImageServiceBean bean) {
        if (bean.getMinimumCutBound() == null || bean.getMinimumCutBound().getBound() == null) {
            return bean.getMin().doubleValue();
        }
        return Math.max(bean.getMin().doubleValue(), bean.getMinimumCutBound().getBound().doubleValue());
    }

    private double getMaxCut(ImageServiceBean bean) {
        if (bean.getMaximumCutBound() == null || bean.getMaximumCutBound().getBound() == null) {
            return Double.POSITIVE_INFINITY;
        }
        return bean.getMaximumCutBound().getBound().doubleValue();
    }

    private double getMinCut(ImageServiceBean bean) {
        if (bean.getMinimumCutBound() == null || bean.getMinimumCutBound().getBound() == null) {
            return Double.NEGATIVE_INFINITY;
        }
        return bean.getMinimumCutBound().getBound().doubleValue();
    }

    /**
     * Calling this wipes out the last three RGBs. Even if you set max
     * @param bean
     */
    private void createCutColours(ImageServiceBean bean) {

        // We *DO NOT* copy the palette here so up to 3 of the original
        // colours can be changed. Instead whenever a palette is given to an
        // ImageService bean it should be original.
        if (bean.getPalette() == null) {
            try {
                final IPaletteService service = (IPaletteService) PlatformUI.getWorkbench()
                        .getService(IPaletteService.class);
                bean.setPalette(service.getDirectPaletteData("Gray Scale"));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // We have three special values, those which are greater than the max cut,
        // less than the min cut and the NaN number. For these we use special pixel
        // values in the palette as defined by the cut bound if it is set.
        if (bean.getMinimumCutBound() != null && bean.getMinimumCutBound().getColor() != null) {
            int[] ia = bean.getMinimumCutBound().getColor();
            bean.getPalette().colors[MIN_PIX_INDEX] = new RGB(ia[0], ia[1], ia[2]);
        }

        if (bean.getNanBound() != null && bean.getNanBound().getColor() != null) {
            int[] ia = bean.getNanBound().getColor();
            bean.getPalette().colors[NAN_PIX_INDEX] = new RGB(ia[0], ia[1], ia[2]);
        }

        if (bean.getMaximumCutBound() != null && bean.getMaximumCutBound().getColor() != null) {
            int[] ia = bean.getMaximumCutBound().getColor();
            bean.getPalette().colors[MAX_PIX_INDEX] = new RGB(ia[0], ia[1], ia[2]);
        }

    }

    private void createMaxMin(ImageServiceBean bean) {

        double[] stats = null;
        if (bean.getMin() == null) {
            if (stats == null)
                stats = getFastStatistics(bean); // do not get unless have to
            bean.setMin(stats[0]);
        }

        if (bean.getMax() == null) {
            if (stats == null)
                stats = getFastStatistics(bean); // do not get unless have to
            bean.setMax(stats[1]);
        }
    }

    /**
     * private finals inline well by the compiler.
     * @param val
     * @param min
     * @param max
     * @param scale
     * @param maxPixel
     * @param scaledImageAsByte
     */
    private final static byte getPixelColorIndex(final double val, final double min, final double max,
            final double scale, final double maxPixel, final double minCut, final double maxCut) {

        // Deal with bounds
        if (Double.isNaN(val))
            return NAN_PIX_BYTE;

        if (val <= minCut)
            return MIN_PIX_BYTE;
        if (val >= maxCut)
            return MAX_PIX_BYTE;

        // If the pixel is within the bounds
        double scaled_pixel;
        if (val < min) {
            scaled_pixel = 0;
        } else if (val >= max) {
            scaled_pixel = maxPixel;
        } else {
            scaled_pixel = val - min;
        }
        scaled_pixel = scaled_pixel * scale;

        return (byte) (0x000000FF & ((int) scaled_pixel));
    }

    /**
     * Get the logged image value and cache the result.
     * 
     * @param bean
     * @return a dataset that can be absolute, if complex, and also be logged according to bean
     * Package private for testing
     */
    /* package */ Dataset getImageLoggedData(ImageServiceBean bean) {
        Dataset ret = DatasetUtils.convertToDataset(bean.getImageValue());
        if (ret == null) {
            ret = getImageLoggedDataCalc(bean);
            bean.setImageValue(ret);
        }
        return ret;
    }

    /**
     * Get the logged image value.
     * 
     * @param bean
     * @return a dataset that can be absolute, if complex, and also be logged according to bean
     * Package private for testing
     */
    /* package */ Dataset getImageLoggedDataCalc(ImageServiceBean bean) {
        Dataset ret = DatasetUtils.convertToDataset(bean.getImage());

        if (ret.isComplex()) {
            ret = Maths.abs(ret);
        }
        if (bean.isLogColorScale()) {
            double offset = bean.getLogOffset();
            if (!Double.isNaN(offset) && !Double.isInfinite(offset)) {
                ret = Maths.subtract(ret, offset);
            }
            ret = Maths.log10(ret);
        }
        return ret;
    }

    /**
     * Fast statistics as a rough guide - this is faster than Dataset.getMin()
     * and getMax() which may cache but slows the opening of images too much.
     * The return array[2] was added in "Updated for Diffraction Tool." commit,
     * but no trace of such usage. However it should not be removed, because
     * it is useful as return array[3].
     * 
     * @param bean
     * @return [0] = min [1] = max(=mean*constant) [2] = mean [3] max
     */
    public double[] getFastStatistics(ImageServiceBean bean) {

        Dataset image = getImageLoggedData(bean);

        if (bean.getHistogramType() == HistoType.OUTLIER_VALUES && !bean.isLogColorScale()) {

            double[] ret = null;
            try {
                double[] stats = Stats.outlierValues(image, bean.getLo(), bean.getHi(), -1);
                ret = new double[] { stats[0], stats[1], -1 };
            } catch (IllegalArgumentException iae) {
                bean.setLo(10);
                bean.setHi(90);
                double[] stats = Stats.outlierValues(image, bean.getLo(), bean.getHi(), -1);
                ret = new double[] { stats[0], stats[1], -1 };
            }

            if (bean.isLogColorScale() && ret != null) {
                ret = new double[] { Math.pow(10, ret[0]), Math.pow(10, ret[1]), -1 };
            }

            return ret;
        }

        double min = Double.MAX_VALUE;
        double max = -Double.MAX_VALUE;
        double sum = 0.0;
        int size = 0;

        BooleanDataset mask = bean.getMask() != null
                ? (BooleanDataset) DatasetUtils.cast(bean.getMask(), Dataset.BOOL)
                : null;

        // Big loop warning:
        final IndexIterator it = image.getIterator();
        final IndexIterator mit = mask == null ? null : mask.getIterator();
        while (it.hasNext()) {

            final double val = image.getElementDoubleAbs(it.index);
            if (mit != null && mit.hasNext()) {
                if (!mask.getElementBooleanAbs(mit.index)) {
                    continue; // Masked!
                }
            }

            if (Double.isNaN(val))
                continue;
            if (!bean.isInBounds(val))
                continue;

            sum += val;
            if (val < min)
                min = val;
            if (val > max)
                max = val;
            size++;
        }

        double retMax = Double.NaN;
        double retExtra = Double.NaN;

        if (bean.getHistogramType() == HistoType.MEDIAN) {

            double median = Double.NaN;
            try {
                median = ((Number) Stats.median(image)).doubleValue(); // SLOW
            } catch (Exception ne) {
                median = ((Number) Stats.median(image.cast(Dataset.INT16))).doubleValue();// SLOWER
            }
            retMax = 2 * median;
            retExtra = median;

        } else { // Use mean based histo
            double mean = sum / size;
            retMax = (Math.E) * mean; // Not statistical, E seems to be better than 3...
            retExtra = mean;

        }

        if (retMax > max)
            retMax = max;

        if (bean.isLogColorScale()) {
            return new double[] { Math.pow(10, min), Math.pow(10, retMax), Math.pow(10, retExtra) };
        }

        return new double[] { min, retMax, retExtra, max };
    }

    @Override
    public Object create(@SuppressWarnings("rawtypes") Class serviceInterface, IServiceLocator parentLocator,
            IServiceLocator locator) {

        if (serviceInterface == IImageService.class) {
            return new ImageServiceMock();
        }
        return null;
    }

    public static final class SDAFunctionBean {

    }

    /**
     * Converts an SWT ImageData to an AWT BufferedImage.
     * 
     * @param bufferedImage
     * @return
     */
    @Override
    public BufferedImage getBufferedImage(ImageData data) {
        ColorModel colorModel = null;
        PaletteData palette = data.palette;
        if (palette.isDirect) {
            colorModel = new DirectColorModel(data.depth, palette.redMask, palette.greenMask, palette.blueMask);
            BufferedImage bufferedImage = new BufferedImage(colorModel,
                    colorModel.createCompatibleWritableRaster(data.width, data.height), false, null);
            for (int y = 0; y < data.height; y++) {
                for (int x = 0; x < data.width; x++) {
                    int pixel = data.getPixel(x, y);
                    RGB rgb = palette.getRGB(pixel);
                    bufferedImage.setRGB(x, y, rgb.red << 16 | rgb.green << 8 | rgb.blue);
                }
            }
            return bufferedImage;
        } else {
            RGB[] rgbs = palette.getRGBs();
            byte[] red = new byte[rgbs.length];
            byte[] green = new byte[rgbs.length];
            byte[] blue = new byte[rgbs.length];
            for (int i = 0; i < rgbs.length; i++) {
                RGB rgb = rgbs[i];
                red[i] = (byte) rgb.red;
                green[i] = (byte) rgb.green;
                blue[i] = (byte) rgb.blue;
            }
            if (data.transparentPixel != -1) {
                colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue, data.transparentPixel);
            } else {
                colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue);
            }
            BufferedImage bufferedImage = new BufferedImage(colorModel,
                    colorModel.createCompatibleWritableRaster(data.width, data.height), false, null);
            WritableRaster raster = bufferedImage.getRaster();
            int[] pixelArray = new int[1];
            for (int y = 0; y < data.height; y++) {
                for (int x = 0; x < data.width; x++) {
                    int pixel = data.getPixel(x, y);
                    pixelArray[0] = pixel;
                    raster.setPixel(x, y, pixelArray);
                }
            }
            return bufferedImage;
        }
    }

    @Override
    public ImageServiceBean createBeanFromPreferences() {

        ImageServiceBean imageServiceBean = new ImageServiceBean();

        if (Platform.getPreferencesService() != null) { // Normally
            IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, "org.dawnsci.plotting");
            imageServiceBean.setOrigin(ImageOrigin.forLabel(store.getString(BasePlottingConstants.ORIGIN_PREF)));
            imageServiceBean
                    .setHistogramType(HistoType.forLabel(store.getString(BasePlottingConstants.HISTO_PREF)));
            imageServiceBean
                    .setMinimumCutBound(HistogramBound.fromString(store.getString(BasePlottingConstants.MIN_CUT)));
            imageServiceBean
                    .setMaximumCutBound(HistogramBound.fromString(store.getString(BasePlottingConstants.MAX_CUT)));
            imageServiceBean.setNanBound(HistogramBound.fromString(store.getString(BasePlottingConstants.NAN_CUT)));
            imageServiceBean.setLo(store.getDouble(BasePlottingConstants.HISTO_LO));
            imageServiceBean.setHi(store.getDouble(BasePlottingConstants.HISTO_HI));

            try {

                imageServiceBean.setPalette(makeJetPalette());

            } catch (Exception e) {
                // Ignored
            }

        } else { // Hard code something

            imageServiceBean.setOrigin(ImageOrigin.TOP_LEFT);
            imageServiceBean.setHistogramType(HistoType.OUTLIER_VALUES);
            imageServiceBean.setMinimumCutBound(HistogramBound.DEFAULT_MINIMUM);
            imageServiceBean.setMaximumCutBound(HistogramBound.DEFAULT_MAXIMUM);
            imageServiceBean.setNanBound(HistogramBound.DEFAULT_NAN);
            imageServiceBean.setLo(00.01);
            imageServiceBean.setHi(99.99);
            imageServiceBean.setPalette(makeJetPalette());
        }

        return imageServiceBean;
    }

    static PaletteData makeJetPalette() {
        RGB jet[] = new RGB[256];

        int nb = 256;

        for (int i = 0; i < nb; i++) {

            double value = (double) i / (double) 255;

            double outBlue = 0;
            if (value <= 0.1) {
                outBlue = 5 * value + 0.5;
            }
            if (value > 0.1 && value <= 1.0 / 3.0) {
                outBlue = 1;
            }
            if (value > 1.0 / 3.0 && value <= 1.0 / 2.0) {
                outBlue = -6 * value + 3;
            }

            double outGreen = 0;
            if (value > 1.0 / 3.0 && value < 2.0 / 3.0) {
                outGreen = 1;
            }
            if (value <= 1.0 / 3.0 && value >= 1.0 / 8.0) {
                outGreen = 24.0 / 5 * value - 0.6;
            }
            if (value >= 2.0 / 3.0 && value <= 7.0 / 8.0) {
                outGreen = -24.0 / 5 * value + 4.2;
            }

            double outRed = 0;
            if (value >= 0.9) {
                outRed = -5 * value + 5.5;
            }
            if (value > 2.0 / 3.0 && value <= 0.9) {
                outRed = 1;
            }
            if (value >= 1.0 / 2.0 && value <= 2.0 / 3.0) {
                outRed = 6 * value - 3;
            }

            jet[i] = new RGB((int) (outRed * 255), (int) (outGreen * 255), (int) (outBlue * 255));

        }
        return new PaletteData(jet);
    }

}