Skip to main content

Save tile form TIFF image

35 replies [Last post]
darwinjob
Offline
Joined: 2004-11-16
Points: 0

Hi
I have 19843 x 12756 uncompressed untiled TIFF image. When I try to save a 256 x 256 region from that TIFF I get OutOfMemory. If I set -Xmx2048M it obviously works. Is JAI trying to load whole image into the memory just to get that 256 x 256 tile???
I do:
RenderedImage image = JAI.create("fileload", inputFile.toString());
RenderedImage tile = JAI.create("crop", params, null);
JAI.create("filestore", tile, outputFile.toString(), "png");

If I proccess tiled TIFF then everything is fine.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
mstandio
Offline
Joined: 2010-12-06
Points: 0

Hi darwinjob, have you found working solution for this?
I am trying to perform similar stuff, by what I mean processing really big images by reading them fragment by fragment, but more in line-by line way. So far I got to reading uncompressed *.tif images with RectIter, where tif images are apparently saved as series of stripes (couse *.bmp are loaded entirely into memory when creating RectIter). I don't want to rely on tifs being saved in this way, and I am looking for working solution that would allow reading fragments of image without out of memory errors.
It would be great if you could post working snippet for this, I dug through both forums and all examples and got nothing.

darwinjob
Offline
Joined: 2004-11-16
Points: 0

If I'll make it work - I swear I publish the converter somewhere :)
http://code.google.com/p/deepjzoom/updates/list
https://github.com/darwinjob/openzoom.java

mstandio
Offline
Joined: 2010-12-06
Points: 0

It would be realy great :)
It happens that I am working with your class as part of project published at http://panozona.com/ . I need reading images fragment by fragment also for converting equirectangular panos to cubes. Perhaps JAI-imageio project will be more helpfull, but I can't tell yet.

rgd
Offline
Joined: 2005-08-23
Points: 0

"fileload" is the old codec mechanism. It will read tiles only to the
extent they exist in the file. So yes, if the image is untiled it will
read the entire image in order to do anything.

The Image I/O Tools package has the "imageread" operator, which uses the
IIO plugin mechanism. That's recommended for all new code. It's
*possible* that you might be able to get the TIFF plugin to read just a
section (i.e. create virtual tiling) but I don't know for sure. The
mechanism supports it, I just don't know how the TIFF plugin was written.

Hope that helps...

-Bob

On 11/30/10 8:56 AM, forums@java.net wrote:
> Hi
>
> I have 19843 x 12756 uncompressed untiled TIFF image. When I try to save a
> 256 x 256 region from that TIFF I get OutOfMemory. If I set -Xmx2048M it
> obviously works. Is JAI trying to load whole image into the memory just to
> get that 256 x 256 tile???
>
> I do:
>
> RenderedImage image = JAI.create("fileload", inputFile.toString());
>
> RenderedImage tile = JAI.create("crop", params, null);
>
> JAI.create("filestore", tile, outputFile.toString(), "png");
>
>
>
> If I proccess tiled TIFF then everything is fine.
>
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Thank you, Bob, it does help indeed. Is there something similar for JPEG?
Where can I find the updated list of the operators?

Thank you!

rgd
Offline
Joined: 2005-08-23
Points: 0

There's a jpeg plugin just like there's a tiff plugin. However, whether
any given plugin implements this particular capability, I don't know...
you'll have to either pore through documentation or (perhaps easier)
experiment.

Updated list? There are only two operators added by the imageio-tools
package, imageread and imagewrite. Of course there are a lot more
operators contributed by the JAI community that are pointed to by links
off the JAI page, but they're not part of JAI per se. For the JAI ops
the most reliable place to look is the javadocs, look for XxxDescriptor
classes in the operator package.

-Bob

On 11/30/10 9:52 AM, forums@java.net wrote:
> Thank you, Bob, it does help indeed. Is there something similar for JPEG?
>
> Where can I find the updated list of the operators?
>
>
>
> Thank you!
>
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

