Source code for polsartools.analysis.pauliRGB

import os
import numpy as np
from osgeo import gdal,osr
gdal.UseExceptions()
import matplotlib.pyplot as plt
from polsartools.utils.utils import conv2d,time_it
# from pyproj import CRS
import os

def read_bin(file):
    ds = gdal.Open(file)
    band = ds.GetRasterBand(1)
    arr = band.ReadAsArray()
    return arr

def norm_data(data,lp=5,gp=95,dB_scale = True):
    
    if dB_scale:
        data = 10*np.log10(data)
    data[data==-np.inf] = np.nan
    data[data==np.inf] = np.nan
    data = (data - np.nanmin(data)) / (np.nanmax(data) - np.nanmin(data))

    p5 = np.nanpercentile(data, lp)
    p95 = np.nanpercentile(data, gp)
    data = np.clip(data, p5, p95)
    data = (data - p5) / (p95 - p5)

    return data 

def read_and_normalize(file_path):
    """Reads binary data and normalizes it."""
    return norm_data(read_bin(file_path)) if os.path.isfile(file_path) else None


# def create_prj(ref_raster_path, output_png_path):
#     """
#     Creates a .prj file with WKT from the given EPSG code.
#     """
#     ds = gdal.Open(ref_raster_path)
#     wkt = ds.GetProjection()
    
#     # if not wkt:
#     #     raise ValueError("Reference raster does not contain spatial projection information.")
    
#     crs = CRS.from_wkt(wkt)
#     wkt_text = crs.to_wkt()

#     prj_path = os.path.splitext(output_png_path)[0] + ".prj"
#     with open(prj_path, "w") as f:
#         f.write(wkt_text)


def get_alpha_channel(red, green, blue):
    """
    Creates an alpha channel based on the RGB values.
    Transparent where all RGB values are zero or NaN.
    """
    zeros_mask = (red == 0.0) & (green == 0.0) & (blue == 0.0)
    nan_mask = np.isnan(red) & np.isnan(green) & np.isnan(blue)
    transparent_mask = zeros_mask | nan_mask
    return np.where(transparent_mask, 0, 255).astype(np.uint8)

def generate_rgb_png(red, green, blue,georef_file, output_path):
    

    alpha_channel = get_alpha_channel(red, green, blue)
    rgb_uint8 = (np.dstack((red, green, blue)) * 255).astype(np.uint8)
    # alpha_channel = np.where(np.all(rgb_uint8 == 0, axis=2), 0, 255).astype(np.uint8)
    rgba_uint8 = np.dstack((rgb_uint8, alpha_channel))
    
    plt.imsave(output_path, rgba_uint8)
    # print(f"Pauli RGB image saved as {output_path}")
    
    fig, ax = plt.subplots()
    plt.imshow(rgba_uint8, vmin=0, vmax=255)
    ax.axis('off')
    plt.savefig(output_path.replace(".png", "_thumb.png"), format='png', 
                bbox_inches='tight', pad_inches=0, transparent=True)

def generate_rgb_tif(red, green, blue, georef_file, output_path):
    driver = gdal.GetDriverByName('GTiff')
    height, width = red.shape
    dataset = driver.Create(output_path, width, height, 4, gdal.GDT_Byte,options=['COMPRESS=DEFLATE','PREDICTOR=2','ZLEVEL=9', 'BIGTIFF=YES','TILED=YES'])
    # Prepare the image bands
    red_uint8 = (red * 255).astype(np.uint8)
    green_uint8 = (green * 255).astype(np.uint8)
    blue_uint8 = (blue * 255).astype(np.uint8)
    # alpha = np.where((red_uint8 == 0) & (green_uint8 == 0) & (blue_uint8 == 0), 0, 255).astype(np.uint8)
    
    # Get geotransform and projection from reference file
    ref = gdal.Open(georef_file)
    dataset.SetGeoTransform(ref.GetGeoTransform())
    dataset.SetProjection(ref.GetProjection())

    
    alpha = get_alpha_channel(red, green, blue)
    
    

    dataset.GetRasterBand(1).WriteArray(red_uint8)
    dataset.GetRasterBand(2).WriteArray(green_uint8)
    dataset.GetRasterBand(3).WriteArray(blue_uint8)
    dataset.GetRasterBand(4).WriteArray(alpha)

    dataset.FlushCache()
    ref = None
    # print(f"RGB GeoTIFF saved to {output_path}")


def create_pgw(reference_file, output_png_path):
    ds = gdal.Open(reference_file)
    gt = ds.GetGeoTransform()
    ds=None
    pgw_values = [
        gt[1],     # pixel width
        gt[2],     # rotation (typically 0)
        gt[4],     # rotation (typically 0)
        gt[5],     # pixel height (typically negative)
        gt[0] + gt[1] / 2,  # x of center of top-left pixel
        gt[3] + gt[5] / 2   # y of center of top-left pixel
    ]
    
    pgw_path = os.path.splitext(output_png_path)[0] + ".pgw"
    with open(pgw_path, "w") as f:
        for val in pgw_values:
            f.write(f"{val:.10f}\n")
    # print(f"PGW file created at {pgw_path}")\



# def create_pgw(reference_file, output_png_path):
#     ds = gdal.Open(reference_file)
#     gt = ds.GetGeoTransform()
#     proj = ds.GetProjection()
#     ds = None

