Module delta.extensions.sources.tiff

Block-aligned reading from multiple Geotiff files.

Functions

def write_tiff(output_path: str, data: numpy.ndarray = None, image: DeltaImage = None, nodata=None, metadata: dict = None, block_size=None, show_progress: bool = False)

Write a numpy array to a file as a tiff.

Parameters

output_path : str
Filename to save tiff file to
data : numpy.ndarray
Image data to save.
image : delta_image.DeltaImage
Image data to save (specify one of this or data).
nodata : Any
Nodata value.
metadata : dict
Optional metadata to include.
block_size : Tuple[int]
Optionally override block size for writing.
show_progress : bool
Display command line progress bar.

Classes

class TiffImage (path, nodata_value=None)

Images supported by GDAL.

Opens a geotiff for reading.

Parameters

paths : str or List[str]
Either a single filename or a list. For a list, the images are opened in order as a multi-band image, assumed to overlap.
nodata_value : dtype of image
Value representing no data.
Expand source code
class TiffImage(delta_image.DeltaImage):
    """Images supported by GDAL."""

    def __init__(self, path, nodata_value=None):
        """
        Opens a geotiff for reading.

        Parameters
        ----------
        paths: str or List[str]
            Either a single filename or a list.
            For a list, the images are opened in order as a multi-band image, assumed to overlap.
        nodata_value: dtype of image
            Value representing no data.
        """
        paths = self._prep(path)

        self._path = path
        self._paths = paths
        self._handles = []
        for p in paths:
            if not os.path.exists(p):
                raise Exception('Image file does not exist: ' + p)
            result = gdal.Open(p)
            if result is None:
                raise Exception('Failed to open tiff file %s.' % (p))
            self._handles.append(result)
        self._band_map = []
        for i, h in enumerate(self._handles):
            if h.RasterXSize != self._handles[0].RasterXSize or h.RasterYSize != self._handles[0].RasterYSize:
                raise Exception('Images %s and %s have different sizes!' % (self._paths[0], self._paths[i]))
            for j in range(h.RasterCount):
                self._band_map.append((i, j + 1)) # gdal uses 1-based band indexing

        if nodata_value is None:
            temp = self._gdal_band(0).GetNoDataValue()
            super().__init__(temp)
        else:
            super().__init__(nodata_value)

    def __del__(self):
        self.close()

    def _prep(self, paths): #pylint:disable=no-self-use
        """
        Prepare the file to be opened by other tools (unpack, etc).

        This can be overwritten by subclasses to, for example,
        unpack a zip file to a cache directory.

        Parameters
        ----------
        paths: str or List[str]
            Paths passed to constructor

        Returns
        -------
        Returns a list of underlying files to load instead of the original paths.
        """
        if isinstance(paths, str):
            return [paths]
        return paths

    def __asert_open(self):
        if self._handles is None:
            raise IOError('Operating on an image that has been closed.')

    def close(self):
        """
        Close the image.
        """
        self._handles = None # gdal doesn't have a close function for some reason
        self._band_map = None
        self._paths = None

    def path(self):
        """
        Returns the paths returned by `_prep`.
        """
        return self._path

    def num_bands(self):
        self.__asert_open()
        return len(self._band_map)

    def size(self):
        self.__asert_open()
        return (self._handles[0].RasterYSize, self._handles[0].RasterXSize)

    def _read(self, roi, bands, buf=None):
        self.__asert_open()

        num_bands = len(bands) if bands else self.num_bands()
        if buf is None:
            buf = np.zeros(shape=(num_bands, roi.height(), roi.width()), dtype=self.dtype())
        else:
            s = buf[0, :, :].shape
            if s != (roi.height(), roi.width()):
                raise IOError('Buffer shape should be (%d, %d) but is (%d, %d)!' %
                              (roi.height(), roi.width(), s[0], s[1]))
        if bands:
            for i, b in enumerate(bands):
                band_handle = self._gdal_band(b)
                band_handle.ReadAsArray(yoff=roi.min_y, xoff=roi.min_x,
                                        win_ysize=roi.height(), win_xsize=roi.width(), buf_obj=buf[i, :, :])
        else:
            cur_band = 0
            for h in self._handles:
                h.ReadAsArray(yoff=roi.min_y, xoff=roi.min_x,
                              ysize=roi.height(), xsize=roi.width(),
                              buf_obj=buf[cur_band:cur_band + h.RasterCount, :, :])
                cur_band += h.RasterCount
        return np.transpose(buf, [1, 2, 0])

    def _gdal_band(self, band):
        (h, b) = self._band_map[band]
        ret = self._handles[h].GetRasterBand(b)
        assert ret
        return ret

    def _gdal_type(self, band=0):
        """
        Returns the GDAL data type of the image.
        """
        self.__asert_open()
        return self._gdal_band(band).DataType

    def dtype(self):
        self.__asert_open()
        dtype = self._gdal_type(0)
        if dtype in _GDAL_TO_NUMPY_TYPES:
            return _GDAL_TO_NUMPY_TYPES[dtype]
        raise Exception('Unrecognized gdal data type: ' + str(dtype))

    def bytes_per_pixel(self, band=0):
        """
        Returns
        -------
        int:
            the number of bytes per pixel
        """
        self.__asert_open()
        return gdal.GetDataTypeSize(self._gdal_type(band)) // 8

    def block_size(self):
        """
        Returns
        -------
        (int, int):
            block height, block width
        """
        self.__asert_open()
        band_handle = self._gdal_band(0)
        block_size = band_handle.GetBlockSize()
        return (block_size[1], block_size[0])

    def metadata(self):
        self.__asert_open()
        data = dict()
        h = self._handles[0]
        data['projection'] = h.GetProjection()
        data['geotransform'] = h.GetGeoTransform()
        data['gcps'] = h.GetGCPs()
        data['gcpproj'] = h.GetGCPProjection()
        data['metadata'] = h.GetMetadata()
        data['spatial_ref'] = h.GetSpatialRef()
        return data

    def block_aligned_roi(self, desired_roi):
        self.__asert_open()
        bounds = rectangle.Rectangle(0, 0, width=self.width(), height=self.height())
        if not bounds.contains_rect(desired_roi):
            raise Exception('desired_roi ' + str(desired_roi)
                            + ' is outside the bounds of image with size' + str(self.size()))

        block_height, block_width = self.block_size()
        start_block_x = int(math.floor(desired_roi.min_x     / block_width))
        start_block_y = int(math.floor(desired_roi.min_y     / block_height))
        # Rect max is exclusive
        stop_block_x = int(math.floor((desired_roi.max_x-1) / block_width))
        # The stops are inclusive
        stop_block_y = int(math.floor((desired_roi.max_y-1) / block_height))

        start_x = start_block_x * block_width
        start_y = start_block_y * block_height
        w = (stop_block_x - start_block_x + 1) * block_width
        h = (stop_block_y - start_block_y + 1) * block_height

        # Restrict the output region to the bounding box of the image.
        # - Needed to handle images with partial tiles at the boundaries.
        ans = rectangle.Rectangle(start_x, start_y, width=w, height=h)
        bounds = rectangle.Rectangle(0, 0, width=self.width(), height=self.height())
        return ans.get_intersection(bounds)

    def save(self, path, tile_size=None, nodata_value=None, show_progress=False):
        """
        Save to file, with preprocessing applied.

        Parameters
        ----------
        path: str
            Filename to save to.
        tile_size: (int, int)
            If specified, overwrite block size
        nodata_value: image dtype
            If specified, overwrite nodata value
        show_progress: bool
            Write progress bar to stdout
        """

        write_tiff(path, image=self, nodata=nodata_value, block_size=tile_size,
                   show_progress=show_progress)