OK, Now I have:
RenderedImage image = JAI.create("imageread", inputFile);
RenderedImage tile = JAI.create("crop", params, null);
JAI.create("imagewrite", tile, outputFile, tileFormat);
Now in the last line I get OutOfMemory EVEN WITH -Xmx2048M!!! Seems like the old codec was more efficient.
Bob, I have noticed the "possible" word in your reply, but isn't it like I'm asking for very basic functionality which should be available right out of the box?

imagero
Offline
Joined: 2003-11-18
Points: 0
rgd
Offline
Joined: 2005-08-23
Points: 0

For this to have any chance of working, you have to tell the imageread
operator itself to turn on tiling. That would be one of the image read
params. It's the sort of thing I know is there but haven't used in a
long time so you'll have to look up the details.

As for "possible", I simply don't use the tiff or jpeg plugins enough to
know what their specific capabilities are. I use our own (vicar/pds
format) which does support tiling-during-read so I've not had to look it
up. It just depends on how the plugin author wrote the code; tiling
during read is not a required capability. It's also hard to do for many
compressed formats since you have to decode most of the image to get to
that data anyway, so many authors would say why not just return it, not
thinking about very huge files.

I do agree that it's pretty basic though and "should" be supported...
but that and four quarters will give you a dollar. ;-)

-Bob

On 11/30/10 2:34 PM, forums@java.net wrote:
> OK, Now I have:
>
> RenderedImage image = JAI.create("imageread", inputFile);
>
> RenderedImage tile = JAI.create("crop", params, null);
>
> JAI.create("imagewrite", tile, outputFile, tileFormat);
>
> Now in the last line I get OutOfMemory EVEN WITH -Xmx2048M!!! Seems like
> the
> old codec was more efficient.
>
> Bob, I have noticed the "possible" word in your reply, but isn't it like
> I'm
> asking for very basic functionality which should be available right out of
> the box?
>
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

OMG!!! IT WORKS!!!
You were right Bob, using Andrey's code I have some sort of "virtual" tiling now.
Thank you, Bob; Spasibo, Andrey.
:)

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Well, it was to early to celebrate, it works on 64 bit, on 32 bit it still spits out OutOfMemory. And there are no 64 bits dlls...

imagero
Offline
Joined: 2003-11-18
Points: 0

How big is your tile size? 256x256?

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Yep - 256
I use your code:
image = RenderedImage readTiled(File f, 256, 256)

Could you make an assumption why "crop" and then "imagewrite" operation is fast on 2 and 4 times downscaled image but incredebly slow on 8 (and futher)?

imagero
Offline
Joined: 2003-11-18
Points: 0

Line "image = RenderedImage readTiled(File f, 256, 256)"looks a bit strange.
Probably you wanted to write: RenderedImage image = readTiled(File f, 256, 256);
I have no Idea why you getting OOME on 32 bit java. May be you could post your code for testing?

Regarding scale performance: one possibility is that by scale >= 8 much more source tiles required to compute single destination tile,
so if JAIs TileCache is too small to keep them all, so JAI reloads source tiles many many many times.
So just increase tile cache size and if this does not helps, try to downscale by 2 and then by 4 (or 3 times by 2).

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Oh, yes, just a typo. Code formatting here is a pain.
I'm making a tiled pyramid, DeepZoom format. The code ():

