Skip to main content

How to write pyramid TIFF File from Huge BMP (OK with small files) ?

39 replies [Last post]
pierre36
Offline
Joined: 2010-04-28

Hi,
I'm having some troubles with JAI : I would like to read a huge BMP file (~4Gb) and write it in a pyramid tiff. I used to process the BMP file with a BufferedImage, but it doesn't work any more with big files (memory problem) :

public void pyramidGenerator(TIFFImageWriter writer,
BufferedImage aInputImage, int tileWidth,
int tileHeight, int resolutionSteps)
throws IOException {

ImageLayout il = new ImageLayout();
il.setTileWidth(tileWidth).setTileHeight(tileHeight);
il.setTileGridXOffset(0).setTileGridYOffset(0);

TIFFEncodeParam tep = new TIFFEncodeParam();
tep.setWriteTiled(true);
tep.setTileSize(tileWidth,tileHeight);
tep.setJPEGCompressRGBToYCbCr(false); //does not work
tep.setLittleEndian(true);

RenderingHints tileHints =new RenderingHints(JAI.KEY_IMAGE_LAYOUT, il);

TIFFImageWriteParam param = (TIFFImageWriteParam) writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

//compression type
param.setCompressionType("JPEG");

//tiling mode
param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
param.setTiling(tileWidth, tileHeight, 0, 0);

//add first layer
RenderedImage renderedImg = aInputImage;
IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(renderedImg), param);

writer.prepareWriteSequence(null);

TIFFDirectory ifd=TIFFDirectory.createFromMetadata(metadata);
TIFFTag tag=new TIFFTag("NewSubfileType", 254, TIFFTag.TIFF_LONG);
TIFFField sub=new TIFFField(tag,TIFFTag.TIFF_LONG , 1, new long[]{0});
ifd.addTIFFField(sub);

IIOImage iioImage = new IIOImage(renderedImg, null, metadata);
writer.writeToSequence(iioImage, param);
System.out.println("*** Layer 1 Done ***");

...

So I guess I should read my BMP File by tiles/parts/pieces, so i don't have to have the whole image in memory (no more use of BufferedImage). I looked for solutions like :

public static RenderedImage readTiled(File f, int tileWidth, int
tileHeight) throws Exception{

ImageInputStream iis = ImageIO.createImageInputStream(f);
ParameterBlockJAI pbj = new ParameterBlockJAI("ImageRead");

ImageLayout layout = new ImageLayout();
layout.setTileWidth(tileWidth);
layout.setTileHeight(tileHeight);

RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout);

pbj.setParameter("Input", iis);
return JAI.create("ImageRead", pbj, hints);
}

But I still have problems with IndexOutOfBoundsException. So this may not be correct and it doesn't not work

Do you have any ideas ? I guess I should learn more about ImageLayout and RenderingHints, but i'm not very familiar with those.

Thank you.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Bob Deen

Rather than calling mosaic.setSource() inside the loop, you should call
pbMosaic.setSource() in the loop and then one mosaic.setParameterBlock()
afterwards. It should be more efficient that way, although perhaps not
by much if no rendering is done in the meantime.

I think what you need to do is to set a RenderingHint on the mosaic
operator to tell it to use 1024x1024 tiles. It could be that it's
getting an inappropriate tile size by default (which may even be the
entire image). You can check this by asking mosaic what its tile size
is and printing it out. You probably should also tell the mosaic what
size to be via RenderingHints (see the MosaicDescriptor docs). It
"should" figure it out automatically, but it's safer to be explicit.
Again, print out what it thinks the size is first as a test, to see if
it got it right. If that print takes forever, then the simple act of
determining the output size is triggering a rendering, which would be a
bad thing (it shouldn't need to do that).

Finally, this all depends on the tiff writer being able to make use of
tiled inputs when writing. It's certainly possible to do so, but I
don't recall how well that's implemented in the IIO tiff plugin. You
might need to tell the tiff writer to write in a tiled format. It's
quite possible the default is to write untiled (which could force
everything to be loaded) or striped (which could force an entire row of
tiles to be loaded). Worst case would be striped where the available
memory was insufficient to hold an entire row of tiles, meaning that
you'd have to re-read the entire row of tiles for every stripe in the
output. THAT would be bad!! So you definitely want to make sure the
output is tiled. I think you can specify that via parameters to the
tiff writer.

Hope that helps...

-Bob

jai-interest@javadesktop.org wrote:
> what are x and y float ?
>
> However, I tried it, and still java.lang.OutOfMemoryError: Java heap space after 17 minutes :
>
>
>
> public static void main(String[] args) throws Exception{
>
> int i;
> TileCache tc=JAI.createTileCache();
> tc.setMemoryCapacity(1024L*1024L*500);
> String workDirectory="C:\\Documents and Settings\\user\\Desktop\\test\\";
> String slideName="slide";
>
> Samples s=new Samples(workDirectory+slideName+".txt");
>
> BMPBuilder_test2 bb=new BMPBuilder_test2();
> bb.setInfos(s.getInfos(),workDirectory);
> File[] f=new File[bb.getFiles().length];
> Tile[] t=new Tile[bb.getFiles().length];
>
> for(i=0;i > f[i]=new File(workDirectory+(bb.getFiles()[i]));
> t[i]=new Tile();
> t[i].f=f[i];
> t[i].x=i;
> t[i].y=0;
> }
>
> List l=Arrays.asList(t);
> PlanarImage test=Tile.getMosaic(l);
> ImageIO.write(test, "TIFF", new FileOutputStream(new File(workDirectory+"toto.tif")));
> }
>
>
>
>
> public class Tile {
> float x;
> float y;
> File f;
>
> public static PlanarImage getMosaic(List tiles) {
>
> ParameterBlock pbMosaic=new ParameterBlock();
> pbMosaic.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY);
>
>
> RenderedOp mosaic = JAI.create("Mosaic",pbMosaic,null);
>
> for (Tile t: tiles) {
>
> RenderedOp image = JAI.create("ImageRead", t.f);
>
> RenderedOp nudged = JAI.create("Translate",(RenderedImage)image, t.x, (Object)t.y);
>
>
> mosaic.addSource((Object)nudged);
> }
> return mosaic;
> }
>
> }
> [Message sent by forum member 'pierre36']
>
> http://forums.java.net/jive/thread.jspa?messageID=403834
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
> For additional commands, e-mail: interest-help@jai.dev.java.net
>

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net

