Java error on bilinear interpolation of 16 bit data
Asked Answered
Q

3

11

I'm having an issue using bilinear interpolation for 16 bit data. I have two images, origImage and displayImage. I want to use AffineTransformOp to filter origImage through an AffineTransform into displayImage which is the size of the display area. origImage is of type BufferedImage.TYPE_USHORT_GRAY and has a raster of type sun.awt.image.ShortInterleavedRaster. Here is the code I have right now

displayImage = new BufferedImage(getWidth(), getHeight(), origImage.getType());
try {
    op = new AffineTransformOp(atx, AffineTransformOp.TYPE_BILINEAR);
    op.filter(origImage, displayImage);
}
catch (Exception e) {
    e.printStackTrace();
}

In order to show the error I have created 2 gradient images. One has values in the 15 bit range (max of 32767) and one in the 16 bit range (max of 65535). Below are the two images

15 bit image alt text

16 bit image alt text

These two images were created in identical fashions and should look identical, but notice the line across the middle of the 16 bit image. At first I thought that this was an overflow problem however, it is weird that it's manifesting itself in the center of the gradient instead of at the end where the pixel values are higher. Also, if it was an overflow issue than I would suspect that the 15 bit image would have been affected as well.

Any help on this would be greatly appreciated.

I was just wondering why no one is answering, did I provide enough information? Is more info needed?

