1

I am trying to run focal statistics in Python. The idea is to set a 1 square kilometers buffer (circular) around each pixel, then the percentage of certain type of pixels inside the buffer was calculated. The central pixel will be assigned a label based on the percentage. I am able to generate a Python code to complete the process. However, there are two issues. First, the result does not make sense. I cannot get a circular shape but a square one. Second, the process takes a long time. I tried to add parallel processing but it still took 5 hours to process one image. The same task can be completed in ArcGIS in 7 minutes. My code is shown as following.

import os
import rasterio
import numpy as np
import geopandas as gpd
from concurrent.futures import ProcessPoolExecutor

shapefile_path = "directory to an outside boundary shapefile"
gdf = gpd.read_file(shapefile_path)
urban = gpd.read_file("a shapefile for defining an inside boundary")

# Convert the CRS of 'urban' GeoDataFrame to match the CRS of 'gdf'
urban = urban.to_crs(gdf.crs)

def reclass(raster_path):
    with rasterio.open(raster_path) as dataset1:
        raster_data = dataset1.read()[0]

    [x_list,y_list] = np.where(raster_data==1)

    urban_reclass = raster_data
    if np.shape(x_list)[0] >int(33*33/2):
        for x in x_list:
            for y in y_list:
                tmp = []
                       
                x_line = np.nanmin([x,np.shape(raster_data)[0]-x,20])
                y_line = np.nanmin([y,np.shape(raster_data)[1]-y,20])
                for i in range(x_line):
                    for j in range(y_line):
                        tmp.append(raster_data[x-i][y-j])
                        tmp.append(raster_data[x+i][y+j])
                        tmp.append(raster_data[x-i][y-j])
                        tmp.append(raster_data[x+i][y+j])
        
 
                tmp = np.array(tmp)
                if np.shape(tmp[tmp==1])[0] >= np.shape(tmp)[0]/2:
                    urban_reclass[x][y] = 11
                if np.shape(tmp[tmp==1])[0] < np.shape(tmp)[0]/2 and np.shape(tmp[tmp==1])[0] >= np.shape(tmp)[0]/4:
                    urban_reclass[x][y] = 12
                if np.shape(tmp[tmp==1])[0] < np.shape(tmp)[0]/4:
                    urban_reclass[x][y] = 13
        if os.path.exists(r"output directory\relcass2\\") == False:
            os.makedirs(r"output directory\relcass2\\")
        file_name = raster_path.split("\\")[-1][:-4]
        with rasterio.open(r"outputdirectory\relcass2\\" + file_name + ".tif", mode='w', driver='GTiff',
                              width=dataset1.width, height=dataset1.height, count=1,
                              crs=dataset1.crs, transform=dataset1.transform, dtype=np.uint8) as dst:
            dst.write(urban_reclass, 1)

# Define the directory containing the raster files
input_dir =  "Folder containing raster files"

# Get a list of raster file paths
raster_files = [os.path.join(input_dir, file) for file in os.listdir(input_dir) if file.endswith(".tif")]

# Process the raster files in parallel using all available CPU cores
with ProcessPoolExecutor() as executor:
    results = executor.map(reclass, raster_files)

# Print the percentage of different pixel types for each raster file
for result in results:
    raster_path, urban_reclass = result
    print(f"Raster file: {raster_path}")
    unique, counts = np.unique(urban_reclass, return_counts=True)
    total_pixels = np.sum(counts)
    for value, count in zip(unique, counts):
        percentage = (count / total_pixels) * 100
        print(f"Pixel type {value}: {percentage:.2f}%")
    print()

The result looks like this: enter image description here

The front layer is the result. I assigned the labels of class 11(>=50%), 12(<50% but >=25%), 13(<25%). The raster layer used to generate the above one is also displayed. Value 80 represent impervious surface, which I used for calculation and reclassification.

1 Answer 1

0

Couple of options spring to mind that you haven't mentioned - I would reccommend first the GDAL VRT options, specifically the KernelFilteredSource, or if you need more customisation, pixel functions. It is a bit clunky with XML but conceptually quite simple.

The other option is to try another implementation, or at least look at their code. For example you could try https://github.com/jasperroebroek/focal-stats

1
  • Thank you for pointing out these useful sources. I will check them out.
    – Larix Yang
    Commented Dec 15, 2023 at 10:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.