pierre36
Offline
Joined: 2010-04-28

Ok, I have indeed some problems for the downsampling operations (OutOfMemory). First I need to write a small jpeg (~192*148) : I use

RenderingHints hint=new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);

ParameterBlock pb=new ParameterBlock();
pb.addSource(planarImage)
pb.add(scale)
pb.add(scale)
RenderedOp resizedImage=JAI.create("SubsampleAverage", pb, hint);

And then for the writing step, I tried
ImageIO.write(resizedImage, "JPEG", fileoutput)
and JAI.create("ImageWrite", ...)

But each time, it lasts very long, takes a lot of memory and never write the small file (works for medium files).

I also tried to downsample using several steps, same problem (and the scale operator too). And I tried to write a tiff instead of a jpeg.

I think that the downsampling operator don't use the mosaic and tiled structure and that's the problem.

Any ideas ? Should I set again the tiles parameters in the layout of the downsampling function ?

Sorry to keep bothering you, I think this is the last step.

pentath
Offline
Joined: 2009-08-04

Ah, yeah, that. So, in the pseudo code you included, you have a chain of processing steps like this:

planarImage -> subsampleaverage -> imagewrite

Which is good, as long as the connections are always approximately tile-sized.

But if the scale argument passed to subsampleaverage is large, you will have a problem. The subsampleaverage routine, if you read it carefully, creates a box 1/scale by 1/scale pixels in size. If you tried to use this operator to create a 100x100 thumbnail from a large 10k x 10k image, you would use a scale of 100/10000=.01. This requires that for each of the 100x100 output pixels, an average operation is done on an area of the input image containing about (1/.01)^2=10k pixels. I'm not a fan.

Use the 'scale' operator instead. Set the interpolation to bilinear, and repeatedly down sample the image by powers of 2 until you can set the scale to exactly the remaining difference in scale without using a scale less than .5. This is essentially like creating a pyramid, where the only level you care about is the one on top.

For example:

double scale=1;
while (scale>0.01) {
scale *= scale*.5 > 0.01 ? .5 : 0.01/scale;
image = JAI.create(image, scale);
set interpolation to bilinear if scale == .5, nearest otherwise
JAI.create('imagewrite', image, '/tmp/blah');
image = JAI.create('imageread', '/tmp/blah');
}

Note that nearest neighbor interpolation is used to resample the last level because averaging pixels together when scale is between .5 and 1 is dicey -- you will have many pixels that are smudged together with others, degrading the data, so it is often better to use nearest neighbor interpolation instead.

pierre36
Offline
Joined: 2010-04-28

double scale=1;
while (scale>0.01) {
scale *= scale*.5 > 0.01 ? .5 : 0.01/scale;
image = JAI.create(image, scale);
set interpolation to bilinear if scale == .5, nearest otherwise
JAI.create('imagewrite', image, '/tmp/blah');
image = JAI.create('imageread', '/tmp/blah');
}

I need to write the Image each time I downsample it ? it seems odd.
And I will still get a "mosaic" image at the end ?

pentath
Offline
Joined: 2009-08-04

No, you pass in a mosaic the first time, but each time you down sample the image, you write the result to a file on disk, and reread the file for the next scale operator. This is done so it won't matter how big the intermediate result is. If you didn't do this, and just essentially did write(scale(scale(scale(scale(mosaic))))), you would (eventually) get a result, but you would end up reading each input tile many, many, many times. So, you can trade some temporary disk space for processing time, and if your mosaic is very large, it is a trade well worth making.

pierre36
Offline
Joined: 2010-04-28

ok, it should work to get a small picture of the whole image.

But, as I wrote in the first post, I will need to build a whole pyramid TIFF. So in my TIFF File, I should write :

1. the whole full res mosaic Image (this writing step is ok thanks to the first mosaic : ~50000*50000)
2. a thumbnail (~1280*1024) of the whole Image
3. lowers resolutions images (width and height divide by 4 each time until width and height are around 2k pixels).

So I thought I could just get some downsampled mosaic for each resolution step and write it.

MoreOver, I will need a small jpeg file (~192*148), but in fact, If I can write the thumbnail or any lower resolution Image, the downsampling and writing of this file should be trivial.

I tried to downsample as you wrote, but the writing is terribly slow, and, as the 2nd higher Resolution will be around 12000*12000, I may have problems to write it if it's not mosaic-like.

I'm also triing to downsample each initial files and write it, so I get 1000 1280*1024 files, 1000 320*256 files, and so on (and each planarImage would be associated with 1000 differents files), but I would rather not do it. This is a really bad idea I think, and quite inefficient.

pentath
Offline
Joined: 2009-08-04

You should get decent performance if you create each level from the preceding level's results as I indicated above, and make sure you are writing into a tiled image.

Specifically, use ImageWrite to create a TIFF image for each level, with a fairly large tile size, like 512x512. That first operation will take a long, long time. If everything works right, then when down sampling by powers of 4, it should actually take something like 95% of the total processing time, just to create that first image.

Using a single disk, single CPU that fairly imitates much of the desktops out there, my pyramid tiff builder cranks through about 100 MB / minute. It's *very* stupid, though, and of the optimizations we've talked about here, it only does tiling. Is your routine anywhere near that fast?

Finally, if things are running slowly, increase the heap size and the JAI tile cache. I tend to run these things with something like 1 GB of ram given to tile cache, so as to forestall overrunning the cache as long as possible. It will eventually happen, with big images, but the less often we have to purge the cache the better performance is :-/

