Working with 2D Bitmap Data

Prerequisites

To gain any more functionality beyond simple read/write of DataTank images, you will need to install one or more of these modules.

GDAL

You may already have GDAL and its Python bindings installed – you can check by doing:

python -c 'import gdal'

For a binary install of GDAL, see this page which has links for various platforms. The Mac OS X framework package includes Python bindings.

PIL

The Python Imaging Library is also required for some operations, and is generally a useful thing to have. You can check for it with:

python -c 'import Image'

If it’s not installed, you can try:

sudo easy_install PIL

This may or may not give you a useful module, depending on what shared libraries you have available. Refer to the PIL documentation for more details.

Geospatial image example

_images/ss_image_as_bitmap.png

This example shows different ways of passing a file to an external program. In one case, we pass a DataTank “File” object, which results in a hard link named “Image File” in the Python program’s working directory. This is most efficient in terms of disk space, but sometimes a geospatial raster includes a so-called “world file” which provides coordinate information, rather than embedding it in the TIFF tags. For example, “my_geotiff.tiff” could have a corresponding “my_geotiff.tfw” and GDAL knows to look for that file alongside the GeoTIFF. In that case, you need to pass the absolute path.

Clear as mud? Use The Source, Luke! Hopefully this example will clarify the words above.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This software is under a BSD license.  See LICENSE.txt for details.

import os
import numpy as np
from datatank_py.DTDataFile import DTDataFile
from datatank_py.DTBitmap2D import DTBitmap2D
from time import time

if __name__ == '__main__':

    #
    # This program replaces a standard DataTank External Program module
    # in C++.  Note that it must be executable (chmod 755 in Terminal)
    # and gdal and numpy are required.
    #
    # It takes as input a file path, then loads an image from it, using
    # GDAL to determine the raster origin and pixel size.  The image is
    # then saved as a 2D Bitmap object, either 8 or 16 bits as needed.
    #
    # Note that this is not appropriate for elevation data, as DataTank
    # will normalize the file range from [0, 1] if you extract the gray
    # component.
    #

    input_file = DTDataFile("Input.dtbin")

    # DT creates this hard link in the working directory, if passed a file.
    # This is preferred, as it's fewer variables in DataTank, but if you
    # have a world file, GDAL needs to be able to find it in the original
    # directory.
    image_path = "Image File"

    # if no path set, then use the file itself (preferable)
    if os.path.exists(image_path) == False:
        image_path = input_file["Image Path"]

    input_file.close()

    start_time = time()
    errors = []

    if image_path:
        image_path = os.path.expanduser(image_path)
    if image_path is None or os.path.exists(image_path) is False:
        errors.append("\"%s\" does not exist" % (image_path))

    img = DTBitmap2D(image_path)
    if img is None:
        # set an error and bail out; DataTank doesn't appear to use this, but displays
        # stderr output instead, so print them also
        errors.append("Unable to open as an image file")
        with DTDataFile("Output.dtbin", truncate=True) as output_file:
            output_file["ExecutionErrors"] = errors
            output_file.["ExecutionTime"] = time() - start_time
            exit(1)

    with DTDataFile("Output.dtbin", truncate=True) as output_file:

        output_file.["ExecutionTime"] = time() - start_time
        output_file["Var"] = img

        # need to save a StringList of execution errors as Seq_ExecutionErrors
        if len(errors):
            output_file["ExecutionErrors"] = errors

DTBitmap2D

class datatank_py.DTBitmap2D.DTBitmap2D(path_or_image=None, rgba_bands=None)

Base implementation for DTBitmap2D. This is a gray or color (RGB) image, with an optional alpha channel. It also has spatial data, so it can be displayed in DataTank on a physical grid, rather than the logical grid of pixels.

Provides implementation of datatank_py.DTPyWrite.DTPyWrite for all subclasses, and can be instantiated directly to use as a container.

__init__(path_or_image=None, rgba_bands=None)
Parameters:
  • path_or_image – a path to an image file or a PIL image object
  • rgba_bands – a 1-4 element tuple mapping one-based band indexes to (r, g, b, a)
Returns:

An object that implements datatank_py.DTPyWrite.DTPyWrite. Don’t rely on the class name for anything.

If you pass the default argument, you’ll get back an object that implements dt_write(), and you are responsible for filling in its attributes. These are:

Member grid:optional, of the form [x0, y0, dx, dy]
Member red:required for RGB image only
Member green:required for RGB image only
Member blue:required for RGB image only
Member gray:required for grayscale image only
Member alpha:optional
Member projection:
 optional WKT string

Each must be a 2D numpy array, and you are responsible for ensuring a consistent shape and proper dimension. This is basically equivalent to the way DTSource constructs a C++ DTBitmap2D. Note that DataTank only supports 8 bit and 16 bit images.

If a PIL image is provided, it will be used as-is, and the grid will be a unit grid with origin at (0, 0). If a path is provided, DTBitmap2D will try to use GDAL to load the image and extract its components, as well as any spatial referencing included with the image. If GDAL fails for any reason, PIL will be used as a fallback.

If you have an image with more than 4 bands, you can choose which to represent as red, green, blue, and alpha by passing :param rgba_bands:. For instance, if you have LANDSAT 8 multispectral imagery, you could pass (4, 3, 2) to get a true color image. By default, instantiating an image with more than 4 bands will give an error.

