com.actelion.research.orbit.imageAnalysis.utils.ImageUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.actelion.research.orbit.imageAnalysis.utils.ImageUtils.java

Source

/*
 *     Orbit, a versatile image analysis software for biological image-based quantification.
 *     Copyright (C) 2009 - 2016 Actelion Pharmaceuticals Ltd., Gewerbestrasse 16, CH-4123 Allschwil, Switzerland.
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.actelion.research.orbit.imageAnalysis.utils;

import com.actelion.research.orbit.beans.RawData;
import com.actelion.research.orbit.beans.RawDataFile;
import com.actelion.research.orbit.beans.RawMeta;
import com.actelion.research.orbit.imageAnalysis.dal.DALConfig;
import com.actelion.research.orbit.utils.RawUtilsCommon;
import com.sun.media.imageio.plugins.jpeg2000.J2KImageWriteParam;
import ij.ImagePlus;
import ij.process.ShortProcessor;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;

/**
 * Created by wirthma1 on 6/12/2014.
 */
public class ImageUtils {

    public enum PlateScalingMin {
        min, p01, p05, p1, p5
    }

    ;

    public enum PlateScalingMax {
        max, p95, p99, p995, p999
    }

    ;

    public static PlateScalingMin defaultPlateScalingMin = PlateScalingMin.p05;
    public static PlateScalingMax defaultPlateScalingMax = PlateScalingMax.p99;

    static {
        ImageIO.scanForPlugins();
    }

    /**
     * Converts image file into a BufferedImage
     *
     * @param inputFile
     * @return
     * @throws IOException
     */
    public static BufferedImage convertImageFileToBI(String inputFile) throws IOException {

        BufferedImage bi = null;

        try {
            ImageIO.setUseCache(false);
            bi = ImageIO.read(new File(inputFile));
        } catch (javax.imageio.IIOException e) // in case tiff format is not recognized, try to resolve exception using ImagePlus (ImageJ) methods
        {
            if (inputFile.endsWith(".tif") || inputFile.endsWith(".tiff")) {
                ImagePlus ip = new ImagePlus(inputFile); // :TODO validate if tiff to be read is truly 16 bit :: otherwise there could be a scaling issue occurring afterwards
                bi = ((ShortProcessor) ip.getProcessor().convertToShort(false)).get16BitBufferedImage();
            } else {
                throw e;
            }
        }

        return bi;
    }

    /**
     * Stores BufferedImage as JPEG2000 encoded file.
     *
     * @param bi
     * @param filename
     * @param dEncRate
     * @param lossless
     * @throws IOException
     */
    public static void storeBIAsJP2File(BufferedImage bi, String filename, double dEncRate, boolean lossless)
            throws IOException {

        if (hasJPEG2000FileTag(filename)) {

            IIOImage iioImage = new IIOImage(bi, null, null);

            ImageWriter jp2iw = ImageIO.getImageWritersBySuffix("jp2").next();
            J2KImageWriteParam writeParam = (J2KImageWriteParam) jp2iw.getDefaultWriteParam();

            // Indicates using the lossless scheme or not. It is equivalent to use reversible quantization and 5x3 integer wavelet filters. The default is true.
            writeParam.setLossless(lossless);

            if (lossless)
                writeParam.setFilter(J2KImageWriteParam.FILTER_53); // Specifies which wavelet filters to use for the specified tile-components. JPEG 2000 part I only supports w5x3 and w9x7 filters.
            else
                writeParam.setFilter(J2KImageWriteParam.FILTER_97);

            if (!lossless) {
                // The bitrate in bits-per-pixel for encoding. Should be set when lossy compression scheme is used. With the default value Double.MAX_VALUE, a lossless compression will be done.
                writeParam.setEncodingRate(dEncRate);

                // changes in compression rate are done in the following way
                // however JPEG2000 implementation seems not to support this <-- no differences visible
                //                writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                //                writeParam.setCompressionType(writeParam.getCompressionTypes()[0]);
                //                writeParam.setCompressionQuality(1.0f);
            }

            ImageOutputStream ios = null;
            try {
                ios = new FileImageOutputStream(new File(filename));
                jp2iw.setOutput(ios);
                jp2iw.write(null, iioImage, writeParam);
                jp2iw.dispose();
                ios.flush();
            } finally {
                if (ios != null)
                    ios.close();
            }
        } else
            System.err.println("please name your file as valid JPEG2000 file: ends with .jp2");
    }

    /**
     * Verifies JPEG2000 file tag
     *
     * @param filename
     * @return
     */
    private static boolean hasJPEG2000FileTag(String filename) {

        return filename.endsWith(".jp2");
    }