pierre36
Offline
Joined: 2010-04-28

So, to sum up :

1. I should write all my bmp images to tiled TIFFs, and then create a mosaic Image from them (I don't need to write it, do I ?) - DONE

2. Downsample by 4 this mosaic, then write a tiled TIFF for each level I need - PROBLEMS WHILE WRITING

3. Pyramid Tiff Creation : I first write the first mosaic, and then, I create the others mosaic images from the TIFF written in 2. and write them - SHOULD WORK ONCE 2 IS SOLVED

4. Downscale the lower resolution image to create my small jpg. - OK

Is it right ?

Edit :
I tried this :

public static void downSampleChain(PlanarImage pi) throws Exception{
int width=pi.getWidth();
PlanarImage resizedImage=pi;

while(width>3000){
ParameterBlock pb=new ParameterBlock();
pb.addSource(resizedImage);
pb.add(0.25f);
pb.add(0.25f);
pb.add(0f);
pb.add(0f);
pb.add(new InterpolationNearest());
resizedImage=JAI.create("scale", pb, null);
width=width/4;

TIFFImageWriteParam param = new TIFFImageWriteParam(null);
param.setCompressionMode(ImageWriteParam.MODE_DISABLED);

//tiling mode
param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
param.setTiling(512, 512, 0, 0);
RandomAccessFile rad=new RandomAccessFile(Main.workDirectory+"downsample_"+width+".tif", "rw");

ParameterBlockJAI writeParams = new ParameterBlockJAI("ImageWrite", "rendered");

writeParams.addSource(resizedImage);
writeParams.setParameter("Output", rad);
writeParams.setParameter("Format", "TIFF");
//writeParams.set(param, 12);
JAI.create("ImageWrite", writeParams, null);

resizedImage=JAI.create("ImageRead", Main.workDirectory+"downsample_"+width+".tif");

}

}

The input is the "big" PlanarImage, I downSample it, write it, and read it again ... But I've got :

Error: One factory fails for the operation "ImageWrite"
...
Caused by: java.lang.OutOfMemoryError: Java heap space

after 70MB written.

Edit 3 :
With scale = 4, it seemed to work, the file was already 1GB when I stopped the writing.
Programming is sometimes random :(
Scale 0.5f worked until 300MB and then failed
Scale 2 is 1.7 GB and still running. Is it really random ? I must miss something (it failed at 4GB).

In fact, I can't even write the original PlanarImage. Maybe it comes from the PlanarImage.

pentath
Offline
Joined: 2009-08-04

Steps 2 and 3 could be the same, *if* you can write to an image at sequence N+1 in a tiff file while reading image N from the same tiff file. But, on to more interesting business.

First, the 4 GB limit may just be a TIFF file format limitation. I hear the imageio-ext project has a promising bigtiff imageio driver that may scale to, well, a very great deal more. So check that out if you need a single file more than 4 GB as a result.

But first, when you say scale=4 let the program run to 4 GB before failing, do you mean the arguments to the 'scale' operator were 4f instead of .25f? Basically, can you be more specific about the edits you made?

pierre36
Offline
Joined: 2010-04-28

yes it was 4f instead of 0.25f.
I'm running it again to see whether it continues over 4 Go.

For the 4GB limitation, I think it should not be an issue. Each level of the pyramid is jpeg compressed, so the final pyramid TIFF file should not be over 2 or 3 GB.
But it may explain why I can't write the first mosaic, because it's around 6GB uncompressed.

I read that libtiff (a tiff library for c language) could switch the size field of the header depending on the final size.

edit :
With scale=4f, the file is currently 8.3GB big and still writing. I will not have enough space disk.
I stopped it at 11GB.

pentath
Offline
Joined: 2009-08-04

What you say about libtiff is just the difference between bigtiff and tiff -- they're both tiff files, but bigtiff uses 64 bit size fields instead of 32 bit. You can look around for an all-Java bigtiff driver, or you could get at the libtiff driver you mentioned through imageio-ext-tiff + GDAL (but this is pretty hairy.)

So, I guess the mystery is why you cannot write the scale=0.5 mosaic. Could the mosaic you are building not actually be tiled, or have a tile size and offset so discordant with the output tile size and offset that a single output tile is dependent on a huge number of input tiles?

pierre36
Offline
Joined: 2010-04-28

It may comes from the tiles indeed, I was writing the small TIFF Files in tiles of 1280*1024 (1 file = 1 tile), I put it back to 512*512.

It seems better : I tried scale=1f, it write a tiff around 7GB, but crash probably because of the tiff limitation.
I'm currently running a scale=0.25 and up to now, it's still running. In fact, it's finished. It was rather slow (around 15 minutes, but I dont care right now), but it seems to have succeeded.

Ok I can read the file (even if there's some problems problems coming from the frames juxtaposition but it has nothing to do with programming). So I guess it's OK.
At 0.25f, the file is only 480MB big.

The 2 others lower resolution files were written as well (a few seconds - very quick).

The last step step is to read from these files and write to the TIFF pyramid. It'll be for tomorrow.

pentath
Offline
Joined: 2009-08-04

Congratulations. That doesn't seem like such a bad running time, when you realize it is reordering and encoding mostly with disk space. If you can, try using 64-bit Java with heap and tile cache sizes set to the amount of RAM you have on the system... that will probably speed it up pretty nicely.

pierre36
Offline
Joined: 2010-04-28

Yeah, I think so.
I just need to convince my boss (public researchs lack funding). Not the easier part ;)
(or I could borrow the one next to me)

If everything goes well, i hope i'll have my pyramid before this evening. Then, it will be just refactoring (my code is so bad right now) and tuning.
I will post some code and results once it's done.

Edit :
I got the pyramid ! Thank you very much pentath.

pierre36
Offline
Joined: 2010-04-28

It works !!! (writing of a big TIFF)

The issue was solved when converting each file to a tiled TIFF Image. Now i'm working on building the pyramid TIFF.

Do I have to put the same tile size in the small TIFF Files and in the Mosaic Operator ? if No, I guess it means I can use untiled tiff for the first part (image size : 1280*1024, not that big) ? I guess there will be an heavy retiling step then.

Still I dont know why my BMP Images didn't work.

I will probably have some more questions.
Once it's done, I will post some code.

Edit :
I have some problems with the downsampling operation.
I don't know which operator to use(scale, subsambpleaverage, ...). I have some OutOfMemory errors again when downscaling.

Edit2 :
erf, i'm stupid. I created a new project and forgot to increase the JVM Memory...

pierre36
Offline
Joined: 2010-04-28

Thank you for this answer.

I will try again on monday. I feel it's getting closer.

I think the input files are ok since there are BMP without compression. Still, I will keep in mind the TIFF preprocessing. I saw this in another example.

I will also check whether the cache was enabled or not.

About java version, everything is fine.

pierre36
Offline
Joined: 2010-04-28

Here is the source code.

http://fex.insa-lyon.fr/get?k=DQ9dOHCyXE2XzYvj9N9

I tried to make it clean.

I tested every function. Everything works if the final size is not more than 600MB and everything is buggy otherwise :(

There is 2 different ways to create the Mosaic and 4 write methods.

If you have any clues on this problem, please let me know. I will keep triing other solutions.

Thank you very much.

pentath
Offline
Joined: 2009-08-04

Don't use the 'stream' operator to read the bmp input files. Use "ImageRead"; it does a much better job of fetching just what is needed. This is a probable cause of your memory issues.

Another possible cause of memory issues is the input images. If they are compressed, then reading even one pixel often requires reading the whole file. And then it may be kept in memory in its entirety. I generally find converting my input images to tiled tiffs to be a good way to prevent OutOfMemory issues -- and I would never dream of using compressed images, unless each one is already essentially the size of one tile. And if they *are* the size of one tile, I will try very hard to make all of the JAI operators use the same tile size, so that every time ImageWrite requests a tile, it results in only one of your input images being read.

When you set up your JAI tile cache, also ensure ImageIO caching is enabled. See the javadocs for ImageIO.

The Mosaic operator can be finicky if you provide an ImageLayout without a SampleModel and ColorModel. It will usually work, but it can do weird things sometimes, so it is safer to just set the SampleModel and ColorModel from the first tile:

PlanarImage firstTile = JAI.create("ImageRead", (Object)files[0].getAbsolutePath());
layout.setColorModel(firstTile.getColorModel());
layout.setSampleModel(firstTile.getSampleModel().createCompatibleSampleModel(tileWidth,tileHeight));

Note that the SampleModel was read from the image, and then resized to a tile. This can be done because the Mosaic operator, and probably any ImageOp subclass, applies the SampleModel on a per-tile basis.

If you set up the image layout on the mosaic operator properly, you won't need to mess with the image layout of the ImageWrite operation; indeed, any decisions you make there are pointless, because if you have a bad layout at the mosaic stage, you can't fix it at the write stage, and if you have a good layout at the mosaic stage and let the writer make its own choices, it will usually do the right thing. You do need to set up the tiling to use on the TIFFImageWriteParam, so keep that code as it is.

If you still have problems, make sure you have a recent Java version, and the latest JAI and JAI-ImageIO releases. The daily builds are often better for solving problems like this, since the current "stable" builds have numerous ssues that are fixed in the dailies. Also, it is often useful to forcefully disable medialib acceleration, since the medialib implementations have a weird set of limitations, the mosaic operator especially, and medialib is not generally much faster than pure Java these days.

For whatever it is worth, I routinely use these tools to build multi-resolution image pyramids at the 10-100 gigabyte level, coming from upwards of 50 image fragments mosaicked together. I've also done subsetting and display tools for things like Deep Zoom tilesets, where there are many thousands of files, each one a single tile. There are potential pits to fall into, but as long as you're not doing something wrong this set of tools is immensely powerful.

pierre36
Offline
Joined: 2010-04-28

Ok thank you, I will work on a clean source file to give you.

I tried to save the tiles by making two loops and within :

Raster r=pmosaic.getTile(x,y);
BufferedImage bi=new BufferedImage(r.getSampleModel(),r.getWidth(),r.getHeight);
bi.setData(r).
ImageIO.write(bi, "BMP", new File(x+"_"+y+".bmp");

But I could see only the first Tile and then the others were black and there was an outofmemory exception very soon (42th tiled image of 512*512).

Edit : source file tomorrow.

pierre36
Offline
Joined: 2010-04-28

Ok, I tried that, and it didn't work.

Whatever mean I use to write the TIFF (tiffimagewriter, JAI encoder, ImageReadDescriptor, ImageIO.write), I always have the same problem around 615 MB.

So It may comes from the planarImage, but I display the sizes of the full picture ands tiles, and number of tiles. Everything seems OK. Tomorrow I will try to write every tile of the PlanarImage into small files too see if the PlanarImage is correct.

If yes, I'm still stuck.

pentath
Offline
Joined: 2009-08-04

Let me know. If you post a link to a source file, I can try it here on some big files I have laying around.

pierre36
Offline
Joined: 2010-04-28

Here is the code I used (I worked with strips of images already constructed as BMP, so I only need to translate under the Y-axis, I got some problems with both axis) :

try{

int k=0;
int i=0;
String workDirectory="C:\\Documents and Settings\\user\\Desktop\\test7\\";
String slideName="slide";
Samples s=new Samples(workDirectory+slideName+".txt");

BMPBuilder_test2 bb=new BMPBuilder_test2();
bb.setInfos(s.getInfos(),workDirectory);
File f;

RenderedOp original = null;

TileCache cache = JAI.getDefaultInstance().getTileCache();
cache.setMemoryCapacity(200*1024*1024L);

ParameterBlock pbMosaic = new ParameterBlock();
pbMosaic.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY);

FileSeekableStream stream = null;

ParameterBlock pbTranslate = null;
RenderedOp translated = null;

//initial Image
f=new File(workDirectory+"slide"+i+".bmp");
stream = new FileSeekableStream(f);
original = JAI.create("stream", stream);
pbMosaic.addSource(original);

//Others images
for(i=0;i

f=new File(workDirectory+"slide"+i+".bmp");
k++;
stream = new FileSeekableStream(f);
original = JAI.create("stream", stream);

pbTranslate = new ParameterBlock();
pbTranslate.addSource(original);
pbTranslate.add(0f);
pbTranslate.add(k*1024f);
pbTranslate.add(new InterpolationNearest());

translated = JAI.create("translate", pbTranslate, null);
pbMosaic.addSource(translated);
}

ImageLayout il=new ImageLayout(0,0, bb.getColumns()*1280 , bb.getRows()*1024);

il.setTileHeight(512);
il.setTileWidth(512);

//il.setColorModel(original.getColorModel());
//il.setSampleModel(original.getSampleModel());

PlanarImage pmosaic = JAI.create("Mosaic",pbMosaic,new RenderingHints(JAI.KEY_IMAGE_LAYOUT,il));

TIFFEncodeParam parms = new TIFFEncodeParam();
parms.setWriteTiled(true);
parms.setTileSize(512, 512);
OutputStream out=new FileOutputStream(new File(workDirectory+"toto8.tif"));
TIFFImageEncoder encoder=new TIFFImageEncoder(out, parms);
encoder.encode(pmosaic);
out.close();

if (JAI.getDefaultInstance().getTileCache() != null)
JAI.getDefaultInstance().getTileCache().flush();

}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

It works fine for "medium file", but as soon as the result file is bigger than ~500-600 MB :

java.lang.IndexOutOfBoundsException
at java.io.BufferedInputStream.read(BufferedInputStream.java:310)
at com.sun.media.jai.codecimpl.BMPImage.read24Bit(BMPImageDecoder.java:717)
at com.sun.media.jai.codecimpl.BMPImage.computeTile(BMPImageDecoder.java:1228)
at com.sun.media.jai.codecimpl.BMPImage.getTile(BMPImageDecoder.java:1300)
at javax.media.jai.RenderedImageAdapter.getTile(RenderedImageAdapter.java:148)
at javax.media.jai.NullOpImage.computeTile(NullOpImage.java:162)
at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:914)
at javax.media.jai.OpImage.getTile(OpImage.java:1129)
at com.sun.media.jai.opimage.TranslateIntOpImage.getTile(TranslateIntOpImage.java:132)
at javax.media.jai.PlanarImage.getData(PlanarImage.java:2085)
at javax.media.jai.PlanarImage.getExtendedData(PlanarImage.java:2440)
at com.sun.media.jai.opimage.MosaicOpImage.computeTile(MosaicOpImage.java:432)
at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
at javax.media.jai.OpImage.getTile(OpImage.java:1129)
at javax.media.jai.PlanarImage.getData(PlanarImage.java:2085)
at javax.media.jai.RenderedOp.getData(RenderedOp.java:2276)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeTile(TIFFImageWriter.java:1904)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.write(TIFFImageWriter.java:2686)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.insert(TIFFImageWriter.java:2903)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeInsert(TIFFImageWriter.java:2862)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeToSequence(TIFFImageWriter.java:2754)
at Main3.main(Main3.java:191)

this is the "encoder" line.

Any ideas ? It's making me crazy.

pierre36
Offline
Joined: 2010-04-28

(following the post above)

//il.setColorModel(original.getColorModel());
//il.setSampleModel(original.getSampleModel());

PlanarImage pmosaic = JAI.create("Mosaic",pbMosaic,new RenderingHints(JAI.KEY_IMAGE_LAYOUT,il));

TIFFEncodeParam parms = new TIFFEncodeParam();
parms.setWriteTiled(true);
parms.setTileSize(512, 512);
OutputStream out=new FileOutputStream(new File(workDirectory+"toto8.tif"));
TIFFImageEncoder encoder=new TIFFImageEncoder(out, parms);
encoder.encode(pmosaic);
out.close();

if (JAI.getDefaultInstance().getTileCache() != null)
JAI.getDefaultInstance().getTileCache().flush();

System.out.println("FINISH!");
}
catch (Exception e)
{

e.printStackTrace();
}

Everything works fine for "medium" images, but as soon as the final size is over 500-600 MB :

java.lang.IndexOutOfBoundsException
at java.io.BufferedInputStream.read(BufferedInputStream.java:310)
at com.sun.media.jai.codecimpl.BMPImage.read24Bit(BMPImageDecoder.java:717)
at com.sun.media.jai.codecimpl.BMPImage.computeTile(BMPImageDecoder.java:1228)
at com.sun.media.jai.codecimpl.BMPImage.getTile(BMPImageDecoder.java:1300)
at javax.media.jai.RenderedImageAdapter.getTile(RenderedImageAdapter.java:148)
at javax.media.jai.NullOpImage.computeTile(NullOpImage.java:162)
at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:914)
at javax.media.jai.OpImage.getTile(OpImage.java:1129)
at com.sun.media.jai.opimage.TranslateIntOpImage.getTile(TranslateIntOpImage.java:132)
at javax.media.jai.PlanarImage.getData(PlanarImage.java:2085)
at javax.media.jai.PlanarImage.getExtendedData(PlanarImage.java:2440)
at com.sun.media.jai.opimage.MosaicOpImage.computeTile(MosaicOpImage.java:432)
at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
at javax.media.jai.OpImage.getTile(OpImage.java:1129)
at javax.media.jai.PlanarImage.getData(PlanarImage.java:2085)
at javax.media.jai.RenderedOp.getData(RenderedOp.java:2276)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeTile(TIFFImageWriter.java:1904)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.write(TIFFImageWriter.java:2686)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.insert(TIFFImageWriter.java:2903)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeInsert(TIFFImageWriter.java:2862)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriter.writeToSequence(TIFFImageWriter.java:2754)
at Main3.main(Main3.java:191)

this line is the "encoder" line.

any ideas ? it's making me crazy.

I guess the PlanarImage is correct. So I need to find a way to write it. I tried to loop with pmosaic.getTile(x,y) but it returns a Raster and I am unable to use it.

pentath
Offline
Joined: 2009-08-04

I suspect your use of FileOutputStream is the cause of your troubles. This seems like what you have to do, because of the encode() method, but FileOutputStream is not random access. The output image will have to be written in byte order, which is often *very* bad for performance and requires holding large parts of the output file in memory at certain parts of the encoding operation.

Instead of JAI's encoder, try the ImageWrite operator [1], which is part of jai-imageio. The first argument may be a RandomAccessFile, which the encoder can use to much greater advantage than a FileOutputStream. You would also need to set up a custom TIFFImageWriteParam [2] to specify the tile settings you want, and set it as the second to last parameter of the ImageWrite operator.

[1] http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/jai...
[2] http://download.java.net/media/jai-imageio/javadoc/1.2-latest/com/sun/me...

pierre36
Offline
Joined: 2010-04-28

Ok I feel it's going to work

I tried to display the PlanarImage height, width and TileHeight, TileWidth and I got :
width-height = 1280-1024
TileWidth-TileHeight=512-512

So first, I need to fix these.

Edit 1:

I add :
ImageLayout il=ImageLayout(0,0, 32*1024, 33*1280);
il.setTileWidth(1280);
il.setHeight(1024);
mosaic = JAI.create("mosaic", pbMosaic, new RenderingHints(JAI.KEY_IMAGE_LAYOUT, il));

Sizes are OK

Edit 4 :
Still memory problems.
Maybe I need to clean the TileCache or something like that ?

Edit 5 :
I guess there is a problem with the "translate" operator. The limit size comes from the two flat values. If I put the maximum size of the resulting Image, I will write me the whole file though it's not readable. I dont know what these values are. I thought there were offset in pixels. But I guess I'm wrong because with the correct offsets, there is no writing.
Ok I got these right.

Edit 6 :
http://72.5.124.102/thread.jspa?threadID=682764&messageID=3977857
Triing to combine both.

Edit 7 :
lots of edit :p

I think I got it, I got something working if the size of the resulting file is under 500Mb. If more, I got an IndexOutOfBounds during the encoder.encode(planarImage). If it can help, the first Exception is at java.io.bufferedInputStream.read(BufferedInputStream.java:310).

Still working on it.
I used the code of the link just above to get the planarImage. But I think there's still problems with tiles and memory.

I will post my new code during the week-end.

Edit 8 :
I think that I can increase this "memory limit" by dicreasing the tiles width and height of the PlanarImage.
...or maybe not in fact.

