Question

Python raster API create tiles

  • 15 March 2018
  • 3 replies
  • 4 views

Badge +22
  • Contributor
  • 1959 replies

I've been working with the python raster api, and I understand that the tiles are a way of chunking the raster, and intelligent tile management can have significant impact on performance.

What I'm struggling with is reading in existing data. The raster data can only be accessed per tile. Since the FMETile is an abstract class, it can't be created directly, and each interpretation type had a different call for creating the tile.

 

So while I can get the interpretation of a given raster band by
raster = feature.getGeometry()
band= raster.getBand(0) 
bandProperties = band.getProperties()
interpretation = bandProperties.getInterpretation()

I can't use that interpretation variable directly in creating a tile.

The only way I can see to access the data itself is to have write a separate create tile method that is essentially a switch statement

 

def createTile(self, interpretation, numRows, numCols):
      if interpretation == fmeobjects.FME_INTERPRETATION_GRAY8:
            return fmeobjects.FMEGray8Tile(numRows, numCols)
        elif interpretation == fmeobjects.FME_INTERPRETATION_RED8:
            return fmeobjects.FMERed8Tile(numRows, numCols)
        elif interpretation == fmeobjects.FME_INTERPRETATION_GREEN8:
            return fmeobjects.FMEGreen8Tile(numRows, numCols)
{...}           
        elif interpretation == fmeobjects.FME_INTERPRETATION_INT16:
            return fmeobjects.FMEInt16Tile(numRows, numCols), 'INT16')         
        else:
            return None

 

Then I can do something like
numTileCols= bandProperties.getNumTileCols()
numTileRows= bandProperties.getNumTileRows()
tile = self.createTile(interpretation, numTileRows, numTileCols)
bandData= band.getTile(numTileRows, numTileCols, tile).getData()

 

Is this really what we need to do every time we don't want to hardcode the interpretation type, or am I missing something obvious?


3 replies

Userlevel 2
Badge +17

Hi @jdh, FMEBand.getMaxValue() or getMinValue() method returns an instance of a concrete Tile class, and you can get the class through the special data member __class__ from the instance. Something like this.

tileClass = band.getMaxValue().__class__
tile = tileClass(numTileRows, numTileCols)
bandData= band.getTile(0, 0, tile).getData()

Note that the FMEBand.getTile method requires start row index and start column index in the cells matrix of the band from where the tile should be created, as its first and second parameters. Probably your setting (numTileRows, numTilCols) would generate a tile out of bounds.
Badge +22

Hi @jdh, FMEBand.getMaxValue() or getMinValue() method returns an instance of a concrete Tile class, and you can get the class through the special data member __class__ from the instance. Something like this.

tileClass = band.getMaxValue().__class__
tile = tileClass(numTileRows, numTileCols)
bandData= band.getTile(0, 0, tile).getData()

Note that the FMEBand.getTile method requires start row index and start column index in the cells matrix of the band from where the tile should be created, as its first and second parameters. Probably your setting (numTileRows, numTilCols) would generate a tile out of bounds.
Yes, I'm using bandProperties.getNumTileCols()  to get the ideal tile size and rasterProperties.getNumCols() to get the total number of columns, determining the number of horizontal tiles  math.ceil(numCols/numTileCols) and then creating a loop

 

 for c in range(numTilesX):
   for r in range(numTilesY):
        bandData= band.getTile(r*numTileRows, c*numTileCols, tile).getData()
This is orders of magnitude faster and consumers about 1/3 the RAM of attempting to read the entire raster in one tile.

 

 

The maxValue class is an interesting work around.  It doesn't however scale well.

 

A 17000x17000 RGB48 takes 0.9 seconds to get the data via the switch method,  and 46 seconds via the maxValue class due to the overhead of calculating the max for each band.

 

 

On the other hand  band.getNodataValue()  also returns a concrete Tile class instance when there is a NoData value set on the band with no additional overhead (0.9 s).

 

 

Userlevel 2
Badge +17

Hi @jdh, FMEBand.getMaxValue() or getMinValue() method returns an instance of a concrete Tile class, and you can get the class through the special data member __class__ from the instance. Something like this.

tileClass = band.getMaxValue().__class__
tile = tileClass(numTileRows, numTileCols)
bandData= band.getTile(0, 0, tile).getData()

Note that the FMEBand.getTile method requires start row index and start column index in the cells matrix of the band from where the tile should be created, as its first and second parameters. Probably your setting (numTileRows, numTilCols) would generate a tile out of bounds.
I think defining a dictionary which maps each band interpretation to corresponding tile class would be a practical solution. e.g.

 

# fmetiles.py
from fmeobjects import *
fmeTileClass = {
    FME_INTERPRETATION_REAL64 : FMEReal64Tile,
    FME_INTERPRETATION_REAL32 : FMEReal32Tile,
    FME_INTERPRETATION_UINT64 : FMEUInt64Tile,
    FME_INTERPRETATION_INT64 : FMEInt64Tile,
    ...
}
Once you define such a dictionary, you can reuse (import or copy&paste;) it easily in any script.

 

from fmetiles import fmeTileClass
tile = fmeTileClass[interpretation](numTileRows, numTileCols)
bandData= band.getTile(0, 0, tile).getData()

Reply