    /**
     * Stores BufferedImage as JPEG2000 encoded bytestream
     *
     * @param bi
     * @return
     * @throws IOException
     */
    public static byte[] encodeBufferedImageAsJP2(BufferedImage bi) throws IOException {

        byte[] byteStream = null;

        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        ImageIO.write(bi, "jpeg2000", baos);
        baos.flush();
        byteStream = baos.toByteArray();
        baos.close();

        return byteStream;
    }

    /**
     * Stores BufferedImage as JPEG encoded bytestream
     *
     * @param bi
     * @return
     * @throws IOException
     */
    public static byte[] encodeBufferedImageAsJPEG(BufferedImage bi) throws IOException {

        byte[] byteStream = null;

        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        ImageIO.write(bi, "jpeg", baos);
        baos.flush();
        byteStream = baos.toByteArray();
        baos.close();

        return byteStream;
    }

    /**
     * Rescales using min/max intensities and converts to 8bit. Works for gray color images.
     *
     * @param bi16
     * @return
     */
    public static BufferedImage convertTo8bit(BufferedImage bi16, int rdfId) {
        if (rdfId > 0)
            try {
                return convertTo8bit(rdfId, bi16, defaultPlateScalingMin, defaultPlateScalingMax);
            } catch (Exception e) {
                e.printStackTrace();
                return convertTo8bit(bi16, 0d, 100d); // use local percentiles instead
            }
        else
            return convertTo8bit(bi16, 0d, 100d); // use local percentiles instead
    }