Note that DTBitmap2D does not attempt to be lazy at loading data; it will read the entire image into memory as soon as you instantiate it.

channel_count()
Returns:number of channels, including data and alpha

Compatible with multiband images.

dtype()
Returns:a NumPy array datatype numpy.dtype
equalize_histogram()

Alters the image by equalizing the histogram among any non-alpha bands.

classmethod from_data_file(datafile, name)

Create a new instance from a DTDataFile by name.

Parameters:
  • datafile – an open DTDataFile instance
  • name – the name of the DTBitmap2D object in the file (including any time index)
Returns:

a new DTBitmap2D instance

has_alpha()
Returns boolean:
 True if the image has an alpha channel

Only valid for grayscale or RGB image models, not arbitrary multiband images.

is_gray()
Returns boolean:
 True if the image is grayscale (not RBG or RBGA)

Only valid for grayscale or RGB image models, not arbitrary multiband images.

mesh_from_channel(channel='gray', alpha_as_mask=False)

Extract a given bitmap plane as a DTMesh2D object.

Parameters:channel – may be one of (red, green, blue, gray, alpha)
Returns:a datatank_py.DTMesh2D.DTMesh2D instance

Requires GDAL

This is how you would extract raw pixels and georegistration data from e.g., a 32-bit GeoTIFF or other image supported by GDAL, and bring it in to DataTank. This is particularly useful for elevation data.

The returned mesh will use the spatial grid (origin and pixel size) of the image. Note that the image’s nodata attribute will be used to create an appropriate datatank_py.DTMask.DTMask which will be applied to the mesh. The nodata value is obtained from GDAL.

>>> from datatank_py.DTBitmap2D import DTBitmap2D
>>> img = DTBitmap2D("int16.tiff")
>>> img.mesh_from_channel()
<datatank_py.DTMesh2D.DTMesh2D object at 0x101a7a1d0>
>>> img = DTBitmap2D("rgb_geo.tiff")
>>> img.mesh_from_channel(channel="red")
<datatank_py.DTMesh2D.DTMesh2D object at 0x10049ab90>

The returned mesh will contain values in floating point

pil_image()

Attempt to convert a raw image to a PIL Image object.

Returns:a PIL Image, or None if PIL can’t be loaded or if the conversion failed.

Requires PIL

This allows you to run PIL image filters and operations. Only tested with 8-bit images, but gray/gray+alpha and RGB/RGBA have all been tested.

raster_size()
Returns:size in pixels, 2-tuple ordered as (horizontal, vertical).
synthesize_alpha()

Adds or overwrites an alpha channel corresponding to all zero-valued RGB pixels. This is not very smart, as it doesn’t check for neighboring pixels; you can end up turning actual black pixels transparent.

write_geotiff(output_path, projection_name=None)

Save a DTBitmap2D as a GeoTIFF file.

Parameters:
  • output_path – a file path; all parent directories must exist
  • projection_name – the spatial reference system to associate with the image

Requires GDAL

The spatial reference must be a string recognized by GDAL. For example, EPSG:4326 and WGS84 are both valid. See the documentation for OSRSpatialReference::SetFromUserInput at http://www.gdal.org/ogr/classOGRSpatialReference.html for more specific details.

Note that exceptions will be raised if the DTBitmap2D is not valid (has no data), or if any GDAL functions fail. This method has only been tested with 8-bit images, but gray/rgb/alpha images work as expected.

DTPyCoreImage

datatank_py.DTPyCoreImage.ci_filter_named(filter_name)
Returns:a Quartz:CIFilter with the given name

Requires Mac OS X and PyObjC

These filters can be applied to a Quartz.CIImage using:

ci_image = ci_image_from_planes(r, g, b)
filt = ci_filter_named("CIExposureAdjust")
filt.setValue_forKey_(ci_image, "inputImage")
filt.setValue_forKey_(0.2, "inputEV")
ci_image = filt.valueForKey_("outputImage")

From this point, you would apply another filter or use dt_bitmap2d_from_ci_image() to convert the image to a datatank_py.DTBitmap2D.DTBitmap2D for DataTank.

datatank_py.DTPyCoreImage.ci_image_from_planes(red, green, blue)

Create a Quartz CIImage from bitmap data.

Parameters:
  • red – a NumPy 2D array of bitmap values
  • green – a NumPy 2D array of bitmap values
  • blue – a NumPy 2D array of bitmap values
Returns:

a Quartz.CIImage instance

Requires Mac OS X and PyObjC

The Quartz.CIImage is only useful for applying Quartz.CIFilter or other transformations using PyObjC. Only 8-bit RGB images are supported at this time, and your image data will be truncated if it is not 8-bit.

datatank_py.DTPyCoreImage.dt_bitmap2d_from_ci_image(ci_image, width, height, grid)
Parameters:
  • ci_image – a Quartz.CIImage instance from PyObjC
  • width – desired width in pixels
  • height – desired height in pixels
  • grid – a four-tuple (x0, y0, dx, dy) spatial grid
Returns:

a datatank_py.DTBitmap2D.DTBitmap2D instance

Requires Mac OS X and PyObjC

This function allows you to turn a Quartz.CIImage into an object that DataTank can use. Only 8-bit RGB images are supported at this time.