Search code examples
python-3.xgdalrasterio

Iteratively load image block by block where blocks are partially overlapped


Trying to process a large satellite image (~10GB). For memory efficient processing chunk of image (block/tile) is being loaded into memory in each iteration.

Tiled Image

The sample code for this as below:

def process_image(src_img, dst_img, band_id=1):
    with rasterio.open(src_img) as src:
        kwargs = src.meta
        tiles = src.block_windows(band_id)
        with rasterio.open(dst_img, 'w', **kwargs) as dst:
            for idx, window in tiles:
                print("Processing Block: ", idx[0]+1, ", ", idx[1]+1)
                src_data = src.read(band_id, window=window)
                dst_data = src_data ** 2 # Do the Processing Here
                dst.write_band( band_id, dst_data, window=window)
    return 0

However, for any kind of processing which requires kernel wise operation (such as any convolve filter like smoothing) this leads to an issue in processing near the edges of the blocks. To address this, each block should be slightly overlapped as shown below:

Overlapped Zoomed

My objective is to load the blocks as the following where the amount of overlap can be adjusted according to the need.

Overlapped Tile

So far I did not find any straight forward way to achieve this. I would be grateful for any help in this regard.


Solution

  • You can just expand your windows:

    import rasterio as rio
    from rasterio import windows
    
    def overlapping_blocks(src, overlap=0, band=1):
        nols, nrows = src.meta['width'], src.meta['height']
        big_window = windows.Window(col_off=0, row_off=0, width=nols, height=nrows)
        for ji, window in src.block_windows(band):
    
            if overlap == 0:
                yield ji, window
    
            else:
                col_off = window.col_off - overlap
                row_off = window.row_off - overlap
                width = window.width + overlap * 2
                height = window.height + overlap * 2
                yield ji, windows.Window(col_off, row_off, width, height).intersection(big_window)
    

    And you would use it like so:

    def process_image(src_img, dst_img, band_id=1):
        with rasterio.open(src_img) as src:
            kwargs = src.meta
            with rasterio.open(dst_img, 'w', **kwargs) as dst:
                for idx, window in overlapping_block_windows(src, overlap=1, band=band_id):
                    print("Processing Block: ", idx[0]+1, ", ", idx[1]+1)
                    src_data = src.read(band_id, window=window)
                    dst_data = src_data ** 2 # Do the Processing Here
                    dst.write_band( band_id, dst_data, window=window)
        return 0
    

    And here is a way to overlap both block windows and non-block windows, with an additional argument boundless to specify whether to extend windows beyond the raster extent, useful for boundless reading:

    from itertools import product
    import rasterio as rio
    from rasterio import windows
    
    
    def overlapping_windows(src, overlap, width, height, boundless=False):
        """"width & height not including overlap i.e requesting a 256x256 window with 
            1px overlap will return a 258x258 window (for non edge windows)"""
        offsets = product(range(0, src.meta['width'], width), range(0, src.meta['height'], height))
        big_window = windows.Window(col_off=0, row_off=0, width=src.meta['width'], height=src.meta['height'])
        for col_off, row_off in offsets:
    
            window = windows.Window(
                col_off=col_off - overlap,
                row_off=row_off - overlap,
                width=width + overlap * 2,
                height=height + overlap * 2)
    
            if boundless:
                yield window
            else:
                yield window.intersection(big_window)
    
    
    def overlapping_blocks(src, overlap=0, band=1, boundless=False):
    
        big_window = windows.Window(col_off=0, row_off=0, width=src.meta['width'], height=src.meta['height'])
        for ji, window in src.block_windows(band):
    
            if overlap == 0:
                yield window
    
            else:
                window = windows.Window(
                    col_off=window.col_off - overlap,
                    row_off=window.row_off - overlap,
                    width=window.width + overlap * 2,
                    height=window.height + overlap * 2)
                if boundless:
                    yield window
                else:
                    yield window.intersection(big_window)
    

    Output windows: enter image description here