PUTvision / qgis-plugin-deepness

Deepness is a remote sensing plugin that enables deep learning inference in QGIS
https://www.buymeacoffee.com/deepness
Apache License 2.0
106 stars 25 forks source link

Identical or nearly identical implementations for load_rlayer_from_file and save_result_img_as_tif functions in map processors #131

Open Saareem opened 10 months ago

Saareem commented 10 months ago

Not a bug but something to think: There's a lot of code in each map processor which could easily be in one place. One specific example is the load_rlayer_from_file and save_result_img_as_tif functions which appear identically or almost identically in MapProcessorRegression, MapProcessorSegmentation and MapProcessorSuperresolution. They would be rather easy to move, for example, to processing_utils with some changes and maybe additional parameters. I've actually refactored my fork so that MapProcessorSegmentation and MapProcessorSuperresolution use those functions from the processing_utils but later I realized that the MapProcessorRegression has yet another implementation which seems to be identical. _create_rlayers_from_images_for_base_extent is another example of such seemingly similar function appearing in multiple classes although there might be differences.

Would such a refactoring make sense as repetitive code is not a good idea in terms of bugs? Another alternative would be to move them to the super class, MapProcessor. Of course, some child classes would never use these functions, which could cause confusion.

The functions below are my implementations in the processing_utils. The differences are:

def save_img_as_tif(
        file_name: str, 
        img: np.ndarray, 
        crs, 
        geo_transform, 
        data_type=gdal.GDT_Float32):
    """
    As we cannot pass easily an numpy array to be displayed as raster layer, we create temporary geotif files,
    which will be loaded as layers later on. Uses QgsProcessingUtils.generateTempFilename to create a 
    uniquely named temporary directory for saving the tempory file with the name given in `file_name`.

    Partially based on example from:
    https://gis.stackexchange.com/questions/82031/gdal-python-set-projection-of-a-raster-not-working
    """
    file_path = QgsProcessingUtils.generateTempFilename(file_name)
    driver = gdal.GetDriverByName('GTiff')
    n_lines = img.shape[0]
    n_cols = img.shape[1]
    n_chanels = img.shape[2]
    grid_data = driver.Create(file_path, n_cols, n_lines, n_chanels, data_type)  # , options)
    # loop over chanels
    for i in range(1, img.shape[2]+1):
        grid_data.GetRasterBand(i).WriteArray(img[:, :, i-1])

    # crs().srsid()  - maybe we can use the ID directly - but how?
    # srs.ImportFromEPSG()
    srs = osr.SpatialReference()
    srs.SetFromUserInput(crs.authid())

    grid_data.SetProjection(srs.ExportToWkt())
    grid_data.SetGeoTransform(geo_transform)
    print(f'***** {file_path = }')
    return file_path

def load_rlayer_from_file(file_path, crs, layer_name="Temporary raster"):
    """
    Create raster layer from a tif file with a layer name
    and assign a CRS.
    """
    rlayer = QgsRasterLayer(file_path, layer_name)
    if rlayer.width() == 0:
        raise Exception("0 width - rlayer not loaded properly. Probably invalid file path?")
    rlayer.setCrs(crs)
    return rlayer