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:
Uses QgsProcessingUtils.generateTempFilename to create temporary files. It saves to the place where QGIS processing scripts save their temporary files. In essence, it will make a uniquely named directory in which it will save the file with the same name.
Crs, geo_transform and GDAL data type are given as parameters
It is assumed that there are channels and the caller adds those to the input array before calling. It would be easily changed so that if there are no more than 1 channel, i.e. len(.img.shape) == 2, it would be single channel raster.
Uses GDAL driver.Create to directly create the dataset, instead of driver.Create and driver.CreateCopy. GeoTiff driver in GDAL supports this.
Layer name is given as a parameter for the QgsRasterLayer. There's no need to generate unique UUIDs and strip them off unless you really want to do it. The paths of the files are unique even though the filenames are not. This also saves us the hassle of joining paths. The only downside of this approach is that if you need to add these files from the directory by hand, they appear with the same name.
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
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
andsave_result_img_as_tif
functions which appear identically or almost identically inMapProcessorRegression
,MapProcessorSegmentation
andMapProcessorSuperresolution
. They would be rather easy to move, for example, toprocessing_utils
with some changes and maybe additional parameters. I've actually refactored my fork so thatMapProcessorSegmentation
andMapProcessorSuperresolution
use those functions from theprocessing_utils
but later I realized that theMapProcessorRegression
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:QgsProcessingUtils.generateTempFilename
to create temporary files. It saves to the place where QGIS processing scripts save their temporary files. In essence, it will make a uniquely named directory in which it will save the file with the same name.driver.Create
to directly create the dataset, instead ofdriver.Create
anddriver.CreateCopy
. GeoTiff driver in GDAL supports this.