lord.cookies
lord.cookies

Reputation: 39

Faster performance with TIFF rendering in Java with JAI

I have been working on software that analyzes microscopy data, which is stored as a multilayer tiff file. After looking through StackOverflow and through the JAI documentation, I hobbled together some code to store the tiff stack and render it correctly.

However, it suffers from some pretty bad performance. I had hoped for some faster performance after reading posts such as this: Anyone have any luck writing a very fast tiff viewer/editor in Java?

Unfortunately, it's not nearly as good as I would have hoped for. I do not have much experience working with external image libraries, or with Java's Graphics capabilities for that matter, so I'm not sure how I would improve this.

For some context, here is a video of the 'stuttering' I experience while traversing through the tiff stack: http://www.youtube.com/watch?v=WiR4o6TsqyM&feature=channel_video_title

Note how the frames stutter occasionally as the slider is dragged.

I improved performance when the image is zoomed in by removing smoothing when scaling, but it still isn't as fast I would have liked.

My code is below:

/** Converts the RenderedImage into a BufferedImage, which is more flexible
 * @param img   The original RenderedImage
 * @return  The new BufferedImage
 */
public BufferedImage convertRenderedImage(RenderedImage img) {
    if (img instanceof BufferedImage) {
        return (BufferedImage)img;  
    }   
    ColorModel cm = img.getColorModel();
    int width = img.getWidth();
    int height = img.getHeight();
    WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
    boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
    Hashtable<String, Object> properties = new Hashtable<String, Object>();
    String[] keys = img.getPropertyNames();
    if (keys!=null) {
        for (int i = 0; i < keys.length; i++) {
            properties.put(keys[i], img.getProperty(keys[i]));
        }
    }
    BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
    img.copyData(raster);
    return result;
}

/** Draws everything on top of the scaled image
 * @param scale The current scale value
 */
private void setScaledImage(double scale) 
{
    //Optimizes the image type for drawing onto the screen
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice device = env.getDefaultScreenDevice();
    GraphicsConfiguration config = device.getDefaultConfiguration();

    int w = (int)(scale*currImage.getWidth());
    int h = (int)(scale*currImage.getHeight());
    scaled = config.createCompatibleImage(w, h, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = scaled.createGraphics();
    g2.drawImage(currImage, 0, 0, w, h, null);
    g2.dispose();
}

I've loaded the multilayer tiff into an imageDecoder with JAI, and to display the correct layer when the slider is dragged, I use the following code:

try {
    currImage = convertRenderedImage(imageStack.decodeAsRenderedImage(currentLayerSlider.getValue()-1));
    setScaledImage (scaleSlider.getValue()/100.0);
    curves.changeFrame(currentLayerSlider.getValue());
    drawImageOverlay ();} 
catch (IOException e) {
    e.printStackTrace();
    currImage = null;}

Basically, whenever the slider is dragged, I extract a rendered image from the imageStack, convert it to a BufferedImage (so that it can be used with an ImageIcon), scale it, and set it as the displayed image. I think the slow part is converting it to the buffered image, and scaling it. Is there any way to do this faster?

Does anyone have any suggestions for how to improve things, or insight from similar experiences?

Any help would be much appreciated.

Upvotes: 3

Views: 2399

Answers (4)

user1495298
user1495298

Reputation: 104

// can render the output with the below code protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub System.out.println("inside GET ..."); String pageNumb = request.getParameter("page"); System.out.println("pageNumb:: "+pageNumb); String filePath = "/Users/test/Downloads/Multi_page24bpp.tif";

    SeekableStream s = new FileSeekableStream(filePath.trim());
    TIFFDecodeParam param = null;
    ImageDecoder dec = ImageCodec.createImageDecoder("tiff", s, param);
    String totNumberOfPage =String.valueOf(dec.getNumPages());
    System.out.println("number of pages:: "+totNumberOfPage);
    RenderedImage op = dec.decodeAsRenderedImage(Integer.parseInt(pageNumb)); 
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write (op, "jpeg", baos);
//    response = ServletActionContext.getResponse();
    response.setContentType("image/jpeg");
    OutputStream out = response.getOutputStream();
    out.write(baos.toByteArray());
    out.flush();
}

Upvotes: 0

manoj kumar yadav
manoj kumar yadav

Reputation: 1

you can change your renderd image in to buffer and then display it in j label because renderd image is jai image and j label take onley buffer image and do not try to display it in jai you try it in jLabel

Upvotes: 0

Matthieu
Matthieu

Reputation: 3097

I have run into the exact same problem and ended up using the decodeAsRaster() to pass to a convert method:

public static BufferedImage convertRenderedImage(Raster raster) {
    int w = raster.getWidth();
    int h = raster.getHeight();
    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    int[] sourcePixels = ((DataBufferInt)raster.getDataBuffer()).getData();
    int[] destPixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
    System.arraycopy(pixels, 0, rasterData, 0, pixels.length); // The speed-up!
    return image;
}

Then change your code to:

currImage = convertRenderedImage(imageStack.decodeAsRaster(currentLayerSlider.getValue()-1));

The credit of speed improvement is to be given back to @Brent Nash here: we basically get the pixel array from the original Raster and the pixel array for the BufferedImage to fill up and perform a System.arraycopy instead of the "expensive" image.setData(raster) which is more portable but, for each pixel, calls a few methods to check consistency and type, while we already have all the information we need.

This code should be valid for the BufferedImage.TYPE_INT_RGB, though I must confess I was not able to test it, but I know it's working on BufferedImage.TYPE_BYTE_GRAY (and byte[]/DataBufferByte).

Upvotes: 3

Horonchik
Horonchik

Reputation: 477

Instead of using BufferedImage and ImageIcon for JLabel, have you tried using DisplayJAI? it can display RenderedImage directly.

Upvotes: 0

Related Questions