Ancestors

Subclasses

Methods

def block_size(self)

Returns

(int, int): block height, block width

def bytes_per_pixel(self, band=0)

Returns

int:
the number of bytes per pixel
def close(self)

Close the image.

def path(self)

Returns the paths returned by _prep.

def save(self, path, tile_size=None, nodata_value=None, show_progress=False)

Save to file, with preprocessing applied.

Parameters

path : str
Filename to save to.
tile_size : (int, int)
If specified, overwrite block size
nodata_value : image dtype
If specified, overwrite nodata value
show_progress : bool
Write progress bar to stdout

Inherited members

class TiffWriter (filename)

Write a geotiff to a file.

Expand source code
class TiffWriter(delta_image.DeltaImageWriter):
    """
    Write a geotiff to a file.
    """
    def __init__(self, filename):
        self._filename = filename
        self._tiff_w = None

    def initialize(self, size, numpy_dtype, metadata=None, nodata_value=None):
        assert (len(size) == 3), ('Error: len(size) of '+str(size)+' != 3')
        TILE_SIZE = 256
        self._tiff_w = _TiffWriter(self._filename, size[0], size[1], num_bands=size[2],
                                   data_type=_numpy_dtype_to_gdal_type(numpy_dtype), metadata=metadata,
                                   nodata_value=nodata_value,
                                   tile_height=min(TILE_SIZE, size[0]), tile_width=min(TILE_SIZE, size[1]))

    def write(self, data, y, x):
        self._tiff_w.write_region(data, y, x)

    def close(self):
        if self._tiff_w is not None:
            self._tiff_w.close()

    def abort(self):
        self.close()
        try:
            os.remove(self._filename)
        except OSError:
            pass

Ancestors

Inherited members