    /**
     * Rescales using arbitrary intensities and converts to 8bit. Works for gray- and rgb color images.
     *
     * @param bi16
     * @return
     */
    public static BufferedImage convertTo8bit(BufferedImage bi16, double intScalingMin, double intScalignMax) {
        // TODO: use min,max from plate meta data, not image specific
        int[][] minMax = ImageUtils.getMinMaxIntensitiesOfBI(bi16);
        BufferedImage bi = null;
        if (bi16.getSampleModel().getNumBands() == 1) {
            bi16 = ImageUtils.scaleIntensities(bi16, getPercentileIntensity(bi16, intScalingMin),
                    getPercentileIntensity(bi16, intScalignMax));
            bi = new BufferedImage(bi16.getWidth(), bi16.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        } else if (bi16.getSampleModel().getNumBands() == 3) {
            bi16 = ImageUtils.scaleIntensities(bi16, getPercentileIntensity(bi16, intScalingMin),
                    getPercentileIntensity(bi16, intScalignMax));
            bi = new BufferedImage(bi16.getWidth(), bi16.getHeight(), BufferedImage.TYPE_INT_RGB);
        } else {
            throw new IllegalArgumentException(
                    "Only images with 1 band (gray-color) or 3 bands (rgb) supported. This image has "
                            + bi16.getSampleModel().getNumBands() + " bands.");
        }
        Graphics2D g2d = bi.createGraphics();
        g2d.drawImage(bi16, 0, 0, null);
        g2d.dispose();
        return bi;
    }

    /**
     * Rescales using plate intensities and converts to 8bit. Works for gray- and rgb color images.
     *
     * @param bi16
     * @return
     */
    public static BufferedImage convertTo8bit(int rdfId, BufferedImage bi16, PlateScalingMin plateScalingMin,
            PlateScalingMax plateScalingMax) throws Exception {
        RawDataFile rdf = DALConfig.getImageProvider().LoadRawDataFile(rdfId);
        /*
        if (plateScalingMin==defaultPlateScalingMin && plateScalingMax==defaultPlateScalingMax) {
            // /orbitvol1/2014-11/4309324.1002.jpg
            String url = RawUtils.STR_SERVER+"/rawFile?rawFile="+rdf.getDataPath()+"/"+rdfId+"."+RawUtils.LEVEL_8BITPREVIEW+".jpg";
            System.out.println("url: " + url);
            return ImageIO.read(new URL(url));
        }
        */

        RawData rd = DALConfig.getImageProvider().LoadRawData(rdf.getRawDataId());
        List<RawMeta> rmList = DALConfig.getImageProvider().LoadRawMetasByRawDataFile(rdfId);
        List<RawMeta> rmDataList = DALConfig.getImageProvider().LoadRawMetasByRawData(rd.getRawDataId());
        rmList.addAll(rmDataList);
        HashMap<String, RawMeta> rmHash = RawUtilsCommon.getHashFromMetaList(rmList);

        if (!rmHash.containsKey(RawUtilsCommon.STR_META_CHANNEL))
            throw new Exception("Error: Meta data 'Channel' not available for RawDataFile " + rdfId);
        //int channel = Integer.parseInt(rmHash.get(RawUtils.STR_META_CHANNEL).getValue()) - 1;
        int channel = Integer.parseInt(rmHash.get(RawUtilsCommon.STR_META_CHANNEL).getValue());
        String metaKey = "Percentiles_wvlength_" + channel + "_channel_0";
        if (!rmHash.containsKey(metaKey))
            throw new Exception("Error: Meta data '" + metaKey + "' not available for RawDataFile " + rdfId);
        String val = rmHash.get(metaKey).getValue();
        int[] minmax = parseMinMax(val, plateScalingMin, plateScalingMax);

        BufferedImage bi = null;
        if (bi16.getSampleModel().getNumBands() == 1) {
            bi16 = ImageUtils.scaleIntensities(bi16, new int[] { minmax[0] }, new int[] { minmax[1] });
            bi = new BufferedImage(bi16.getWidth(), bi16.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        } else {
            throw new IllegalArgumentException("Only images with 1 band (gray-color) supported. This image has "
                    + bi16.getSampleModel().getNumBands() + " bands.");
        }
        Graphics2D g2d = bi.createGraphics();
        g2d.drawImage(bi16, 0, 0, null);
        g2d.dispose();
        return bi;
    }

    /**
     * input format is e.g. "MIN=82;0.1%=176;0.5%=186;1%=191;5%=204;95%=1726;99%=4239;99.5%=4832;99.9%=6079;MAX=65535";
     * returns int[2] with int[0]=minValue and int[1]=maxValue
     *
     * @param percentiles
     * @return
     */
    public static int[] parseMinMax(String percentiles, PlateScalingMin min, PlateScalingMax max) {
        //String percentiles = "MIN=82;0.1%=176;0.5%=186;1%=191;5%=204;95%=1726;99%=4239;99.5%=4832;99.9%=6079;MAX=65535";
        int[] minmax = new int[2];
        String[] split = percentiles.split(";", -1);
        for (String s : split) {
            String[] keyval = s.split("=", -1);
            //System.out.println("test: "+keyval[0]+" min: "+min.toString()+" max: "+max.toString());
            if (keyval[0].equalsIgnoreCase(getMinIntensityName(min)))
                minmax[0] = Integer.parseInt(keyval[1]);
            if (keyval[0].equalsIgnoreCase(getMaxIntensityName(max)))
                minmax[1] = Integer.parseInt(keyval[1]);
        }
        return minmax;
    }

    /**
     * Rescales using predefined min,max intensities and converts to 8bit. Works for gray- and rgb color images.
     *
     * @param bi16
     * @return
     */
    public static BufferedImage convertTo8bit(BufferedImage bi16, int min, int max) {
        BufferedImage bi = null;
        if (bi16.getSampleModel().getNumBands() == 1) {
            bi16 = ImageUtils.scaleIntensities(bi16, new int[] { min }, new int[] { max });
            bi = new BufferedImage(bi16.getWidth(), bi16.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        } else if (bi16.getSampleModel().getNumBands() == 3) {
            throw new IllegalArgumentException("only grayscale images supported");
        } else {
            throw new IllegalArgumentException(
                    "Only images with 1 band (gray-color) or 3 bands (rgb) supported. This image has "
                            + bi16.getSampleModel().getNumBands() + " bands.");
        }
        Graphics2D g2d = bi.createGraphics();
        g2d.drawImage(bi16, 0, 0, null);
        g2d.dispose();
        return bi;
    }

    private static String getMinIntensityName(PlateScalingMin plateScalingMin) {
        switch (plateScalingMin) {
        case min:
            return "MIN";
        case p01:
            return "0.1%";
        case p05:
            return "0.5%";
        case p1:
            return "1%";
        case p5:
            return "5%";
        default:
            return "MIN";
        }
    }

    private static String getMaxIntensityName(PlateScalingMax plateScalingMax) {
        switch (plateScalingMax) {
        case max:
            return "MAX";
        case p95:
            return "95%";
        case p99:
            return "99%";
        case p995:
            return "99.5%";
        case p999:
            return "99.9%";
        default:
            return "MAX";
        }
    }

    /**
     * scales bands to min = 0 & max = 2^8 or 2^16
     *
     * @param image
     * @param min   int[] containing minima per band
     * @param max
     * @return
     */
    public static BufferedImage scaleIntensities(BufferedImage image, int[] min, int[] max)
            throws IllegalArgumentException {
        WritableRaster raster = image.getRaster();

        if ((min.length != max.length) || min.length != raster.getNumBands())
            throw new IllegalArgumentException(
                    "Please ensure consistency of min and max arrays and number of bands in image");

        int width = image.getWidth();
        int height = image.getHeight();

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster.getNumBands(); band++) {
                    int pixel = raster.getSample(col, row, band);
                    if (pixel < min[band])
                        pixel = min[band];
                    if (pixel > max[band])
                        pixel = max[band];
                    pixel = ((int) (((((double) pixel - min[band]) / (max[band] - min[band]))
                            * ((int) Math.pow(2, image.getColorModel().getPixelSize()) - 1)) + 0.5));
                    raster.setSample(col, row, band, pixel);
                }
            }
        }

        return image;
    }