<code>
RenderedImage image = this.readTiled(inputFile, 256, 256);
int originalWidth = image.getWidth();
int originalHeight = image.getHeight();
/* omitted code here
double maxDim = Math.max(originalWidth, originalHeight);
int nLevels = (int) Math.ceil(Math.log(maxDim) / Math.log(2));
/* omitted code here
double width = originalWidth;
double height = originalHeight;
for (int level = nLevels; level >= 0; level--) {
int nCols = (int) Math.ceil(width / tileSize);
int nRows = (int) Math.ceil(height / tileSize);
/* omitted code here
for (int col = 0; col < nCols; col++) {
for (int row = 0; row < nRows; row++) {
RenderedImage tile = this.getTile(image, row, col);
this.saveImage(tile, dir + File.separator + col + '_' + row);
}
}
// Scale down image for next level
width = Math.ceil(width / 2);
height = Math.ceil(height / 2);
image = this.resizeImage(image, width, height);
}
private RenderedImage readTiled(File f, int tileWidth, int tileHeight)
throws IOException {
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);
}
private RenderedImage getTile(RenderedImage img, int row, int col) {
/* omitted code here
ParameterBlock params = new ParameterBlock();
params.addSource(img);
// X:
params.add((float) x);
// Y:
params.add((float) y);
// W:
params.add((float) w);
// H:
params.add((float) h);
RenderedImage tile = JAI.create("crop", params, null);
return tile;
}

private RenderedImage resizeImage(RenderedImage img, double width, double height) {
ParameterBlock pb = new ParameterBlock();
pb.addSource(img);
pb.add((float) width / img.getWidth());
pb.add((float) height / img.getHeight());
pb.add(0.0F);
pb.add(0.0F);
pb.add(new InterpolationNearest());
PlanarImage scaled = JAI.create("scale", pb);
return scaled;
}

private void saveImage(RenderedImage img, String path) throws IOException {
/* omitted code here
JAI.create("imagewrite", img, outputFile, tileFormat);
}
</code>

Or you want the entire code? As far as I can see I'm not doing anything fancy. Could you tell me how and where I should change the TileCache? What I supposed to do with this interface?

imagero
Offline
Joined: 2003-11-18
Points: 0

I think that problem is in getTile().
Don't use JAI#crop.
Use Raster r = image.getTile(x, y);
Then create BufferedImage (from Raster + ColorModel) and save it.

darwinjob
Offline
Joined: 2004-11-16
Points: 0

ok, I found it

TileCache cache =
    JAI.getDefaultInstance().getTileCache();
long size = 32*1024*1024L; // 32 megabytes
cache.setMemoryCapacity(size);
rgd
Offline
Joined: 2005-08-23
Points: 0

Indeed. You can also attach a "private" tile cache to any operator, via
the RenderingHints if I recall correctly, if you want to ensure that op
has enough space without competition for space from any other op.

Cache management is an important topic for dealing with large images;
some analysis is often required to make sure you're doing the right thing.

-Bob

forums@java.net wrote:
> ok, I found it
>
> TileCache cache = JAI.getDefaultInstance().getTileCache(); long size =
> 32*1024*1024L; // 32 megabytes cache.setMemoryCapacity(size);
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Thanks Bob
As far as understand, this is how JAI works: let's say I have two scale and one crop operations: scale, scale, crop and then imagewrite. The real calculations happen only on the imagewrite phase, right? Does this mean that JAI scales the whole image everytime it needs to save the copped tile? Is there any way to "apply" or "burn in" the scaling?

rgd
Offline
Joined: 2005-08-23
Points: 0

In a logical sense, yes. When imagewrite needs a tile, it asks its
upstream operator (crop) for that tile. Crop in turn asks its upstream
operator (scale) for the same tile. Scale then asks its upstream op,
but it needs 2x more data so it might ask for 4 tiles (depending on the
phasing of the tile grid) (also this assumes all tiles are the same
size, which need not be the case, and I'm assuming 1/2 scaling for
simplicity). That scale then asks its parent for 4 tiles for *each*
request it gets, which finally are loaded. Then the data flows back
through each op, imagewrite finally gets it, and writes it to the file.

If caching were completely disabled, the scale-scale step would be
recomputing the same tiles over and over and over again to satisfy each
of the requests. If you were fetching the entire image, the last scale
might request each source tile 4 times. Each of those requests causes
the first scale to ask for 4 tiles; the upshot being that the load might
get requests for the same tile 16 times!

This is why the tile cache is so critical. It short-circuits all of
that, so that a given op does not recompute (and thus does not re-fetch
from its sources) anything that's in the cache. You end up fetching
each tile only once, each first-stage scale gets computed only once, and
each second-stage also only once. It is in fact the mechanism for
"burn-in" that you asked about.

If your images are big enough that not everything can be held in memory,
then that's when you have to think about cache tuning. How much data
has to be held at each stage in order to avoid cache thrashing? That's
what you have to think about.

For most applications this stuff isn't terribly important; the default
cache is sufficient (or maybe just raising the overall cache size is
sufficient). But when you start dealing with very large images and
complex processing chains, you do have to think about it. For example
if you did a contrast stretch (e.g. rescale) after the load, that's a
1-for-1 op so you might set up to cache the results of that and NOT
cache the results of the load directly, since you'll never have a loaded
tile that's not stretched. Conversely if you had two different
stretches hanging off the load, you might disable caching on the
stretches and cache the load instead, on the theory that the stretch is
fast enough that you don't care if it gets re-done and it's not worth 2x
memory to save both stretch results.

Hope that helps...

-Bob

On 12/8/10 3:09 AM, forums@java.net wrote:
> Thanks Bob
>
> As far as understand, this is how JAI works: let's say I have two scale and
> one crop operations: scale, scale, crop and then imagewrite. The real
> calculations happen only on the imagewrite phase, right? Does this mean
> that
> JAI scales the whole image everytime it needs to save the copped tile? Is
> there any way to "apply" or "burn in" the scaling?
>
>

imagero
Offline
Joined: 2003-11-18
Points: 0

Hi darvinjob,
is it possible that you missed my reply, that you don't need crop here?
Andrey

darwinjob
Offline
Joined: 2004-11-16
Points: 0

No, Andrey, I have not. The problem is that I need more then just one tile - I need a tile with overlap. Plus a tile's dimension can vary. Can I get this somehow from Raster?
<code>
private RenderedImage getTile(RenderedImage img, int row, int col) {
int x = col * tileSize - (col == 0 ? 0 : tileOverlap);
int y = row * tileSize - (row == 0 ? 0 : tileOverlap);
int w = tileSize + (col == 0 ? 1 : 2) * tileOverlap;
int h = tileSize + (row == 0 ? 1 : 2) * tileOverlap;

if (x + w > img.getWidth())
w = img.getWidth() - x;
if (y + h > img.getHeight())
h = img.getHeight() - y;

logger.debug("getTile: row=" + row + " col=" + col + " x=" + x + " y="
+ y + " w=" + w + " h=" + h);

ParameterBlock params = new ParameterBlock();
params.addSource(img);
// X:
params.add((float) x);
// Y:
params.add((float) y);
// W:
params.add((float) w);
// H:
params.add((float) h);

RenderedImage tile = JAI.create("crop", params, null);

return tile;
}

</code>

Thank you.

imagero
Offline
Joined: 2003-11-18
Points: 0

Well, in this case you need to put some kind CacheOp (which never releases his Tiles) just after ScaleOp. This should solve performance problems.

rgd
Offline
Joined: 2005-08-23
Points: 0

There's no such thing per se... but if you create a private cache, size
it big enough to hold the entire image, and attach it to the scale op,
you'll achieve the same effect.

-Bob

On 12/8/10 11:09 AM, forums@java.net wrote:
> Well, in this case you need to put some kind CacheOp (which never releases
> his Tiles) just after ScaleOp. This should solve performance problems.
>
>
>
>

imagero
Offline
Joined: 2003-11-18
Points: 0

Well, this should be trivial to implement, but since it is possible to attach private TileCache it is probably better...

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Bob, Andrey,
Thank you very much for the excellent explanations!
So, when I make a pyramid level I only need to keep the previuos level in the cache. That should be enough to calculate the current scaled down level, am I right? If I'm right then the performace should increase with each level because we scale down an image with twice lower dimensions!
Could you please provide a couple of lines of the code or a link of such manual TiledCache managment? Me and mstandio would really appreciate that.

rgd
Offline
Joined: 2005-08-23
Points: 0

Not code, but here's how you do it. Use JAI.createTileCache(long) to
create the tile cache object at whatever size you want. Then stuff that
in a RenderingHints object with the key KEY_TILE_CACHE (defined in the
JAI class). Then supply that RH to JAI.create() when you create the
operator in question. Voila! A private tile cache. Note that you can
give the same cache to multiple operators if you want.

-Bob

On 12/15/10 2:57 AM, forums@java.net wrote:
> Bob, Andrey,
>
> Thank you very much for the excellent explanations!
>
> So, when I make a pyramid level I only need to keep the previuos level
> in the
> cache. That should be enough to calculate the current scaled down level,
> am I
> right? If I'm right then the performace should increase with each level
> because we scale down an image with twice lower dimensions!
>
> Could you please provide a couple of lines of the code or a link of such
> manual TiledCache managment? Me and mstandio would really appreciate that.
>
>

rgd
Offline
Joined: 2005-08-23
Points: 0

You can simply request a given area from your image instead of using the
crop operator, something like getData(Rectangle). But that is likely to
"cobble" your data, i.e. force copying tiles (or pieces thereof) into a
single Raster to return to you. If that's what you want, fine, this is
as efficient as anything else, and it's common to want the data to be in
a single Raster.

If however you want to do other things with the cropped result that
would benefit from retaining the tiled nature of your image, then the
crop operator is the best way to go. Some image writers for example can
write out a tile at a time, or maybe a row of tiles, and never require
the entire image be in memory at once. (others are not so smart, it
just depends on how they were written, and no I don't know which is
which ;-) ).

-Bob

On 12/8/10 4:29 AM, forums@java.net wrote:
> No, Andrey, I have not. The problem is that I need more then just one
> tile -
> I need a tile with overlap. Plus a tile's dimension can vary. Can I get
> this
> somehow from Raster?
>
>

>
> private RenderedImage getTile(RenderedImage img, int row, int col) {
>          int x = col * tileSize - (col == 0 ? 0 : tileOverlap);
>          int y = row * tileSize - (row == 0 ? 0 : tileOverlap);
>          int w = tileSize + (col == 0 ? 1 : 2) * tileOverlap;
>          int h = tileSize + (row == 0 ? 1 : 2) * tileOverlap;
>          if (x + w > img.getWidth())
>              w = img.getWidth() - x;
>          if (y + h > img.getHeight())
>              h = img.getHeight() - y;
>          logger.debug("getTile: row=" + row + " col=" + col + " x=" + x
> + " y="
>                  + y + " w=" + w + " h=" + h);
>          ParameterBlock params = new ParameterBlock();
>          params.addSource(img);
>          // X:
>          params.add((float) x);
>          // Y:
>          params.add((float) y);
>          // W:
>          params.add((float) w);
>          // H:
>          params.add((float) h);
>          RenderedImage tile = JAI.create("crop", params, null);
>          return tile;
>      }
>
>
>
>

>
>
>
> Thank you.
>
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

OK,
Now I do it like this:
<code>
private BufferedImage getTile(RenderedImage img, int row, int col) {
int x = col * tileSize - (col == 0 ? 0 : tileOverlap);
int y = row * tileSize - (row == 0 ? 0 : tileOverlap);
int w = tileSize + (col == 0 ? 1 : 2) * tileOverlap;
int h = tileSize + (row == 0 ? 1 : 2) * tileOverlap;

if (x + w > img.getWidth())
w = img.getWidth() - x;
if (y + h > img.getHeight())
h = img.getHeight() - y;

logger.debug("getTile: row=" + row + " col=" + col + " x=" + x + " y="
+ y + " w=" + w + " h=" + h);

Raster raster = img.getData(new Rectangle(x, y, w, h));
raster = raster.createTranslatedChild(0, 0);
ColorModel colorModel = img.getColorModel();
return new BufferedImage(colorModel, (WritableRaster) raster, colorModel.isAlphaPremultiplied(), null);
}
</code>
And it is MUCH faster now! But it still slows down while I process the higher levels (smaller image) of a pyramid. I guess I really need that manual TileCache managment.
Last levels still take more then hour to process (I have 2G TileCache!).

imagero
Offline
Joined: 2003-11-18
Points: 0

I don't know about TileCache, ask Bob, but to implement caching RenderedImage is trivial.
//I alologize for possible typos.
public class CachingRenderedImage implements RenderedImage {
RenderedImage source;
Raste [][] cache;
public CachingRenderedImage(RenderedImage source) {
this.source = source;
cache = new Raster[source.getNumYTiles()][source.getNumXTiles()];
}
//most important method:
public Raster getTile(int tileX, int tileY) {
int x = tileX - source.getMinTileX();
int y = tileY - source.getMinTileY();
if(cache[y][x] == null) {
cache[y][x] = source.getTile(tileX, tileY);
}
return cache[y][x];
}
//all other methods should just redirect to source.
}

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Damn, another issue:
java.lang.IllegalArgumentException: width*height > Integer.MAX_VALUE!
in:
Raster raster = img.getData(new Rectangle(x, y, w, h));
It happens when I try to convert 51200x108544 TIFF. Obviously 51200x108544 > Integer.MAX_VALUE (5557452800>2147483647?). But it works with "crop"... I guess this one is unsolvable?

rgd
Offline
Joined: 2005-08-23
Points: 0

On 12/15/10 8:15 AM, forums@java.net wrote:
> Damn, another issue:
>
> java.lang.IllegalArgumentException: width*height > Integer.MAX_VALUE!
>
> in:
>
> Raster raster = img.getData(new Rectangle(x, y, w, h));
>
> It happens when I try to convert 51200x108544 TIFF. Obviously
> 51200x108544 >
> Integer.MAX_VALUE (5557452800>2147483647?). But it works with "crop"... I
> guess this one is unsolvable?

Rasters have to be smaller than Integer.MAX_VALUE. But images do not.
For an image, I think the only limitation is that each *dimension* has
to be less than Integer.MAX_VALUE.

This is the value of tiling. Each tile is its own Raster, so Rasters
never get bigger than the tile. I have successfully dealt with > 2GB
images before... it ain't fast, but it works.

This is also the value of using the crop operator rather than getData
directly... crop maintains tiling, while getData does not.

Tradeoffs are everywhere, unfortunately! ;-)

-Bob

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Rasters have to be smaller than Integer.MAX_VALUE.

Well, actually I don't ask for a huge raster, the exception happens when I do img.getData(new Rectangle(0, 0, 257, 257)) , it seems like we can not use getData() at all on a big images... OK, I switch to "crop".

rgd
Offline
Joined: 2005-08-23
Points: 0

Hmmm... that should work, I think. Are those bounds perhaps outside the
range of the image?

Also, the huge raster may not be in your request but in the source for
your request...

-Bob

On 12/16/10 1:35 AM, forums@java.net wrote:
> >Rasters have to be smaller than Integer.MAX_VALUE.
> >
>
>
> Well, actually I don't ask for a huge raster, the exception happens when
> I do
> img.getData(new Rectangle(0, 0, 257, 257)) , it seems like we can not use
> getData() at all on a big images... OK, I switch to "crop".
>
>

darwinjob
Offline
Joined: 2004-11-16
Points: 0

Ohhh....
WIth TIFF 51200x108544 I can not use "imageread" (as a consequence I can not use Andery's readTiled()), it fails in "imagewrite" with width*height > Integer.MAX_VALUE!. If I switch to the old "fileload" it works fine - for tiled images only - bewitched circle.