polsartools.analysis.pauliRGB — PolSARtools 0.8 documentation

Skip to main content
`Ctrl`+`K`

# 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")

    @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}")