I still do not understand why he would call the BufferedInputStream.read functions and its crash (The inputstreams are for inputs, or the crash is in the encode step).

pierre36
Offline
Joined: 2010-04-28

So, is it even possible to do what I want to do ?

I saw a very interesting post there : http://forums.sun.com/thread.jspa?threadID=5372351
where it seems able to process big files by tiles with very small memory load.

But when I try the code, I get "One factory fails for the operation "encode"" and I was unable to go further.

Sorry to ask for help, but this is a big project on cancer diagnosis and I'm kind of stuck.

Thank you.

pentath
Offline
Joined: 2009-08-04

It should be, but you have to both set the mosaic's ImageLayout hint, as Bob suggested, and set the TIFF image writer with the parameters to write a tiled tiff. If you are doing both, I would try a smaller scale test to verify that it's actually doing what you want it to. Also, depending on your TIFF writer, you may have to set the tile size separately from requesting tiling, like so:

TIFFEncodeParam parms = new TIFFEncodeParam();
parms.setWriteTiled(true);
parms.setTileSize(tx, ty);
OutputStream ostream = new BufferedOutputStream(...);
TIFFImageEncoder encoder = new TIFFImageEncoder(ostream, parms);
encoder.encode(planarImage);

Best of luck.

pierre36
Offline
Joined: 2010-04-28

Ok, i thought these two values may be offsets rather than width/length.

I tried with 1280/1024. Same result.

pierre36
Offline
Joined: 2010-04-28

what are x and y float ?

However, I tried it, and still java.lang.OutOfMemoryError: Java heap space after 17 minutes :

public static void main(String[] args) throws Exception{

int i;
TileCache tc=JAI.createTileCache();
tc.setMemoryCapacity(1024L*1024L*500);
String workDirectory="C:\\Documents and Settings\\user\\Desktop\\test\\";
String slideName="slide";

Samples s=new Samples(workDirectory+slideName+".txt");

BMPBuilder_test2 bb=new BMPBuilder_test2();
bb.setInfos(s.getInfos(),workDirectory);
File[] f=new File[bb.getFiles().length];
Tile[] t=new Tile[bb.getFiles().length];

for(i=0;i f[i]=new File(workDirectory+(bb.getFiles()[i]));
t[i]=new Tile();
t[i].f=f[i];
t[i].x=i;
t[i].y=0;
}

List l=Arrays.asList(t);
PlanarImage test=Tile.getMosaic(l);
ImageIO.write(test, "TIFF", new FileOutputStream(new File(workDirectory+"toto.tif")));
}

public class Tile {
float x;
float y;
File f;

public static PlanarImage getMosaic(List tiles) {

ParameterBlock pbMosaic=new ParameterBlock();
pbMosaic.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY);

RenderedOp mosaic = JAI.create("Mosaic",pbMosaic,null);

for (Tile t: tiles) {

RenderedOp image = JAI.create("ImageRead", t.f);

RenderedOp nudged = JAI.create("Translate",(RenderedImage)image, t.x, (Object)t.y);

mosaic.addSource((Object)nudged);
}
return mosaic;
}

}

imagero
Offline
Joined: 2003-11-18

The loop is a bit screwed, however in email it was correct:
for(i=0;i f[i]=new File(workDirectory+(bb.getFiles()[i]));
t[i]=new Tile();
t[i].f=f[i];
t[i].x=i;
t[i].y=0;
}

I hope it is clear that all your tiles have t.y = 0;
So that your resulting image is pretty wide...

pierre36
Offline
Joined: 2010-04-28

Ok, I still got OutOfMemoryException with your code.

However, i may have done a mistake while describing my problem. In fact, I don't really need to process from a huge BMP because this huge BMP is created by myself from 1000 images (res : 1024*1280 each).

So maybe it will be easier to try to open, write (in the tiled tiff) and close each of these small files.

I guess there should be a loop for each layer/level. Something like

for(i=0;i

}

What code should I use inside ? open every images as a buffered Stream and make a TIFFImageWriter.write(bufferedImage) ? how about the metadata and fields ? Do i just need to write it for the first tiledImage of each level ? I'm afraid that the tile offset would be wrong this way. Maybe I can add them as a fied myself.

I'm going to try something like that :

for(i=0;i BufferedImage bi=new BufferedImage(new File(Image[i]));
if (i==0) writer.writeToSequence(IIOMetadata);
else writer.write(bi);
}

and look at the fields.

Maybe the TiledImageClass would be more appropriate but I think there will still be an OutOfMemoryError since it seems to contain all the data before writing.

But it don't feel like the right way to do it.

Bob Deen

RenderedImage is just an interface, which can be implemented in any way
you want. Sounds like you should have something implementing
RenderedImage that presents the huge image to the caller with tiles of
size 1024x1280. Whenever a tile is requested, your class reads the
appropriate image, massages the metadata appropriately (probably
wrapping a new Raster around the underlying data with an appropriate
offset), and returns that.

Thus to the caller it looks like a huge image, but each tile image is
read only when needed.

You'll want to use a tile cache to avoid re-reading tiles, to the limits
of memory you have available. To that end, you might write the whole
thing as a custom sourceless JAI operator.

I know there's been talk of such things in the past (images backed by
tiles where each tile is its own image file) but I don't recall if any
of them ever got implemented or not...

-Bob

jai-interest@javadesktop.org wrote:
> Ok, I still got OutOfMemoryException with your code.
>
> However, i may have done a mistake while describing my problem. In fact, I don't really need to process from a huge BMP because this huge BMP is created by me from 1000 images (res : 1024*1280 each).
>
> So maybe it will be easier to try to open, write (in the tiled tiff) and close each of these small files.
>
> I guess there should be a loop for each layer/level. Something like
>
> for(i=0;i >
> }
>
> What code should I use inside ? open every images as a buffered Stream and make a TIFFImageWriter.write(bufferedImage) ? how about the metadata and fields ? Do i just need to write it for the first tiledImage of each level ? I'm afraid that the tile offset would be wrong this way. Maybe I can add them as a fied myself.
>
> I'm going to try something like that :
>
> for(i=0;i > BufferedImage bi=new BufferedImage(new File(Image[i]));
> if (i==0) writer.writeToSequence(IIOMetadata);
> else writer.write(bi);
> }
>
> and look at the fields.
>
> Maybe the TiledImageClass would be more appropriate but I think there will still be an OutOfMemoryError since it seems to contain all the data before writing.
> [Message sent by forum member 'pierre36']
>
> http://forums.java.net/jive/thread.jspa?messageID=400115
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
> For additional commands, e-mail: interest-help@jai.dev.java.net
>

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net

pentath
Offline
Joined: 2009-08-04

If these input tiles are to be seen like the squares on a checkerboard, then the JAI 'Mosaic' operator will do what Bob suggests. Combined with the ImageRead and Translate operators, you can construct a PlanarImage that requires very little memory and will be available very quickly since it does almost nothing until you need actual tiles.

Given a list of objects that contain the file and offsets for your mosaic:

class Tile {
float x;
float y;
File f;
}

PlanarImage getMosaic(List tiles) {
RenderedOp mosaic = JAI.create("Mosaic");
for (Tile t: tiles) {
RenderedOp image = JAI.create("ImageRead", t.f);
RenderedOp nudged = JAI.create("Translate",
(RenderedImage)image, t.x, t.y);
mosaic.addSource(nudged);
}
return mosaic;
}

So you get out a PlanarImage that takes up very little memory and does no real work until you ask it to. If you pass this to ImageIO.write(), it will compute tiles as it needs to, but when you run out of memory, it will drop old tiles and just create new ones on demand. If your Java program is *only* doing this one thing, be sure to call JAI.getTileCache().setMemoryCapacity() to something like 70% of the Java heap size, so you don't needlessly discard tiles you might need again.

pierre36
Offline
Joined: 2010-04-28

ok, this looks good, thank you.

I will try this monday and give you some feedback then.

pentath
Offline
Joined: 2009-08-04

Well, there are a couple of problems to solve if you want it to scale, and scale, and scale, and...

One is reading the data in. The ImageRead operator in the aforementioned jai-imageio project can read many input image formats on demand, but JAI's love of tiling clashes badly with a BMP file, which is striped. So, an input image that supports tiling will work better than one that doesn't. Much.

The other is lazily computing the additional levels of the pyramid, since even several steps later could be too large to fit in memory. Fortunately you can do this with the Scale operator, a stock JAI offering. By creating each downstampled level as a Scale of the prior level, you can essentially materialize everything out of thin air.

The following implementation is an example. It can be made faster I suspect (certainly by increasing Java heap size, and brining the JAI cache size to within a 100 MB of the heap size), but I didn't want to distract from the above notes too much. Also, I'm using Image, a helper of my own, but for these purposes, just pretend it's doing JAI.create("op",arg1,arg2).

