ImagePlus, ImageProcessor <--> BufferedImage, for 16-bit unsigned gray image?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

ImagePlus, ImageProcessor <--> BufferedImage, for 16-bit unsigned gray image?

Bill Christens-Barry
I'm stumped in my efforts to move between an ImagePlus and a
BufferedImage for a TYPE_USHORT_GRAY image in a program that use ij.jar.
The main problem seems to be related to how to manipulate values in a
short array, given Java's lack of unsigned short integers. I've been
playng with ij-ImageIO BufferedImageCreator class, which has a
create(ij.process.ShortProcessor src) method that creates a
BufferedImage from a ShortProcessor, but this hasn't gotten me home yet.
I didn't see an obvious solution when I searched the list archives, and
wonder if anyone can set me straight on how to do this.

I have a 16-bit gray TIFF input image that has pixel values from 0 to
around 26722 (although they could range up to 65535). I want to invert
and linearly rescale these values so that they cover the range from 0 to
65535, with 0 --> 65535, and 26722 --> 0. In particular, I want to get
these values back into a BufferedImage that will be saved to disk and
used elsewhere in the program.

The following code yields an image that does not have the desired range
of values (0 to 65535). I've tried many different ways to manipulate the
values in :
my ImageProcessor ipin, but none of them result in an output image that
has the desired range of values. Here's my code:

  public static void generateProcImage() {
        ImagePlus imageProc = new ImagePlus(gelFile.getAbsolutePath());
//load the image
        ImageProcessor ipin = new ShortProcessor(width, height);
        ipin = imageProc.getProcessor();

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                ipin.putPixel(x, y, (int) (2.4524736*ipin.getPixel(x, y)));
                imageProc.setProcessor(null, ipin);
                ipin.putPixel(x, y, (short) ((int) (65535 - (int)
(0xffff & ipin.getPixel(x, y))))); // I've tried all kinds of tricks here.
                imageProc.setProcessor(null, ipin);
            }
        }

        BufferedImage tempCB =
BufferedImageCreator.create((ShortProcessor) ipin);

        File f2=new File(theGel.getProcessedImagePath() + "/" +
gelFile.getName().replaceAll(".tif", "") +  "-PR.tif");
        newGelImageFile = new File(theGel.getProcessedImagePath() + "/"
+ gelFile.getName().replaceAll(".tif", "") +  "-PR.tif");
        FileOutputStream f2b = null;
     
        try {
            f2b = new FileOutputStream(f2);
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            System.exit(0);
        }
       
        TIFFEncodeParam params = new TIFFEncodeParam();
        params.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
        ImageEncoder encoder =
ImageCodec.createImageEncoder("TIFF",f2b,params);
       
        if(encoder == null) {
            System.out.println("imageEncoder is null");
            System.exit(0);
        }
       
        try {
            encoder.encode(tempCB);
            //encoder.encode(tempBI);
        } catch (IOException ex) {
            System.exit(0);
        }
    }

Perhaps related, the ImageJ source code for TiffEncoder contains the
following (fragment):

   public TiffEncoder (FileInfo fi) {
       this.fi = fi;
       fi.intelByteOrder = false;
       bitsPerSample = 8;
       samplesPerPixel = 1;
       nEntries = 9;
       int bytesPerPixel = 1;
       switch (fi.fileType) {
            case FileInfo.GRAY8:
                photoInterp = fi.whiteIsZero?0:1;
                break;
            case FileInfo.GRAY16_UNSIGNED:
            case FileInfo.GRAY16_SIGNED:
                bitsPerSample = 16;
                photoInterp = fi.whiteIsZero?0:1;
                bytesPerPixel = 2;
                break;
            case FileInfo.GRAY32_FLOAT:
                bitsPerSample = 32;
                photoInterp = fi.whiteIsZero?0:1;
                bytesPerPixel = 4;
                break;
            case FileInfo.RGB:
                photoInterp = 2;
                samplesPerPixel = 3;
                bytesPerPixel = 3;
                break;
            case FileInfo.COLOR8:
                photoInterp = 3;
                nEntries = 10;
                break;
            default:
                photoInterp = 0;
        }

I see here that the case for GRAY16_UNSIGNED has no code. Why don't the
relevant variable values as for the other cases need to be assigned for
GRAY16_UNSIGNED?

As you can see, I'm really perplexed by the use of unsigned 16-bit
images in a world lacking 16-bit unsigned types. Thanks for any
explanations, pointers, or other comments.

Bill Christens-Barry
Reply | Threaded
Open this post in threaded view
|

Re: ImagePlus, ImageProcessor <--> BufferedImage, for 16-bit unsigned gray image?

ctrueden
Hi Bill,

>I see here that the case for GRAY16_UNSIGNED has no code. Why don't the
>relevant variable values as for the other cases need to be assigned for
>GRAY16_UNSIGNED?

Actually, without a break statement, the case falls through into the next
one, meaning that GRAY16_SIGNED and GRAY16_UNSIGNED execute the same case
there. GRAY16_UNSIGNED does not need a separate case because the variable
values are the same as for GRAY16_SIGNED.

As for how to manipulate unsigned values in an array of shorts, there are
two ways I have typically seen it done in Java. One is to bloat everything
up to the next type (int for 16-bit), because it makes coding easy. Of
course, everything takes twice as much memory then. The other way (which I
understand is how BufferedImages handle things) is to use shorts anyway, but
simply treat them as unsigned in key places (like when mapping color values
to the screen, or dumping values to the console). The nice thing about
2s-complement arithmetic is that much of the math works the same regardless
of whether the fixed-width values are being treated as signed or unsigned.
For example, with width 3, the unsigned operation 010+100 (2+4) = 110 (6),
and the signed operation 010+100 (2+-4) = 110 (-2). Nonetheless, it is
frustrating that the Java designers have not seen fit to include an
"unsigned" type modifier within the language.

For constructing your BufferedImage, you could try creating one of the
proper type, then painting the ImagePlus's Image object directly onto the
BufferedImage canvas. It generally does the type conversions and such
correctly. You would still need to scale and transform your image, but you
could try using BufferedImage operations (see java.awt.image.BufferedImageOp).
Alternately, it should be possible to do it by hand by extracting the values
from the BufferedImage's DataBuffer and then modifying them.

If I had more time I would try to mock up an example for you, but my todo
list is far too long as of late.

Good luck,
Curtis

On 7/24/06, Bill Christens-Barry <[hidden email]> wrote:

>
> I'm stumped in my efforts to move between an ImagePlus and a
> BufferedImage for a TYPE_USHORT_GRAY image in a program that use ij.jar.
> The main problem seems to be related to how to manipulate values in a
> short array, given Java's lack of unsigned short integers. I've been
> playng with ij-ImageIO BufferedImageCreator class, which has a
> create(ij.process.ShortProcessor src) method that creates a
> BufferedImage from a ShortProcessor, but this hasn't gotten me home yet.
> I didn't see an obvious solution when I searched the list archives, and
> wonder if anyone can set me straight on how to do this.
>
> I have a 16-bit gray TIFF input image that has pixel values from 0 to
> around 26722 (although they could range up to 65535). I want to invert
> and linearly rescale these values so that they cover the range from 0 to
> 65535, with 0 --> 65535, and 26722 --> 0. In particular, I want to get
> these values back into a BufferedImage that will be saved to disk and
> used elsewhere in the program.
>
> The following code yields an image that does not have the desired range
> of values (0 to 65535). I've tried many different ways to manipulate the
> values in :
> my ImageProcessor ipin, but none of them result in an output image that
> has the desired range of values. Here's my code:
>
>   public static void generateProcImage() {
>         ImagePlus imageProc = new ImagePlus(gelFile.getAbsolutePath());
> //load the image
>         ImageProcessor ipin = new ShortProcessor(width, height);
>         ipin = imageProc.getProcessor();
>
>         for (int y = 0; y < height; y++) {
>             for (int x = 0; x < width; x++) {
>                 ipin.putPixel(x, y, (int) (2.4524736*ipin.getPixel(x,
> y)));
>                 imageProc.setProcessor(null, ipin);
>                 ipin.putPixel(x, y, (short) ((int) (65535 - (int)
> (0xffff & ipin.getPixel(x, y))))); // I've tried all kinds of tricks here.
>                 imageProc.setProcessor(null, ipin);
>             }
>         }
>
>         BufferedImage tempCB =
> BufferedImageCreator.create((ShortProcessor) ipin);
>
>         File f2=new File(theGel.getProcessedImagePath() + "/" +
> gelFile.getName().replaceAll(".tif", "") +  "-PR.tif");
>         newGelImageFile = new File(theGel.getProcessedImagePath() + "/"
> + gelFile.getName().replaceAll(".tif", "") +  "-PR.tif");
>         FileOutputStream f2b = null;
>
>         try {
>             f2b = new FileOutputStream(f2);
>         } catch (FileNotFoundException ex) {
>             ex.printStackTrace();
>             System.exit(0);
>         }
>
>         TIFFEncodeParam params = new TIFFEncodeParam();
>         params.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
>         ImageEncoder encoder =
> ImageCodec.createImageEncoder("TIFF",f2b,params);
>
>         if(encoder == null) {
>             System.out.println("imageEncoder is null");
>             System.exit(0);
>         }
>
>         try {
>             encoder.encode(tempCB);
>             //encoder.encode(tempBI);
>         } catch (IOException ex) {
>             System.exit(0);
>         }
>     }
>
> Perhaps related, the ImageJ source code for TiffEncoder contains the
> following (fragment):
>
>    public TiffEncoder (FileInfo fi) {
>        this.fi = fi;
>        fi.intelByteOrder = false;
>        bitsPerSample = 8;
>        samplesPerPixel = 1;
>        nEntries = 9;
>        int bytesPerPixel = 1;
>        switch (fi.fileType) {
>             case FileInfo.GRAY8:
>                 photoInterp = fi.whiteIsZero?0:1;
>                 break;
>             case FileInfo.GRAY16_UNSIGNED:
>             case FileInfo.GRAY16_SIGNED:
>                 bitsPerSample = 16;
>                 photoInterp = fi.whiteIsZero?0:1;
>                 bytesPerPixel = 2;
>                 break;
>             case FileInfo.GRAY32_FLOAT:
>                 bitsPerSample = 32;
>                 photoInterp = fi.whiteIsZero?0:1;
>                 bytesPerPixel = 4;
>                 break;
>             case FileInfo.RGB:
>                 photoInterp = 2;
>                 samplesPerPixel = 3;
>                 bytesPerPixel = 3;
>                 break;
>             case FileInfo.COLOR8:
>                 photoInterp = 3;
>                 nEntries = 10;
>                 break;
>             default:
>                 photoInterp = 0;
>         }
>
> I see here that the case for GRAY16_UNSIGNED has no code. Why don't the
> relevant variable values as for the other cases need to be assigned for
> GRAY16_UNSIGNED?
>
> As you can see, I'm really perplexed by the use of unsigned 16-bit
> images in a world lacking 16-bit unsigned types. Thanks for any
> explanations, pointers, or other comments.
>
> Bill Christens-Barry
>