All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.actelion.research.orbit.imageAnalysis.utils.TiledImagePainter Maven / Gradle / Ivy

Go to download

Orbit, a versatile image analysis software for biological image-based quantification

There is a newer version: 3.15
Show newest version
/*
 *     Orbit, a versatile image analysis software for biological image-based quantification.
 *     Copyright (C) 2009 - 2018 Idorsia Pharmaceuticals Ltd., Hegenheimermattweg 91, 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 .
 *
 */

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

import com.actelion.research.orbit.beans.RawDataFile;
import com.actelion.research.orbit.beans.RawMeta;
import com.actelion.research.orbit.dal.IOrbitImage;
import com.actelion.research.orbit.dal.IOrbitImageMultiChannel;
import com.actelion.research.orbit.exceptions.OrbitImageServletException;
import com.actelion.research.orbit.imageAnalysis.components.RecognitionFrame;
import com.actelion.research.orbit.imageAnalysis.dal.DALConfig;
import com.actelion.research.orbit.imageAnalysis.imaging.ManipulationUtils;
import com.actelion.research.orbit.imageAnalysis.imaging.MedianFilter;
import com.actelion.research.orbit.imageAnalysis.models.FeatureDescription;
import com.actelion.research.orbit.utils.RawUtilsCommon;
import com.sun.media.jai.codec.PNGDecodeParam;
import imageJ.Colour_Deconvolution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.media.jai.*;
import javax.media.jai.registry.RenderedRegistryMode;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.awt.image.renderable.ParameterBlock;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * Problem with some old fluorescence TIFFs:
 * Some strange tiffs (e.g. RDFid 1874912) are in RGB (instead of YcBcR)  photometric interpretation and have 8,8,1 bit samples.
 * This works with ImageIO, but not with JAI. Thus, with Orbit OIS (RMI) service it works but via the servlet (here JAI is used on client side) not.
 * The current workaround is to just use RMI for these images. However, the best way would be to convert all images on server-side via imagemagick to YcBcR encoding.
 * For all newer merged (NDPIS) fluo images (>1.12.2012) this should not happen.
 */
public class TiledImagePainter implements Closeable {

    public static final int CHANNEL_RED = 0;
    public static final int CHANNEL_GREEN = 1;
    public static final int CHANNEL_BLUE = 2;
    public static final int CHANNEL_OVERLAY = 3;
    private final static Logger logger = LoggerFactory.getLogger(TiledImagePainter.class);
    transient protected OrbitTiledImage2 image;
    transient protected PlanarImage origImage;
    private String imageName = "";
    private int width = 0, height = 0;
    private int tileGridXOffset = 0, tileGridYOffset = 0;
    // private TileCache tileCache = null;
    private Object sourceImage = null;
    private TiledImagePainter[] mipMaps = null;
    private boolean generateMipMaps = true;
    private OrbitTiledImage2 redChannel = null;
    private OrbitTiledImage2 greenChannel = null;
    private OrbitTiledImage2 blueChannel = null;
    private OrbitTiledImage2 overlayChannel = null;
    private int redChannelRdfId = 0;
    private int greenChannelRdfId = 0;
    private int blueChannelRdfId = 0;
    private int overlayChannelRdfId = 0;

    private double gamma = 100d;
    private double brightness = 0d;
    private double contrast = 100d;
    private int blur = 0;
    private double redAdjust = 0d;
    private double greenAdjust = 0d;
    private double blueAdjust = 0d;
    private boolean redActive = true;
    private boolean greenActive = true;
    private boolean blueActive = true;

    private int deconvChannel = 0; // 0 is disabled, 1 to 3 for channel (-1 for index)
    private String deconvName = Colour_Deconvolution.DECONV_NONE;
    private OrbitUtils.ImageAdjustments imageAdjustments = null;
    final private OrbitUtils.ImageAdjustCachedParams cachedParams = new OrbitUtils.ImageAdjustCachedParams();


    public final static ExecutorService executorService = Integer.parseInt(System.getProperty("java.version").split("\\.")[1]) >=8 ?
            Executors.newWorkStealingPool()
            :
            Executors.newFixedThreadPool(Math.min(4,Runtime.getRuntime().availableProcessors()), new ThreadFactory() {
               @Override
               public Thread newThread(Runnable r) {
                   Thread thread = new Thread(r);
                   thread.setDaemon(true);
                   return thread;
               }
           });
    
//    public final static ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
//        @Override
//        public Thread newThread(Runnable r) {
//            Thread thread = new Thread(r);
//            thread.setDaemon(true);
//            return thread;
//        }
//    });
//    public final static ExecutorService executorService = Executors.newFixedThreadPool(1);
//    public final static ExecutorService executorService = Executors.newFixedThreadPool(Math.min(4,Runtime.getRuntime().availableProcessors()), new ThreadFactory() {
//        @Override
//        public Thread newThread(Runnable r) {
//            Thread thread = new Thread(r);
//            thread.setDaemon(true);
//            return thread;
//        }
//    });

    // private ContrastColor contrastColor = new ContrastColor();
    //private GaussianBlur gaussianBlur = new GaussianBlur();
    // private MedianFilter medianFilter = new MedianFilter();

    private BufferedImage bi;
    private WritableRaster wr;
    private DataBuffer dataBuffer;
    private Raster tile;
    private GraphicsConfiguration graphicsConfiguration;

    public TiledImagePainter() {
    }
               
    public TiledImagePainter(boolean generateMipMaps) {
        this.generateMipMaps = generateMipMaps;
    }

    public TiledImagePainter(PlanarImage inputImage, String name) {
        this.generateMipMaps = false;
        try {
            setImage(inputImage);
        } catch (OrbitImageServletException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("Cannot set image: " + inputImage);
        }
    }


    private IOrbitImage wrapImage(IOrbitImage image) {
//        if (image instanceof IOrbitImageMultiChannel) {
//            return new TileSizeWrapperMultiChannel((IOrbitImageMultiChannel) image,512,512);
//        } else {
//            return new TileSizeWrapper(image,512,512);
//        }
        return image;
    }