public class PyramidTiffCompressor {
private static final int tx = 512;
private static final int ty = 512;
private static final double scale = 0.5;
public static void main(String[] args) throws IOException {
if (args.length != 2 || (args.length == 1 && Arrays.asList("-h","-help").contains(args[0].toLowerCase()))) {
System.out.println("ptif_compress ");
System.exit(0);
}
File inputFile = new File(args[0]);
if (!inputFile.exists()) {
System.err.println("Input file does not exist, aborting");
System.exit(-1);
}
File outputFile = new File(args[1]);
if (outputFile.exists()) {
System.err.println("Output file exists, aborting");
System.exit(-2);
}
PlanarImage image = new Image("ImageRead", inputFile.getAbsolutePath()).getOp();
OutputStream ostream = new BufferedOutputStream(new FileOutputStream(outputFile));
TIFFEncodeParam parms = new TIFFEncodeParam();
parms.setWriteTiled(true);
parms.setTileSize(tx, ty);
parms.setCompression(TIFFEncodeParam.COMPRESSION_DEFLATE);
List overviews = new ArrayList();
PlanarImage overview = image;
while (overview.getWidth() > tx*scale || overview.getHeight() > ty*scale) {
overview = new Image(overview).to("Scale", scale, scale).getOp();
overviews.add(overview);
}
if (!overviews.isEmpty()) {
parms.setExtraImages(overviews.iterator());
}
TIFFImageEncoder encoder = new TIFFImageEncoder(ostream, parms);
encoder.encode(image);
ostream.flush();
ostream.close();
}
}

pierre36
Offline
Joined: 2010-04-28

I already set 1,5Gb to the JVM, which is the max. And the goal is to be able to process images even bigger than 4Gb. So I need to be able not to load the whole Image/File in memory, but only load parts/tiles of it and do the same operations as before (writing in tiff file + metadata +downsample).

For the path, it's OK.

imagero
Offline
Joined: 2003-11-18

BTW, do you have jai-imageio.jar in your classpath?
It is required for "ImageRead".

imagero
Offline
Joined: 2003-11-18

> But I still have problems with
> IndexOutOfBoundsException. So this may not be correct
> and it doesn't not work

Stacktrace would be helpful...

Tara Gilliam

Hi,

The first thing to try could be increasing the amount of memory the Java VM can
use (with your original code).

From the command line this would be:
java -Xmx4G

to give it up to 4Gb of memory -- assuming your machine has this!

If you're using eclipse, it would be something like:
Run > Run configurations > Arguments
and enter -Xmx4G in the VM arguments box.

jai-interest@javadesktop.org wrote:
> Hi,
> I'm having some troubles with JAI : I would like to read a huge BMP file (~4Gb) and write it in a pyramid tiff. I used to process the BMP file with a BufferedImage, but it doesn't work any more with big files (memory problem) :
>
> public void pyramidGenerator(TIFFImageWriter writer,
> BufferedImage aInputImage, int tileWidth,
> int tileHeight, int resolutionSteps)
> throws IOException {
>
> ImageLayout il = new ImageLayout();
> il.setTileWidth(tileWidth).setTileHeight(tileHeight);
> il.setTileGridXOffset(0).setTileGridYOffset(0);
>
> TIFFEncodeParam tep = new TIFFEncodeParam();
> tep.setWriteTiled(true);
> tep.setTileSize(tileWidth,tileHeight);
> tep.setJPEGCompressRGBToYCbCr(false); //does not work
> tep.setLittleEndian(true);
>
> RenderingHints tileHints =new RenderingHints(JAI.KEY_IMAGE_LAYOUT, il);
>
> TIFFImageWriteParam param = (TIFFImageWriteParam) writer.getDefaultWriteParam();
> param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
>
> //compression type
> param.setCompressionType("JPEG");
>
> //tiling mode
> param.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
> param.setTiling(tileWidth, tileHeight, 0, 0);
>
> //add first layer
> RenderedImage renderedImg = aInputImage;
> IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(renderedImg), param);
>
> writer.prepareWriteSequence(null);
>
> TIFFDirectory ifd=TIFFDirectory.createFromMetadata(metadata);
> TIFFTag tag=new TIFFTag("NewSubfileType", 254, TIFFTag.TIFF_LONG);
> TIFFField sub=new TIFFField(tag,TIFFTag.TIFF_LONG , 1, new long[]{0});
> ifd.addTIFFField(sub);
>
> IIOImage iioImage = new IIOImage(renderedImg, null, metadata);
> writer.writeToSequence(iioImage, param);
> System.out.println("*** Layer 1 Done ***");
>
> ...
>
>
> So I guess I should read my BMP File by tiles/parts/pieces, so i don't have to have the whole image in memory (no more use of BufferedImage). I looked for solutions like :
>
>
>
> public static RenderedImage readTiled(File f, int tileWidth, int
> tileHeight) throws Exception{
>
> ImageInputStream iis = ImageIO.createImageInputStream(f);
> ParameterBlockJAI pbj = new ParameterBlockJAI("ImageRead");
>
> ImageLayout layout = new ImageLayout();
> layout.setTileWidth(tileWidth);
> layout.setTileHeight(tileHeight);
>
> RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout);
>
> pbj.setParameter("Input", iis);
> return JAI.create("ImageRead", pbj, hints);
> }
>
>
> But I still have problems with IndexOutOfBoundsException. So this may not be correct and it doesn't not work
>
> Do you have any ideas ? I guess I should learn more about ImageLayout and RenderingHints, but i'm not very familiar with those.
>
> Thank you.
> [Message sent by forum member 'pierre36']
>
> http://forums.java.net/jive/thread.jspa?messageID=399532
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
> For additional commands, e-mail: interest-help@jai.dev.java.net
>

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net