    /**
     * Can be used to verify impact of different encoding algorithms. Compares two BufferedImages and outputs differences between those.
     *
     * @param image
     * @param image2
     * @return
     */
    public static int[] calculateBufferedImageDifferencesPxByPx(BufferedImage image, BufferedImage image2) {
        int width = image.getWidth();
        int height = image.getHeight();

        int width2 = image2.getWidth();
        int height2 = image2.getHeight();

        WritableRaster raster1 = image.getRaster();
        WritableRaster raster2 = image2.getRaster();

        if (width != width2 || height != height2)
            throw new IllegalArgumentException(
                    "Please insert two identical images that were treated with different image algorithms");

        int[] diff = new int[width * height];

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster1.getNumBands(); band++) {
                    int p1 = raster1.getSample(col, row, band);
                    int p2 = raster2.getSample(col, row, band);

                    diff[((row * width) + col)] = p2 - p1;
                }
            }
        }

        return diff;
    }

    /**
     * Returns min and max intensities and percentiles 0.01, 0.05, 0.1, 0.9, 0.95, 0.99 of a BufferedImage.
     *
     * @param image
     * @return
     */
    public static int[][] getMinMaxIntensitiesOfBI(BufferedImage image) {
        WritableRaster raster = image.getRaster();
        // per band: { min, 1%, 5%, 10%, 90%, 95%, 99%, max }
        int[][] intensities = new int[raster.getNumBands()][8];

        DescriptiveStatistics[] ds = new DescriptiveStatistics[raster.getNumBands()];
        for (int i = 0; i < raster.getNumBands(); i++)
            ds[i] = new DescriptiveStatistics();

        int width = image.getWidth();
        int height = image.getHeight();

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster.getNumBands(); band++) {
                    ds[band].addValue(raster.getSample(col, row, band));
                }
            }
        }

        for (int i = 0; i < ds.length; i++) {
            intensities[i][0] = (int) ds[i].getMin();
            intensities[i][1] = (int) ds[i].getPercentile(1);
            intensities[i][2] = (int) ds[i].getPercentile(5);
            intensities[i][3] = (int) ds[i].getPercentile(10);
            intensities[i][4] = (int) ds[i].getPercentile(90);
            intensities[i][5] = (int) ds[i].getPercentile(95);
            intensities[i][6] = (int) ds[i].getPercentile(99);
            intensities[i][7] = (int) ds[i].getMax();
        }
        return intensities;
    }

    public static int[] getPercentileIntensity(BufferedImage image, double percentile) {
        WritableRaster raster = image.getRaster();
        int[] intensities = new int[raster.getNumBands()];

        DescriptiveStatistics[] ds = new DescriptiveStatistics[raster.getNumBands()];
        for (int i = 0; i < raster.getNumBands(); i++)
            ds[i] = new DescriptiveStatistics();

        int width = image.getWidth();
        int height = image.getHeight();

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster.getNumBands(); band++) {
                    ds[band].addValue(raster.getSample(col, row, band));
                }
            }
        }

        for (int i = 0; i < ds.length; i++) {
            if (percentile == 0d)
                intensities[i] = (int) ds[i].getMin();
            else if (percentile == 100d)
                intensities[i] = (int) ds[i].getMax();
            else
                intensities[i] = (int) ds[i].getPercentile(percentile);
        }
        return intensities;
    }

    /**
     * Calculates and returns band histograms of a BufferedImage
     *
     * @param image
     * @return
     */
    public static int[][] getChannelHistograms(BufferedImage image) {
        WritableRaster raster = image.getRaster();
        int[][] histograms = new int[raster
                .getNumBands()][(int) (Math.pow(2, image.getColorModel().getPixelSize()) - 1)];

        int width = image.getWidth();
        int height = image.getHeight();

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster.getNumBands(); band++) {
                    histograms[band][raster.getSample(col, row, band)]++;
                }
            }
        }

        return histograms;
    }

    /**
     * Calculates and returns band histograms of a BufferedImage w/o a need to extract the raster
     *
     * @param raster
     * @param width
     * @param height
     * @param iNumBins
     * @return
     */
    public static int[][] getChannelHistograms(WritableRaster raster, int width, int height, int iNumBins) {

        int[][] histograms = new int[raster.getNumBands()][iNumBins];

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                for (int band = 0; band < raster.getNumBands(); band++) {
                    histograms[band][raster.getSample(col, row, band)]++;
                }
            }
        }

        return histograms;
    }

}