    public void loadImage(Object inputStrOrURL) throws Exception {
        loadImage(inputStrOrURL, 0);
    }


    public void loadImage(Object inputStrOrURL, int imagePyramidNum) throws Exception {
        loadImage(inputStrOrURL, imagePyramidNum, false);
    }

    public synchronized void loadImage(Object inputStrOrURL, int imagePyramidNum, boolean loadRemote) throws Exception {

        if (inputStrOrURL instanceof RawDataFile) {
            origImage = new OrbitTiledImageIOrbitImage(wrapImage(DALConfig.getImageProvider().createOrbitImage(((RawDataFile) inputStrOrURL), imagePyramidNum)),imagePyramidNum);
            imageName = ((RawDataFile) inputStrOrURL).getFileName();
            imageAdjustments = OrbitUtils.getAndParseImageAdjustments(((RawDataFile) inputStrOrURL).getRawDataFileId());
        } else if (inputStrOrURL instanceof URL) {
            PNGDecodeParam param = null;
            if (String.valueOf(inputStrOrURL).toLowerCase().endsWith("png")) {
                param = new PNGDecodeParam();
                param.setSuppressAlpha(true);   // we don't support alpha channels here
            }
            origImage = JAI.create("url", inputStrOrURL, param);
            imageName = inputStrOrURL.toString();
        } else if (inputStrOrURL instanceof RenderedImage) {
            origImage = PlanarImage.wrapRenderedImage((RenderedImage) inputStrOrURL);
        } else {
            if (!loadRemote) {

                origImage = TiffConverter.loadFromFile((String) inputStrOrURL); // load from local filesystem
            } else {
                throw new Exception("Image loading method not supported");
                //origImage = accessRemote((String)inputStrOrURL);
                //imageName = (String)inputStrOrURL;
            }
        }


        if (origImage == null) return;
        this.sourceImage = inputStrOrURL;
        setImage(origImage);
        // is a previewImage available? use full size of preview image if possible (last parameter)
        if ((imagePyramidNum == 0) && generateMipMaps && ((origImage.getWidth() > 5000) || (origImage.getHeight() > 5000))) {
            loadMipMaps(inputStrOrURL);
        }

        origImage = null;

        if (image!=null && mipMaps!=null) {
            image.setTiledImagePainterStats(this);
            for (TiledImagePainter mip: mipMaps) {
                if (mip.getImage()!=null) {
                    mip.getImage().setTiledImagePainterStats(this);
                }
            }
        }

    }


    public void loadImageSpecial(RawDataFile rdf, int level) throws OrbitImageServletException, SQLException {
        int num = level;
        if (num < 1000)
            num++;   // +1 because orbit file system starts with 1 and not 0 (id, id.1, id.2, ...)  - but not for special layers like slide overview or label (>=1000)
        origImage = null;
        try {
            switch (level) {
                case RawUtilsCommon.LEVEL_LABEL: {
                    origImage = new OrbitTiledImagePlanarImage(PlanarImage.wrapRenderedImage(DALConfig.getImageProvider().getLabelImage(rdf)));
                    break;
                }
                case RawUtilsCommon.LEVEL_OVERVIEW: {
                    origImage = new OrbitTiledImagePlanarImage(PlanarImage.wrapRenderedImage(DALConfig.getImageProvider().getOverviewImage(rdf)));
                    break;
                }
                default: {
                    origImage = new OrbitTiledImageIOrbitImage(wrapImage(DALConfig.getImageProvider().createOrbitImage(rdf, level)));
                    break;
                }
            }
            if (origImage == null) return; // not available
            if (level == RawUtilsCommon.LEVEL_LABEL) {
                float centerX = (float) origImage.getWidth() / 2;
                float centerY = (float) origImage.getHeight() / 2;
                ParameterBlock pb = new ParameterBlock();
                pb.addSource(origImage);
                pb.add(centerX);
                pb.add(centerY);
                pb.add(new Float(Math.toRadians(90)));
                pb.add(new javax.media.jai.InterpolationBicubic(10));
                origImage = JAI.create("rotate", pb);
            }

            setImage(origImage);
            imageName = rdf.toString();
            imageAdjustments = OrbitUtils.getAndParseImageAdjustments(rdf.getRawDataFileId());
        } catch (Exception e) {
            logger.error("Special layer not available for this image.");
        }
    }

    /**
     * Loads an appropriate image layer acording to the scale factor. (No accurate scaling done here.)
     * @param rdf
     * @param scaleFactor
     * @param parentWidth
     * @throws OrbitImageServletException
     * @throws SQLException
     */
    public void loadImageScaleFactor(RawDataFile rdf, double scaleFactor, int parentWidth) throws OrbitImageServletException, SQLException {
        origImage = null;
        try {
            RecognitionFrame rfFull = new RecognitionFrame(rdf);
            TiledImagePainter[] mipFull = rfFull.bimg.mipMaps;
            int level = 0;
            if (mipFull != null && scaleFactor < 0.9f) {
                for (int i = mipFull.length - 1; i >= 0; i--) {
                    if (mipFull[i] != null) {
                        if ((scaleFactor <= (double) mipFull[i].getWidth() / rfFull.bimg.image.getWidth())) {
                            level = i;
                            break;
                        }
                    }
                }
            }

            logger.debug("used level: "+level);

            generateMipMaps = false; // TODO
            IOrbitImage orbitImage = DALConfig.getImageProvider().createOrbitImage(rdf, level+1); // why +1 ?
            origImage = new OrbitTiledImageIOrbitImage(wrapImage(orbitImage));
// we don't scale here, just select an appropriate level. Scaling should be done afterwards.
// scaling here would convert a OrbitTiledImageIOrbitImage into a OrbitTiledImagePlanarImage (no multichannel anymore)
//            double levelScale = (double)origImage.getWidth() / (double)parentWidth;
//            double addScale = scaleFactor / levelScale;
//            int oldWidth = origImage.getWidth();
//            origImage = scalePlanarImage(origImage,addScale);     // here it's only a PlanarImage
            if (origImage == null) return; // not available
            setImage(origImage);
            imageName = rdf.toString();
            imageAdjustments = OrbitUtils.getAndParseImageAdjustments(rdf.getRawDataFileId());
        } catch (Exception e) {
            logger.error("Special layer not available for this image.");
        }
    }