#     # Prepare source spatial reference
#     source_ref = osr.SpatialReference()
#     source_ref.ImportFromWkt(proj)

#     # Prepare target spatial reference (WGS84)
#     target_ref = osr.SpatialReference()
#     target_ref.ImportFromEPSG(4326)

#     # Create coordinate transformation if needed
#     if not source_ref.IsGeographic() or not source_ref.IsSame(target_ref):
#         transform = osr.CoordinateTransformation(source_ref, target_ref)

#         # Transform center of top-left pixel to WGS84
#         x_geo = gt[0] + gt[1] / 2
#         y_geo = gt[3] + gt[5] / 2
#         x_wgs84, y_wgs84, _ = transform.TransformPoint(x_geo, y_geo)
#     else:
#         x_wgs84 = gt[0] + gt[1] / 2
#         y_wgs84 = gt[3] + gt[5] / 2

#     # Create PGW values (preserve pixel scale and rotation)
#     pgw_values = [
#         gt[1],     # pixel width
#         gt[2],     # rotation
#         gt[4],     # rotation
#         gt[5],     # pixel height
#         x_wgs84,   # transformed X
#         y_wgs84    # transformed Y
#     ]

#     # Write PGW
#     pgw_path = os.path.splitext(output_png_path)[0] + ".pgw"
#     with open(pgw_path, "w") as f:
#         for val in pgw_values:
#             f.write(f"{val:.10f}\n")

    
[docs] @time_it def pauliRGB(infolder,save_tif=False, window_size=None): """ Generate Pauli RGB image from polarimetric SAR data (C4, C3, T4, T3, or S2 matrices). This function creates a Pauli decomposition RGB composite image from full polarimetric SAR data. Examples -------- >>> # Generate Pauli RGB from a polarimetric folder >>> pauliRGB("/path/to/polSAR_data") >>> # Output both PNG and GeoTIFF versions >>> pauliRGB("/path/to/polSAR_data", tif_flag=True) Parameters ---------- infolder : str Path to the folder containing polarimetric matrix files (.bin or .tif). Supports full- and dual-pol data in formats: C4, C3, T4, T3, or S2. save_tif : bool, default=False If True, generates both PNG and GeoTIFF (.tif) versions of the Pauli RGB image. window_size : int, optional Size of the moving average window for smoothing. If None, no smoothing is applied. Returns ------- None Writes output to: - PauliRGB.png: RGB composite with world file for georeferencing - PauliRGB.tif: Optional GeoTIFF output if `tif_flag=True` """ def find_file(infolder, base_name): for ext in [".bin", ".tif"]: path = os.path.join(infolder, f"{base_name}{ext}") if os.path.isfile(path): return path return None c4_keys = ["C11", "C22", "C33", "C44", "C14_real"] c4_files = [find_file(infolder, key) for key in c4_keys] if all(c4_files): C11, C22, C33, C44 = [read_bin(f) for f in c4_files[:4]] C14_real = read_bin(c4_files[4]) red = norm_data(C11 + C44 - 2 * C14_real) green = norm_data(C22 + C33) blue = norm_data(C11 + C44 + 2 * C14_real) georef_file = c4_files[0] # C3 fallback elif all(find_file(infolder, f"C{i}") for i in [11, 22, 33]): C11 = read_bin(find_file(infolder, "C11")) C22 = read_bin(find_file(infolder, "C22")) C33 = read_bin(find_file(infolder, "C33")) C13_real = read_bin(find_file(infolder, "C13_real")) blue = norm_data(0.5 * (C11 + C33 + 2 * C13_real)) red = norm_data(0.5 * (C11 + C33 - 2 * C13_real)) green = norm_data(C22) georef_file = find_file(infolder, "C11") # T3 fallback elif all(find_file(infolder, f"T{i}") for i in [11, 22, 33]): red = read_and_normalize(find_file(infolder, "T22")) green = read_and_normalize(find_file(infolder, "T33")) blue = read_and_normalize(find_file(infolder, "T11")) georef_file = find_file(infolder, "T11") # S2 fallback elif all(find_file(infolder, f"s{i}") for i in [11, 12, 22]): S11 = read_bin(find_file(infolder, "s11")) S12 = read_bin(find_file(infolder, "s12")) S22 = read_bin(find_file(infolder, "s22")) blue = norm_data(np.abs((1 / np.sqrt(2)) * (S11 + S22))**2) red = norm_data(np.abs((1 / np.sqrt(2)) * (S11 - S22))**2) green = norm_data(np.abs((2 / np.sqrt(2)) * S12)**2) georef_file = find_file(infolder, "s11") else: raise ValueError("No matching dataset found for C4, C3, T3, or S2!") output_path = os.path.join(infolder, "PauliRGB.png") if window_size is not None: kernel = np.ones((window_size, window_size), np.float32) / (window_size * window_size) red = conv2d(red, kernel).astype(np.float32) green = conv2d(green, kernel).astype(np.float32) blue = conv2d(blue, kernel).astype(np.float32) generate_rgb_png(red, green, blue,georef_file, output_path) create_pgw(georef_file, output_path) # create_prj(georef_file, output_path) print(f"Pauli RGB image saved as {output_path}") if save_tif: output_path = os.path.join(infolder, "PauliRGB.tif") generate_rgb_tif(red, green, blue, georef_file, output_path) print(f"Pauli RGB image saved as {output_path}")