Below is the code I use to generate the AffineTransform. All of the referenced variables are calculated based off of user input (mouse movement) and should be correct (it's been tested by a lot of people including myself). Hopefully this can help with the error.

AffineTransform panTranslate = new AffineTransform();
panTranslate.translate(imagePanOffset.x, imagePanOffset.y);

AffineTransform rotateCenterTranslate = new AffineTransform();
rotateCenterTranslate.translate(imageRotateCTR.x, imageRotateCTR.y);
AffineTransform rotateTransform = new AffineTransform();
rotateTransform.rotate(Math.toRadians(rotateValue));
AffineTransform rotateAntiCenterTranslate = new AffineTransform();
rotateAntiCenterTranslate.translate(-imageRotateCTR.x, -imageRotateCTR.y);

AffineTransform translateTransform = new AffineTransform();
translateTransform.translate(imageMagOffset.x, imageMagOffset.y);

AffineTransform flipMatrixTransform = new AffineTransform();

switch (flipState) {
    case ENV.FLIP_NORMAL: // NORMAL
        break;

    case ENV.FLIP_TOP_BOTTOM: // FLIP
        flipMatrixTransform.scale(1.0, -1.0);
        flipMatrixTransform.translate(0.0, -h);
        break;

    case ENV.FLIP_LEFT_RIGHT: // MIRROR
        flipMatrixTransform.scale(-1.0, 1.0);
        flipMatrixTransform.translate(-w, 0.0);
        break;

    case ENV.FLIP_TOP_BOTTOM_LEFT_RIGHT: // FLIP+MIRROR
        flipMatrixTransform.scale(-1.0, -1.0);
        flipMatrixTransform.translate(-w, -h);
        break;
}

scaleTransform = new AffineTransform();
scaleTransform.scale(magFactor, magFactor);

AffineTransform atx = new AffineTransform();
atx.concatenate(panTranslate);
atx.concatenate(rotateCenterTranslate);
atx.concatenate(rotateTransform);
atx.concatenate(rotateAntiCenterTranslate);
atx.concatenate(translateTransform);
atx.concatenate(flipMatrixTransform);
atx.concatenate(scaleTransform);

I still have no idea what's going on here. I'd really appreciate any help that can be provided. I've also attached an example of the bug happening in a real image that I encounter for more reference.

Here is the bug happening in an X-ray of the hand alt text

Here is a zoomed up version focused on the area between the thumb and first finger. alt text

Note again how the bug doesn't occur on the extremely white areas, but on the values in the middle of the dynamic range, just like in the gradient image.

I've discovered more information. I was adjusting some of the transforms and found that the bug does not occur if I just filter through an identity matrix. It also doesn't occur if I translate by an integer amount. It does occur if I translate by a non integer amount. It also occurs if I zoom by any amount other than 1 (integer or not). Hopefully this helps.

After more experimenting, the bug definitely manifests itself at the boundary pixels between half the max intensity (65535/2 = 32767.5). It also ONLY occurs at this value. I hope this might help diagnosis!!

At the request of AlBlue here is code that is completely independent of my application that can generate the bug. Note that in the original post I included an image gradient generated with the below code however I zoomed in on one of the gradients to better show the effect. You should see the effect four times on the 0.5 translated image and not on either of the other two images. Also note that this bug appears while scaling by any amount other than 1. Just replace AffineTransform.getTranslateInstance() with AffineTransform.getScaleInstance(0.9, 0.9) to see the bug also.

private static class MyJPanel extends JPanel {
    BufferedImage displayImage = null;
    public MyJPanel(double translateValue) {
        super();
        BufferedImage bi = new BufferedImage(1024, 1024, BufferedImage.TYPE_USHORT_GRAY);

        int dataRange = (int)Math.pow(2, 16);
        double step = dataRange/(bi.getRaster().getDataBuffer().getSize()/4.0);
        double value = 0;
        for (int i=0; i<bi.getRaster().getDataBuffer().getSize(); i++) {
            bi.getRaster().getDataBuffer().setElem(i, (int)value);
            if (value >= dataRange)
                value = 0;
            else
                value += step;
        }
        displayImage = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
        AffineTransform tx = AffineTransform.getTranslateInstance(translateValue, translateValue);
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
        op.filter(bi, displayImage);
    }

    public void paint(Graphics g) {
        super.paint(g);
        g.drawImage(displayImage, 0, 0, this);
    }
}

private static void showDisplayError() {
    JDialog dialog1 = new JDialog();
    dialog1.setTitle("No Translation");
    MyJPanel panel1 = new MyJPanel(0);
    dialog1.getContentPane().add(panel1);
    dialog1.setSize(1024, 1024);
    dialog1.setVisible(true);

    JDialog dialog2 = new JDialog();
    dialog2.setTitle("Translation of 0.5");
    MyJPanel panel2 = new MyJPanel(0.5);
    dialog2.getContentPane().add(panel2);
    dialog2.setSize(1024, 1024);
    dialog2.setVisible(true);

    JDialog dialog3 = new JDialog();
    dialog3.setTitle("Translation of 1.0");
    MyJPanel panel3 = new MyJPanel(1.0);
    dialog3.getContentPane().add(panel3);
    dialog3.setSize(1024, 1024);
    dialog3.setVisible(true);
}

As another update, I just tried this on Fedora 10 and saw the bug is still present.

Quadragesimal answered 11/3, 2010 at 19:43 Comment(5)
It does looks like some sort of overflow problem, since it is precisely the three rows of pixels that should have the RGB color 128-128-128 that have been changed. (They are replaced by one row of 57-57-57, one row of 232-232-232, and one row of 151-151-151.) But I have no idea why.Compatriot
Thanks for the reply. These images should be grayscale, so there should only be one channel, why do you suggest the particular values you posted?Quadragesimal
I opened your png images in gimp and looked at the color values. Both have a smooth vertical gradient from 0 to 255 (0x00 to 0xff), except the white and black bands at the top and bottom, and the three problematic pixel rows in the middle of the second image. But again, I have no idea where they come from.Compatriot
OK. I just choose to save them in .png format using ImageIO.write() to post them here. Could more information be gleamed if I saved them in a different format?Quadragesimal
I am looking for a workaround, anyone? Encountered exactly the same issue on NM medical images.Riffle
A
2

You can work around it by applying the transform in a Graphics2D instead of an AffineTransformOp:

if (useG2D) {
    Graphics2D g = displayImage.createGraphics();
    g.transform(tx);
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                       RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.drawImage(bi, null, 0, 0);
} else {
    AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
    op.filter(bi, displayImage);
}

I don't know why this would give different output, but it does.

Note: useG2D could be a constant or it could be set based on the result of tx.getType(). The bug does not occur with TYPE_QUADRANT_ROTATION, TYPE_FLIP or TYPE_IDENTITY transforms.

Aedes answered 11/3, 2010 at 19:43 Comment(0)
D
2

What version of java (java -version) and OS are you using? It might be a bug in the transform (which has since been fixed) or it might be an error in the rendering to PNG.

Have you tried using a NEAREST_NEIGHBOR filter instead of the BILINEAR one?

Desiderata answered 20/3, 2010 at 8:45 Comment(13)
Yes I have, it doesn't appear while using nearest neighbor. However, since I'm working with medical data the user will have 16 bit data displayed on a very high resolution monitors where the nearest neighbor interpolation will not be sufficient.Quadragesimal
I'm using Windows XP SP3 32 bit and Java 1.6.0_18Quadragesimal
It's not a bug rendering to PNG. I load the file in as a Dicom file using dcm4che (www.dcm4che.org) and create a BufferedImage. I then paint this BufferedImage to the screen and see the error. I just chose to save it as a PNG to show the bug here.Quadragesimal
Sounds like it's an overflow bug in the Java implementation, unfortunately. And it also sounds like you have the most up-to-date version of the JVM so this is unlikely to help. The only other thing I'd suggest is to try the TYPE_BICUBIC to see if that works any better than the other two. I don't know when that was added (1.5? 1.6?) which may be an issue for you. Lastly, you could always filter for that known value in your code at a post-processing step; sounds like it's always that one value that does it. Can you put the gradient generating code up? I can test on OSX.Desiderata
It does appear on TYPE_BICUBIC as well. I've posted code that will generate the bug independent of my source. Please let me know what you discover.Quadragesimal
OK, so I can run this on my Mac (java -version 1.6.0_17) and get a similar result as yours. The no translation and 1 translation have no visible artefact; the translated by 0.5 has an artefact across the middle. Oddly, though, on the left hand side of the image it comes out as pure white, whereas the right hand side of the image is pure black. Either way, it seems to be a Java bug, which means that at the moment, your choices are (a) raise a bug with Sun, and (b) implement your own as a workaround.Desiderata
Thanks for looking into this. As for your issue with the left vs. right sides of the image that will change as you zoom in. If you can zoom in on the image you will see the same effect that I've shown in my images. Also, I think I'll submit this to Sun. Do you know what their usual turnaround time is on these bugs?Quadragesimal
Given you have a test case (though please submit it within a class and a main method to make it easier) it should be easy for them to investigate. However, I wouldn't get your hopes up too high for a speedy response. Have you tried using a bugger buffer size for your data, like TYPE_UINT or one of the floating point values, like DOUBLE? Not only would they five more precision, the double value is likely to be less affected by non-integral transforms.Desiderata
How do you implement a bigger buffer size? Could you provide some example code. I tried new BufferedImage(width, height, DataBuffer.TYPE_INT) and then the filtering took an extremely long time. Hopefully the Sun guys will look into this soon. Thanks again for looking at this.Quadragesimal
That was the kind of idea I was thinking of. Alternatively, you might try a floating point rather than int representation, which would be able to hold smaller values without loss of precision. Unfortunately a larger data set may take longer; but does it avoid the bug?Desiderata
It kinda does. The original bug isn't there, but the color is all messed up. Also the amount of time it takes to filter makes the application unusable, so I guess I'm stuck waiting for Sun.Quadragesimal
I don't think I'm going to be able to help any further. You might want to file a bug with Sun, post it here for others to view in the future, and mark an answer as accepted since I don't think anyone but Sun can help with this, short of writing your own filtering operation.Desiderata
Thanks, I have filed a bug with Sun, but they haven't approved it yet, once they approve it I will post it here as another answer and accept that. Thanks again for your help with thisQuadragesimal
I
1

Did you solve this? It is likely a being caused by not using the AffineTransformOp correctly. How did you create the AffineTransform atx ? If I have that I should be able to replicate to help debug.

You may wish to have a look at this site too. It contains lots of useful information about AffineTransformOp

Irrepressible answered 16/3, 2010 at 13:15 Comment(4)
I've posted the code for generating the AffineTransform, let me know if this helps.Quadragesimal
And no I haven't figured out a solution to this yet.Quadragesimal
Were you able to find anything out by looking at the source for the AffineTransform?Quadragesimal
It would appear that the transform does affect the presence of the bug. Refer to my latest edit for more information.Quadragesimal

© 2022 - 2024 — McMap. All rights reserved.