    /**
     * Will return a PlanarImage (even if it was a multichannel image before!)
     * Do not use for multi channel images.
     * @param sourceImage
     * @param factor
     * @return
     */
    @Deprecated
    private PlanarImage scalePlanarImage(PlanarImage sourceImage, double factor) {
        ParameterBlockJAI pb = new ParameterBlockJAI("scale", RenderedRegistryMode.MODE_NAME);
        pb.setSource("source0", sourceImage);
        pb.setParameter("xScale", new Float(factor));
        pb.setParameter("yScale", new Float(factor));
        PlanarImage pi = JAI.create("scale", pb);
        return pi;
    }

    /**
     * @param inputStrOrURL can be RawDataFile or String (url is not allowed here)
     * @throws OrbitImageServletException
     */
    public void loadMipMaps(Object inputStrOrURL) throws Exception {
        if (inputStrOrURL instanceof URL) {
            logger.error("loadMipMaps for input type URL not supported.");
            return;
        }
        // so inputStrorURL can be of type RawDataFile or String

        double level0ratio = (double)image.getWidth()/image.getHeight();
        boolean oldMipPyramid = false;
        if (inputStrOrURL instanceof RawDataFile) {
            oldMipPyramid = ((RawDataFile) inputStrOrURL).getModifyDate().getTime() < 1411075560000L;   // before 9/18/14 5:26 PM  the pyramid limit was width < 2000
        }
        logger.trace("old pyramid limit: " + oldMipPyramid);

        // try to query the resolution count (if known)
        int numResolutionCount = -1;
        if (inputStrOrURL instanceof RawDataFile) {
            List rmList = DALConfig.getImageProvider().LoadRawMetasByRawDataFileAndName(((RawDataFile) inputStrOrURL).getRawDataFileId(),RawUtilsCommon.STR_META_IMAGE_RESOLUTIONCOUNT);
            if (rmList!=null && rmList.size()>0) {
                try {
                    numResolutionCount = Integer.parseInt(rmList.get(0).getValue());
                } catch (Exception e) {
                    logger.warn("cannot parse resolution count: "+rmList.get(0));
                }
            }
        }

        List mipList = new ArrayList();
        int mipNum = 1;
        // while there was no error reading resolution mipNum, numResolution is unknown or  mipNum  0 && (numResolutionCount<0||mipNum 0) {
                        //logger.debug("building painter "+mipFileName);
                        TiledImagePainter mipPainter = new TiledImagePainter(false);
                        mipPainter.loadImage(inputStrOrURL, mipNum, true);
                        double mipRatio = (double)mipPainter.getWidth()/mipPainter.getHeight();
                        if (Math.abs(level0ratio-mipRatio)<0.01d) {
                            if (logger.isTraceEnabled())
                                logger.trace("mipmap loaded " + mipPainter.getWidth() + "x" + mipPainter.getHeight() + " mipNum:" + mipNum);
                            mipList.add(mipPainter);
                            // load further mipMaps ?
                            if (oldMipPyramid) {
                                if (mipPainter.getWidth() > 2000)
                                    mipNum++;
                                else mipNum = -1;
                            } else {
                                if ((long) mipPainter.getWidth() * mipPainter.getHeight() > RawUtilsCommon.MIN_SIZE_FOR_IMAGE_PYRAMID) // according RawUploader (line ~764) criteria
                                    mipNum++;
                                else mipNum = -1;
                            }
//                            if (((long) mipPainter.getWidth() * mipPainter.getHeight()) > (1024L*1024L))
//                                   mipNum++;
//                             else mipNum = -1;
                        }
                    }
                } else {
                    mipNum = -1;
                    break;
                }

            } else if (inputStrOrURL instanceof URL) {
                // should not happen
            } else if (inputStrOrURL instanceof String) {
                mipFileName = ((String) inputStrOrURL).split("\\.")[0] + "." + mipNum + "." + ((String) inputStrOrURL).split("\\.")[1];
                if (mipFileName == null) break;
                logger.trace("trying string mip: " + mipFileName);
                File testFile = new File(mipFileName);
                if (!testFile.exists()) {
                    mipNum = -1;
                    break;
                }
                //logger.trace("building painter");
                TiledImagePainter mipPainter = new TiledImagePainter(false);
                mipPainter.loadImage(mipFileName, mipNum, false);
                double mipRatio = (double)mipPainter.getWidth()/mipPainter.getHeight();
                if (Math.abs(level0ratio-mipRatio)<0.01d) {
                    if (logger.isTraceEnabled())
                        logger.trace("mipmap loaded " + mipPainter.getWidth() + "x" + mipPainter.getHeight());
                    mipList.add(mipPainter);

                    // load further mipMaps ?
                    if (oldMipPyramid) {
                        if (mipPainter.getWidth() > 2000)
                            mipNum++;
                        else mipNum = -1;
                    } else {
                        if ((long) mipPainter.getWidth() * mipPainter.getHeight() > RawUtilsCommon.MIN_SIZE_FOR_IMAGE_PYRAMID) // according RawUploader (line ~764) criteria
                            mipNum++;
                        else mipNum = -1;
                    }
                }
            }
        } // while i>0

        if (mipList != null && mipList.size() > 0) {
            mipMaps = new TiledImagePainter[mipList.size()];
            for (int i = 0; i < mipList.size(); i++) {
                mipMaps[i] = mipList.get(i);
            }
            mipList = null;
        }
    }


    /**
     * Returns image data of the modified image (e.g. for classification)
     */
    public Raster getData(Rectangle region, FeatureDescription fd) throws OrbitImageServletException {
        Raster r = null;
        try {
            PlanarImage pi = getModifiedImage(fd);
            r = pi.getExtendedData(region, BorderExtender.createInstance(BorderExtender.BORDER_COPY));
        } catch (Exception e) {
            e.printStackTrace();
            r = null;
            logger.error("Error loading image.\nOrbit will now retry to load the image several times.");
        }
        if (r == null) {
            throw new OrbitImageServletException("error loading image data");
        }

        return r;
    }




    public int getType() {
        if (getImage().getColorModel() == null) return BufferedImage.TYPE_INT_RGB; // but should be TYPE_3BYTE_BGR!
        if (getImage().getColorModel().hasAlpha()) return BufferedImage.TYPE_INT_ARGB;
        else return BufferedImage.TYPE_INT_RGB;
    }

    /**
     * Returns the PlanarImage with all modifications (e.g. median filter) applied. Used for classification.
     *
     * @return
     */
    public PlanarImage getModifiedImage(FeatureDescription featureDescription) {
        PlanarImage pi = image;

// multichannel features should not be set here (too slow), but instead before a certain task starts (e.g. in OrbitHelper.Segmentation)
//        if (pi instanceof OrbitTiledImage2) {
//            OrbitUtils.setMultiChannelFeatures((OrbitTiledImage2) pi, featureDescription);
//        }

        if (featureDescription.getNumBlur() > 0) {
            MedianFilter medianFilter = new MedianFilter();
            medianFilter.setParameter(pi, featureDescription.getNumBlur());
            pi = medianFilter.process();
        }


        if (featureDescription.isUseImageAdjustments() && imageAdjustments != null) {
            logger.trace("image adjustments available, will be used for modified image");

            if (Math.abs(imageAdjustments.getBrightness()) > 0.01d) {
                pi = OrbitTiledImage2.adjustBrightness(pi, imageAdjustments.getBrightness());
            }
            if (imageAdjustments.getGamma() != 100) {
                pi = OrbitTiledImage2.adjustGamma(pi, imageAdjustments.getGamma(), cachedParams, this);
            }
            if ((imageAdjustments.getRed() != 0) || (imageAdjustments.getBlue() != 0) || (imageAdjustments.getGreen() != 0) || imageAdjustments.getContrast() != 100) {
                pi = OrbitTiledImage2.adjustContrast(pi, imageAdjustments.getRed(), imageAdjustments.getGreen(), imageAdjustments.getBlue(), (float) imageAdjustments.getContrast() / 100f, imageAdjustments.getBrightness(), cachedParams, this);
            }
        }

        pi.getWidth(); // initialize, e.g. set colorModel and sampleModel if not set already

        return pi;
    }




    private OrbitTiledImage2 setOrWrapImage(PlanarImage img) {
        if (img instanceof OrbitTiledImage2) {
            return (OrbitTiledImage2) img;
        } else {
            // wrap PlanarImage
            try {
                return new OrbitTiledImagePlanarImage(img);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    public OrbitTiledImage2 getImage() {
        //return getModifiedImage(OrbitImageAnalysis.getInstance().getModel().getFeatureDescription());
        return image;
    }


    protected void setImage(PlanarImage img) throws OrbitImageServletException {
        origImage = img;
        image = setOrWrapImage(img);

        //sampleModel = image.getSampleModel();
        //colorModel = image.getColorModel();
        width = image.getWidth();
        height = image.getHeight();
        tileGridXOffset = image.getTileGridXOffset();
        tileGridYOffset = image.getTileGridYOffset();
    }

	 
	 /*
     protected RenderedOp makeTiledImageObs(PlanarImage img) {
		  ImageLayout tileLayout = new ImageLayout(img);
	      tileLayout.setTileWidth(tileWidth);
	      tileLayout.setTileHeight(tileHeight);
	      RenderingHints tileHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, tileLayout);
	      ParameterBlock pb = new ParameterBlock();
	      pb.addSource(img);
	      return JAI.create("format", pb, tileHints);
	   }
	 */

    @SuppressWarnings("unused")
    private String getTileInfo(PlanarImage img) {
        StringBuilder sb = new StringBuilder();
        //sb.append("imageName = "+imageName+"\n");
        sb.append("imageWidth = " + img.getWidth() + "\n");
        sb.append("imageHeight = " + img.getHeight() + "\n");
        sb.append("tileWidth = " + img.getTileWidth() + "\n");
        sb.append("tileHeight = " + img.getTileHeight() + "\n");
        sb.append("maxTileIndexX = " + (img.getMinTileX() + img.getNumXTiles() - 1) + "\n");
        sb.append("maxTileIndexY = " + (img.getMinTileY() + img.getNumYTiles() - 1) + "\n");
        sb.append("maxTileCordX = " + img.getMaxX() + "\n");
        sb.append("maxTileCordY = " + img.getMaxY() + "\n");
        sb.append("minTileIndexX = " + img.getMinTileX() + "\n");
        sb.append("minTileIndexY = " + img.getMinTileY() + "\n");
        sb.append("minTileCordX = " + img.getMinX() + "\n");
        sb.append("minTileCordY = " + img.getMinY() + "\n");
        sb.append("tileGridXOffset = " + img.getTileGridXOffset() + "\n");
        sb.append("tileGridYOffset = " + img.getTileGridYOffset() + "\n");
        // sb.append("tileCache: " + tileCache.getMemoryCapacity() / (1024 * 1024) + " MB");
        return sb.toString();
    }

    /**
     * Remove it when the new multi threaded drawImage version works fine!
     * 

* Draws the image to graphics. It automatically chooses the best MIP if available unless mipNum is > -1. * * @param graphics * @param _vpOffsX * @param _vpOffsY * @param _vpWidth * @param _vpHeight * @param scale 0..100 * @param mipNum set to -1 for automatic MIP selection */ @Deprecated private synchronized void drawImageSingleThread(Graphics2D graphics, double _vpOffsX, double _vpOffsY, double _vpWidth, double _vpHeight, double scale, int mipNum) { if (getImage() == null) return; double sc = scale / 100d; // if mipMaps are loaded, find the one with the best resolution (according to scale-size-ratio) if (generateMipMaps) { TiledImagePainter mip = null; if (mipMaps != null && sc < 0.9f) { for (int i = mipMaps.length - 1; i >= 0; i--) { if (mipMaps[i] != null) { if ((mipNum > -1 && mipNum == i) || (sc < (double) mipMaps[i].getWidth() / this.getWidth())) { // System.out.println("scale:"+sc+" mipNr:"+i+" mipMapWidth:"+mipMaps[i].getWidth()+" ratioMipMap:"+(double)mipMaps[i].getWidth()/this.getWidth()); mip = mipMaps[i]; break; } } } } /* if ((sc<0.0625f/2f) && (mipMaps[4]!=null)) mip = mipMaps[4]; else if ((sc<0.0625f) && (mipMaps[3]!=null)) mip = mipMaps[3]; else if ((sc<0.125f) && (mipMaps[2]!=null)) mip = mipMaps[2]; else if ((sc<0.25f) && (mipMaps[1]!=null)) mip = mipMaps[1]; else if ((sc<0.5f) && (mipMaps[0]!=null)) mip = mipMaps[0]; */ if (mip != null) { double mipScaleX = getImage().getWidth() / (double) mip.getWidth(); double mipScaleY = getImage().getHeight() / (double) mip.getHeight(); AffineTransform oldTrans = graphics.getTransform(); // push transformation graphics.scale(mipScaleX, mipScaleY); mip.drawImage(graphics, (_vpOffsX / mipScaleX), (_vpOffsY / mipScaleY), (_vpWidth / mipScaleX), (_vpHeight / mipScaleY), scale, -1); graphics.setTransform(oldTrans); // restore transformation return; } } // System.out.println("mipmap1"); double vpWidth = _vpWidth / sc; double vpHeight = _vpHeight / sc; double vpOffsX = _vpOffsX / sc; double vpOffsY = _vpOffsY / sc; int topIndex = Math.max(PlanarImage.YToTileY((int) vpOffsY, 0, getImage().getTileHeight()), getImage().getMinTileY()); int bottomIndex = Math.min(PlanarImage.YToTileY((int) (vpOffsY + vpHeight), 0, getImage().getTileHeight()), getImage().getMaxTileY()); int leftIndex = Math.max(PlanarImage.XToTileX((int) vpOffsX, 0, getImage().getTileWidth()), getImage().getMinTileX()); int rightIndex = Math.min(PlanarImage.XToTileX((int) (vpOffsX + vpWidth), 0, getImage().getTileWidth()), getImage().getMaxTileX()); // bandmerge op OrbitTiledImage2 imageMerged = getImage(); float[] channelContributions = null; if (imageMerged instanceof OrbitTiledImageIOrbitImage) { IOrbitImage oi = ((OrbitTiledImageIOrbitImage) imageMerged).getOrbitImage(); if (oi instanceof IOrbitImageMultiChannel) { channelContributions = ((IOrbitImageMultiChannel) oi).getChannelContributions(); } } for (int tj = topIndex; tj <= bottomIndex; tj++) { for (int ti = leftIndex; ti <= rightIndex; ti++) { tile = null; try { tile = imageMerged.getTile(ti, tj, gamma, contrast, brightness, blur, redAdjust, greenAdjust, blueAdjust, redChannel, greenChannel, blueChannel, overlayChannel, redActive, greenActive, blueActive, deconvChannel, deconvName, channelContributions); } catch (Exception e) { // tile not available (e.g. thread stopped), so do nothing } if (tile == null) return; // but return here dataBuffer = tile.getDataBuffer(); wr = Raster.createWritableRaster(/*sampleModel*/ imageMerged.getSampleModel(), dataBuffer, new Point(0, 0)); // bi = new BufferedImage(/*colorModel*/imageMerged.getColorModel(),wr,imageMerged.getColorModel().isAlphaPremultiplied(),null); GraphicsConfiguration graphicConfig = graphics.getDeviceConfiguration(); if (graphicConfig != null) { bi = graphics.getDeviceConfiguration().createCompatibleImage(imageMerged.getSampleModel().getWidth(), imageMerged.getSampleModel().getHeight(), imageMerged.getColorModel().getTransparency()); bi.setData(wr); } else { bi = new BufferedImage(imageMerged.getColorModel(), wr, imageMerged.getColorModel().isAlphaPremultiplied(), null); } if (bi == null) continue; // bound check if ((getImage().tileYToY(tj) + bi.getHeight() > getImage().getHeight())) { bi = bi.getSubimage(0, 0, bi.getWidth(), bi.getHeight() - (getImage().tileYToY(tj) + bi.getHeight() - getImage().getHeight())); } if ((getImage().tileXToX(ti) + bi.getWidth() > getImage().getWidth())) { bi = bi.getSubimage(0, 0, bi.getWidth() - (getImage().tileXToX(ti) + bi.getWidth() - getImage().getWidth()), bi.getHeight()); } int xInTile = getImage().tileXToX(ti); int yInTile = getImage().tileYToY(tj); AffineTransform tx = AffineTransform.getTranslateInstance(xInTile, yInTile); //System.out.println("bi: "+bi.getWidth()+"x"+bi.getHeight()+" inTile: "+xInTile+"/"+yInTile); try { graphics.drawRenderedImage(bi, tx); } catch (Throwable t) { logger.trace("1.5 bug drawRenderedImage"); // happens with JRE 1.5, but everything seems to work fine anyway? } } } /* // schedule tiles int topIndexP = Math.max(PlanarImage.YToTileY(vpOffsY, 0, tileHeight), displayImage.getMinTileY()); int bottomIndexP = Math.min(PlanarImage.YToTileY(vpOffsY+vpHeight, 0, tileHeight), displayImage.getMaxTileY()); int leftIndexP = Math.max(PlanarImage.XToTileX(vpOffsX+vpWidth+1, 0, tileWidth), displayImage.getMinTileX()); int rightIndexP = Math.min(PlanarImage.XToTileX(vpOffsX+vpWidth+5, 0, tileWidth), displayImage.getMaxTileX()); ArrayList prefetchList = new ArrayList(); for(int tj = topIndexP; tj <= bottomIndexP; tj++) { for(int ti = leftIndexP; ti <= rightIndexP; ti++) { prefetchList.add(new Point(ti,tj)); } } displayImage.prefetchTiles(prefetchList.toArray(new Point[0])); */ } private synchronized GraphicsConfiguration getGraphicsConfig() { if (graphicsConfiguration==null) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (!ge.isHeadlessInstance()) { GraphicsDevice[] gs = ge.getScreenDevices(); if (gs != null && gs.length > 1) { graphicsConfiguration = gs[0].getDefaultConfiguration(); } } } return graphicsConfiguration; } public void drawImage(final Graphics2D graphics, double _vpOffsX, double _vpOffsY, double _vpWidth, double _vpHeight, double scale, int mipNum) { if (getImage() == null) return; double sc = scale / 100d; // if mipMaps are loaded, find the one with the best resolution (according to scale-size-ratio) if (generateMipMaps) { TiledImagePainter mip = null; if (mipMaps != null && sc < 0.9f) { for (int i = mipMaps.length - 1; i >= 0; i--) { if (mipMaps[i] != null) { if ((mipNum > -1 && mipNum == i) || (sc < (double) mipMaps[i].getWidth() / this.getWidth())) { // System.out.println("scale:"+sc+" mipNr:"+i+" mipMapWidth:"+mipMaps[i].getWidth()+" ratioMipMap:"+(double)mipMaps[i].getWidth()/this.getWidth()); mip = mipMaps[i]; break; } } } } if (mip != null) { double mipScaleX = getImage().getWidth() / (double) mip.getWidth(); double mipScaleY = getImage().getHeight() / (double) mip.getHeight(); AffineTransform oldTrans = graphics.getTransform(); // push transformation graphics.scale(mipScaleX, mipScaleY); mip.drawImage(graphics, (_vpOffsX / mipScaleX), (_vpOffsY / mipScaleY), (_vpWidth / mipScaleX), (_vpHeight / mipScaleY), scale, -1); graphics.setTransform(oldTrans); // restore transformation return; } } // System.out.println("mipmap1"); double vpWidth = _vpWidth / sc; double vpHeight = _vpHeight / sc; double vpOffsX = _vpOffsX / sc; double vpOffsY = _vpOffsY / sc; int topIndex = Math.max(PlanarImage.YToTileY((int) vpOffsY, 0, getImage().getTileHeight()), getImage().getMinTileY()); int bottomIndex = Math.min(PlanarImage.YToTileY((int) (vpOffsY + vpHeight), 0, getImage().getTileHeight()), getImage().getMaxTileY()); int leftIndex = Math.max(PlanarImage.XToTileX((int) vpOffsX, 0, getImage().getTileWidth()), getImage().getMinTileX()); int rightIndex = Math.min(PlanarImage.XToTileX((int) (vpOffsX + vpWidth), 0, getImage().getTileWidth()), getImage().getMaxTileX()); // bandmerge op final OrbitTiledImage2 imageMerged = getImage(); float[] channelContributionsTemp = null; if (imageMerged instanceof OrbitTiledImageIOrbitImage) { IOrbitImage oi = ((OrbitTiledImageIOrbitImage) imageMerged).getOrbitImage(); if (oi instanceof IOrbitImageMultiChannel) { channelContributionsTemp = ((IOrbitImageMultiChannel) oi).getChannelContributions(); } } final float[] channelContributions = channelContributionsTemp; final List> taskList = new ArrayList>(); for (int tji = topIndex; tji <= bottomIndex; tji++) { for (int tii = leftIndex; tii <= rightIndex; tii++) { final int tj = tji; final int ti = tii; final Callable renderTile = new Callable() { @Override public Void call() { BufferedImage bi; WritableRaster wr; DataBuffer dataBuffer; Raster tile; tile = null; try { tile = imageMerged.getTile(ti, tj, gamma, contrast, brightness, blur, redAdjust, greenAdjust, blueAdjust, redChannel, greenChannel, blueChannel, overlayChannel, redActive, greenActive, blueActive, deconvChannel, deconvName, channelContributions); } catch (Exception e) { // tile not available (e.g. thread stopped), so do nothing return null; // s.th. went wrong (e.g. no network), so nothing to render } if (tile == null) return null; // but return here dataBuffer = tile.getDataBuffer(); wr = Raster.createWritableRaster(/*sampleModel*/ imageMerged.getSampleModel(), dataBuffer, new Point(0, 0)); //BufferedImage bim = new BufferedImage(imageMerged.getColorModel(),wr,imageMerged.getColorModel().isAlphaPremultiplied(),null); GraphicsConfiguration graphicConfig = getGraphicsConfig(); if (graphicConfig != null) { bi = graphicConfig.createCompatibleImage(imageMerged.getSampleModel().getWidth(), imageMerged.getSampleModel().getHeight(), imageMerged.getColorModel().getTransparency()); bi.setData(wr); } else { bi = new BufferedImage(imageMerged.getColorModel(), wr, imageMerged.getColorModel().isAlphaPremultiplied(), null); } if (bi == null) return null; // bound check if ((getImage().tileYToY(tj) + bi.getHeight() > getImage().getHeight())) { bi = bi.getSubimage(0, 0, bi.getWidth(), bi.getHeight() - (getImage().tileYToY(tj) + bi.getHeight() - getImage().getHeight())); } if ((getImage().tileXToX(ti) + bi.getWidth() > getImage().getWidth())) { bi = bi.getSubimage(0, 0, bi.getWidth() - (getImage().tileXToX(ti) + bi.getWidth() - getImage().getWidth()), bi.getHeight()); } int xInTile = getImage().tileXToX(ti); int yInTile = getImage().tileYToY(tj); AffineTransform tx = AffineTransform.getTranslateInstance(xInTile, yInTile); synchronized (graphics) { graphics.drawRenderedImage(bi, tx); } return null; } }; taskList.add(renderTile); } try { TiledImagePainter.executorService.invokeAll(taskList, 5, TimeUnit.SECONDS); } catch (Exception e) { logger.warn("ORBITERR00320: error rendering tile", e); } } } private PlanarImage gammaCorrection(PlanarImage source, double gammaValue) { ParameterBlock pb = new ParameterBlock(); pb.addSource(source); // The source image pb.add(null); // The region of the image to scan pb.add(100); // The horizontal sampling rate pb.add(100); // The vertical sampling rate RenderedOp op = JAI.create("extrema", pb); double[][] extrema = (double[][]) op.getProperty("extrema"); double minValue = Math.min(Math.min(extrema[0][0], extrema[1][0]), extrema[2][0]); double maxValue = Math.max(Math.max(extrema[0][1], extrema[1][1]), extrema[2][1]); // TODO: gamma per channel? byte[] lut = new byte[256]; double scale = 255.0 / (maxValue - minValue); double gammaPower = 1.0 / gammaValue; for (int i = (int) minValue; i < (int) maxValue; i++) { if (gammaValue == 1.0f) { lut[i] = (byte) ((i - minValue) * scale); } else { lut[i] = (byte) ((Math.pow(i / 255.0, gammaPower) * 255.0 - minValue) * scale); } } for (int i = 0; i < minValue; i++) { lut[i] = 0; } for (int i = (int) maxValue; i < 256; i++) { lut[i] = (byte) 255; } LookupTableJAI lookup = new LookupTableJAI(lut); pb = new ParameterBlock(); pb.addSource(source); pb.add(lookup); return JAI.create("lookup", pb, ManipulationUtils.getRenderingHints(getImage())); } @SuppressWarnings("unused") private BufferedImage getTileAsBufferedImage(int tileX, int tileY) { Raster tile = getImage().getTile(tileX, tileY); DataBuffer dataBuffer = tile.getDataBuffer(); WritableRaster wr = Raster.createWritableRaster(/*sampleModel*/getImage().getSampleModel(), dataBuffer, new Point(0, 0)); BufferedImage bi = new BufferedImage(/*colorModel*/getImage().getColorModel(), wr, getImage().getColorModel().isAlphaPremultiplied(), null); return bi; } public BufferedImage getPreviewImage(int sizeX, int sizeY) { if ((mipMaps != null) && (mipMaps.length > 0) && (mipMaps[mipMaps.length - 1] != null)) { BufferedImage image = new BufferedImage(sizeX, sizeY, BufferedImage.TYPE_INT_RGB); if (mipMaps[mipMaps.length - 1].getWidth() == 0) { logger.error("Error mipmap width=0"); return null; } logger.trace("requesting layer "+(mipMaps.length - 1)+" ; "+mipMaps.length+" layerwidth: "+mipMaps[mipMaps.length-1].getImage().getWidth()); image.getGraphics().drawImage(mipMaps[mipMaps.length - 1].getImage().getAsBufferedImage(), 0, 0, sizeX, sizeY, null); logger.trace("retrieved"); return image; } else { Object source = null; // Object source = sourceImage; // if (sourceImage instanceof RawDataFile) { // source = DALConfig.getImageProvider().getRawDataFileUrl((RawDataFile) sourceImage); // } return TiffConverter.getDownsampledImage(getImage(), source, sizeX, sizeY, this.getType(), false, 1); } } public int getTileWidth() { return getImage().getTileWidth(); } public int getTileHeight() { return getImage().getTileHeight(); } public int getWidth() { return width; } public int getHeight() { return height; } public int getTileGridXOffset() { return tileGridXOffset; } public int getTileGridYOffset() { return tileGridYOffset; } public void setTileGridXOffset(int tileGridXOffset) { this.tileGridXOffset = tileGridXOffset; } public void setTileGridYOffset(int tileGridYOffset) { this.tileGridYOffset = tileGridYOffset; } // public TileCache getTileCache() { // return tileCache; // } // // // public void setTileCache(TileCache tileCache) { // this.tileCache = tileCache; // } public String getImageName() { return imageName; } public void setImageName(String imageName) { this.imageName = imageName; } public void setMipMaps(TiledImagePainter[] mipMaps) { this.mipMaps = mipMaps; } public TiledImagePainter[] getMipMaps() { return mipMaps; } public boolean isGenerateMipMaps() { return generateMipMaps; } public void setGenerateMipMaps(boolean generateMipMaps) { this.generateMipMaps = generateMipMaps; } public boolean hasMipMaps() { return (mipMaps != null && mipMaps.length > 0); } public void setChannel(PlanarImage channel, int chanNr) { switch (chanNr) { case CHANNEL_RED: { setRedChannel(channel); break; } case CHANNEL_GREEN: { setGreenChannel(channel); break; } case CHANNEL_BLUE: { setBlueChannel(channel); break; } case CHANNEL_OVERLAY: { setOverlayChannel(channel); break; } } } public PlanarImage getChannel(int chanNr) { switch (chanNr) { case CHANNEL_RED: { return getRedChannel(); } case CHANNEL_GREEN: { return getGreenChannel(); } case CHANNEL_BLUE: { return getBlueChannel(); } case CHANNEL_OVERLAY: { return getOverlayChannel(); } default: return null; } } public void setChannelRdfId(int rdfId, int chanNr) { switch (chanNr) { case CHANNEL_RED: { setRedChannelRdfId(rdfId); break; } case CHANNEL_GREEN: { setGreenChannelRdfId(rdfId); break; } case CHANNEL_BLUE: { setBlueChannelRdfId(rdfId); break; } case CHANNEL_OVERLAY: { setOverlayChannelRdfId(rdfId); break; } } } public int getChannelRdfId(int chanNr) { switch (chanNr) { case CHANNEL_RED: { return getRedChannelRdfId(); } case CHANNEL_GREEN: { return getGreenChannelRdfId(); } case CHANNEL_BLUE: { return getBlueChannelRdfId(); } case CHANNEL_OVERLAY: { return getOverlayChannelRdfId(); } default: return 0; } } public int getRedChannelRdfId() { return redChannelRdfId; } public void setRedChannelRdfId(int redChannelRdfId) { this.redChannelRdfId = redChannelRdfId; } public int getGreenChannelRdfId() { return greenChannelRdfId; } public void setGreenChannelRdfId(int greenChannelRdfId) { this.greenChannelRdfId = greenChannelRdfId; } public int getBlueChannelRdfId() { return blueChannelRdfId; } public void setBlueChannelRdfId(int blueChannelRdfId) { this.blueChannelRdfId = blueChannelRdfId; } public OrbitTiledImage2 getRedChannel() { return redChannel; } public void setRedChannel(PlanarImage redChannel) { //this.redChannel = redChannel; this.redChannel = setOrWrapImage(redChannel); } public OrbitTiledImage2 getGreenChannel() { return greenChannel; } public void setGreenChannel(PlanarImage greenChannel) { //this.greenChannel = greenChannel; this.greenChannel = setOrWrapImage(greenChannel); } public OrbitTiledImage2 getBlueChannel() { return blueChannel; } public void setBlueChannel(PlanarImage blueChannel) { //this.blueChannel = blueChannel; this.blueChannel = setOrWrapImage(blueChannel); } public OrbitTiledImage2 getOverlayChannel() { return overlayChannel; } public void setOverlayChannel(PlanarImage overlayChannel) { //this.overlayChannel = overlayChannel; this.overlayChannel = setOrWrapImage(overlayChannel); } public int getOverlayChannelRdfId() { return overlayChannelRdfId; } public void setOverlayChannelRdfId(int overlayChannelRdfId) { this.overlayChannelRdfId = overlayChannelRdfId; } public double getBrightness() { return brightness; } /** * Sets the brightness and for all embedded mipmaps, too. * * @param brightness */ public void setBrightness(double brightness) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setBrightness((int) brightness); this.brightness = brightness; // and also for all embedded mipMaps if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setBrightness(brightness); } } } public double getContrast() { return contrast; } public void setContrast(double contrast) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setContrast((int) contrast); this.contrast = contrast; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setContrast(contrast); } } } public int getBlur() { return blur; } public void setBlur(int blur) { this.blur = blur; } public double getRedAdjust() { return redAdjust; } public void setRedAdjust(double redAdjust) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setRed((int) redAdjust); this.redAdjust = redAdjust; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setRedAdjust(redAdjust); } } } public double getGreenAdjust() { return greenAdjust; } public void setGreenAdjust(double greenAdjust) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setGreen((int) greenAdjust); this.greenAdjust = greenAdjust; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setGreenAdjust(greenAdjust); } } } public double getBlueAdjust() { return blueAdjust; } public void setBlueAdjust(double blueAdjust) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setBlue((int) blueAdjust); this.blueAdjust = blueAdjust; // and also for all embedded mipMaps if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setBlueAdjust(blueAdjust); } } } public double getGamma() { return gamma; } public void setGamma(double gamma) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setGamma((int) gamma); this.gamma = gamma; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setGamma(gamma); } } } public boolean isRedActive() { return redActive; } public void setRedActive(boolean redActive) { this.redActive = redActive; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setRedActive(redActive); } } } public boolean isGreenActive() { return greenActive; } public void setGreenActive(boolean greenActive) { this.greenActive = greenActive; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setGreenActive(greenActive); } } } public boolean isBlueActive() { return blueActive; } public void setBlueActive(boolean blueActive) { this.blueActive = blueActive; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setBlueActive(blueActive); } } } public int getDeconvChannel() { return deconvChannel; } public void setDeconvChannel(int deconvChannel) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setDeconvChannel(deconvChannel); this.deconvChannel = deconvChannel; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setDeconvChannel(deconvChannel); } } } public String getDeconvName() { return deconvName; } public void setDeconvName(String deconvName) { if (imageAdjustments == null) imageAdjustments = new OrbitUtils.ImageAdjustments(); imageAdjustments.setDeconvName(deconvName); this.deconvName = deconvName; if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setDeconvName(deconvName); } } } /** * Sets the channelContributions in the image and all images of mip layers if the image is a IOrbitImageMultiChannel (otherwise it does nothing). * @param channelContributions */ public void setChannelContributions(final float[] channelContributions) { if (image !=null && image instanceof OrbitTiledImageIOrbitImage) { IOrbitImage oi = ((OrbitTiledImageIOrbitImage) image).getOrbitImage(); if (oi instanceof IOrbitImageMultiChannel) { final IOrbitImageMultiChannel oim = (IOrbitImageMultiChannel) oi; oim.setChannelContributions(channelContributions); } } if (mipMaps != null) { for (TiledImagePainter tip : mipMaps) { tip.setChannelContributions(channelContributions); } } } public OrbitUtils.ImageAdjustments getImageAdjustments() { return imageAdjustments; } public void setImageAdjustments(OrbitUtils.ImageAdjustments imageAdjustments) { this.imageAdjustments = imageAdjustments; /* if (mipMaps!=null) { for (TiledImagePainter tip : mipMaps) { tip.setImageAdjustments(imageAdjustments); } } */ } @Override public void close() throws IOException { if (image!=null) image.close(); if (mipMaps!=null) { for (TiledImagePainter tip: mipMaps) { tip.close(); } } } /** * Stops the rendering thread. Call at the end of an Orbit application! */ /* public static void shutDown() { if (executorService!=null) { try { executorService.shutdown(); } catch (Exception e) { e.printStackTrace(); } } } */ }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy