Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for processing raster data
This adds a `normalize` function that accepts a GeoTIFF file as input and creates a new GeoTIFF which has been optimized for serving through GeoServer. The following steps are taken: * Paletted images are expanded to RGB. * Images are internally tiled using a block size of 512. * Overview images are added to the TIFF when larger than block size. Support is provided through the `rasterio` package which itself depends on GDAL. You'll need to make sure the header files for GDAL are accessible in order to install it.
- Loading branch information
Mike Graves
committed
Jun 16, 2017
1 parent
4d641e5
commit e5d6e28
Showing
12 changed files
with
233 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/usr/bin/env sh | ||
set -e | ||
|
||
if [ ! -d "$HOME/gdal/lib" ]; then | ||
wget http://download.osgeo.org/gdal/$GDAL_VERSION/gdal-$GDAL_VERSION.tar.xz | ||
tar -xf gdal-$GDAL_VERSION.tar.xz | ||
cd gdal-$GDAL_VERSION && ./configure --prefix=$HOME/gdal && make && make install | ||
else | ||
echo "Using cached directory." | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import math | ||
|
||
import rasterio | ||
from rasterio.enums import ColorInterp, Resampling | ||
import numpy as np | ||
|
||
|
||
BLOCKSIZE = 512 | ||
TIFF_DEFAULTS = { | ||
'compress': 'JPEG', | ||
'interleave': 'PIXEL', | ||
} | ||
|
||
|
||
def make_palette(cmap): | ||
"""Turn a color map dictionary into a numpy array.""" | ||
palette = np.zeros((len(cmap),), dtype=[('R', np.uint8), ('G', np.uint8), | ||
('B', np.uint8), ('A', np.uint8)]) | ||
for k, v in cmap.items(): | ||
palette[k] = v | ||
return palette | ||
|
||
|
||
def windows(fp): | ||
"""Generate tuples of TIFF windows and blocks. | ||
``windows`` is a generator that produces tuples of ``rasterio`` | ||
windows and block generators. A block generator produces data arrays | ||
for each band in the given window. | ||
This can be used to iterate over chunks of large TIFFs without | ||
reading the whole thing into memory at once. | ||
""" | ||
try: | ||
palette = make_palette(fp.colormap(1)) | ||
except: | ||
palette = None | ||
for _, window in fp.block_windows(): | ||
yield window, blocks(fp, window, palette) | ||
|
||
|
||
def blocks(fp, window, palette=None): | ||
"""Generate blocks of band data for a given window. | ||
This produces numpy arrays of data for an image's bands in the given | ||
window. Paletted images will be expanded to RGB bands. | ||
""" | ||
cinterp = fp.colorinterp(1) | ||
if cinterp == ColorInterp.palette: | ||
block = fp.read(1, window=window) | ||
expanded = np.take(palette, block) | ||
for band in ('R', 'G', 'B'): | ||
yield expanded[band] | ||
elif cinterp == ColorInterp.gray: | ||
yield fp.read(1, window=window) | ||
else: | ||
for band in range(1, 4): | ||
yield fp.read(band, window=window) | ||
|
||
|
||
def overviews(size, blocksize): | ||
"""Compute optimal list of overview levels. | ||
The size parameter should be max(width, height). | ||
""" | ||
num_levels = int(math.ceil(math.log((size/blocksize), 2))) | ||
return [2**y for y in range(1, num_levels + 1)] | ||
|
||
|
||
def normalize(filein, fileout): | ||
"""Create a normalized GeoTIFF file. | ||
This will take the input GeoTIFF and create a GeoTIFF that is | ||
optimized for serving through GeoServer. Compression is set to | ||
JPEG, the TIFF is tiled using a block size of 512, and overviews | ||
are added. Paletted TIFFs are expanded to 3-band images and RGB | ||
images are converted to YCbCr color space. | ||
""" | ||
with rasterio.open(filein, 'r') as fp_in: | ||
kwargs = fp_in.profile | ||
kwargs.update(TIFF_DEFAULTS) | ||
if max(fp_in.width, fp_in.height) >= BLOCKSIZE: | ||
kwargs.update({'tiled': True, 'blockxsize': BLOCKSIZE, | ||
'blockysize': BLOCKSIZE}) | ||
if fp_in.colorinterp(1) == ColorInterp.palette or fp_in.count == 3: | ||
kwargs.update({'photometric': 'YCBCR', 'count': 3}) | ||
with rasterio.open(fileout, 'w', **kwargs) as fp_out: | ||
for window, bands in windows(fp_in): | ||
for band, block in enumerate(bands, start=1): | ||
fp_out.write(block, window=window, indexes=band) | ||
with rasterio.open(fileout, 'r+') as fp: | ||
factors = overviews(max(fp.width, fp.height), BLOCKSIZE) | ||
fp.build_overviews(factors, Resampling.average) | ||
fp.update_tags(ns='rio_overview', resampling='average') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from tempfile import NamedTemporaryFile | ||
|
||
import rasterio | ||
|
||
from slingshot.raster import normalize, BLOCKSIZE, overviews | ||
|
||
|
||
def test_normalize_tiles_layer(rgb): | ||
with NamedTemporaryFile() as out: | ||
normalize(rgb, out.name) | ||
with rasterio.open(out.name, 'r') as fp: | ||
assert fp.block_shapes[0] == (BLOCKSIZE, BLOCKSIZE) | ||
|
||
|
||
def test_normalize_turns_paletted_layer_into_rgb(paletted): | ||
with NamedTemporaryFile() as out: | ||
normalize(paletted, out.name) | ||
with rasterio.open(out.name, 'r') as fp: | ||
assert fp.count == 3 | ||
|
||
|
||
def test_normalize_keeps_grayscale_as_grayscale(grayscale): | ||
with NamedTemporaryFile() as out: | ||
normalize(grayscale, out.name) | ||
with rasterio.open(out.name, 'r') as fp: | ||
assert fp.count == 1 | ||
|
||
|
||
def test_overviews_generates_factors_of_overviews(): | ||
assert overviews(2048, 512) == [2, 4] | ||
|
||
|
||
def test_normalize_creates_overviews(rgb): | ||
with NamedTemporaryFile() as out: | ||
normalize(rgb, out.name) | ||
with rasterio.open(out.name, 'r') as fp: | ||
assert fp.overviews(1) == [2] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters