diff --git a/geest/core/convert_to_8bit.py b/geest/core/convert_to_8bit.py new file mode 100644 index 0000000..5b786bb --- /dev/null +++ b/geest/core/convert_to_8bit.py @@ -0,0 +1,57 @@ +import os +import processing +from qgis.core import QgsMessageLog, Qgis, QgsRasterLayer, QgsProject + + +class RasterConverter: + """ + A class to handle the conversion of rasters to 8-bit TIFFs. + """ + + def __init__(self, feedback=None): + """ + Initialize the RasterConverter with optional feedback for progress reporting. + :param feedback: Optional QgsFeedback object for reporting progress. + """ + self.feedback = feedback + + def convert_to_8bit(self, input_raster: str, output_raster: str) -> bool: + """ + Convert the input raster to an 8-bit TIFF using gdal:translate. + :param input_raster: Path to the input raster file. + :param output_raster: Path to the output 8-bit TIFF file. + :return: True if conversion is successful, False otherwise. + """ + QgsMessageLog.logMessage( + f"Converting {input_raster} to 8-bit TIFF at {output_raster}.", + tag="RasterConverter", + level=Qgis.Info, + ) + + params = { + "INPUT": input_raster, + "TARGET_CRS": None, # Use input CRS + "NODATA": -9999, + "COPY_SUBDATASETS": False, + "OPTIONS": "", + "EXTRA": "", + "DATA_TYPE": 1, # 1 = Byte (8-bit unsigned) + "OUTPUT": output_raster, + } + + try: + # Run the gdal:translate processing algorithm + processing.run("gdal:translate", params, feedback=self.feedback) + QgsMessageLog.logMessage( + f"Successfully converted {input_raster} to 8-bit TIFF.", + tag="RasterConverter", + level=Qgis.Info, + ) + return True + except Exception as e: + QgsMessageLog.logMessage( + f"Failed to convert {input_raster} to 8-bit: {str(e)}", + tag="RasterConverter", + level=Qgis.Critical, + ) + return False diff --git a/geest/core/workflows/factor_aggregation_workflow.py b/geest/core/workflows/factor_aggregation_workflow.py index e466e39..208ef72 100644 --- a/geest/core/workflows/factor_aggregation_workflow.py +++ b/geest/core/workflows/factor_aggregation_workflow.py @@ -9,6 +9,7 @@ ) from qgis.analysis import QgsRasterCalculator, QgsRasterCalculatorEntry from .workflow_base import WorkflowBase +from geest.core.convert_to_8bit import RasterConverter class FactorAggregationWorkflow(WorkflowBase): @@ -113,6 +114,16 @@ def aggregate_vrt_files(self, vrt_files: list) -> None: # Run the calculation result = calc.processCalculation() + converter = RasterConverter() + + aggregation_output_8bit = aggregation_output.replace(".tif", "_8bit.tif") + + # Convert the aggregated raster to 8-bit + converter.convert_to_8bit(aggregation_output, aggregation_output_8bit) + + if os.path.exists(aggregation_output_8bit): + os.remove(aggregation_output) + if result == 0: QgsMessageLog.logMessage( "Raster aggregation completed successfully.", @@ -121,7 +132,7 @@ def aggregate_vrt_files(self, vrt_files: list) -> None: ) # Add the aggregated raster to the map aggregated_layer = QgsRasterLayer( - aggregation_output, f"{self.indicator_name}" + aggregation_output_8bit, f"{self.indicator_name}" ) if aggregated_layer.isValid(): # Copy the style (.qml) file to the same directory as the VRT @@ -134,7 +145,9 @@ def aggregate_vrt_files(self, vrt_files: list) -> None: qml_dest_path = os.path.join( self.workflow_directory, "contextual", - os.path.basename(aggregation_output).replace(".tif", ".qml"), + os.path.basename(aggregation_output_8bit).replace( + ".tif", ".qml" + ), ) shutil.copy(qml_src_path, qml_dest_path) QgsMessageLog.logMessage( diff --git a/test/test_convert_to_8bit.py b/test/test_convert_to_8bit.py new file mode 100644 index 0000000..b942e90 --- /dev/null +++ b/test/test_convert_to_8bit.py @@ -0,0 +1,61 @@ +import os +import unittest +from qgis.core import QgsApplication, QgsRasterLayer, Qgis +from geest.core.convert_to_8bit import RasterConverter + + +class TestRasterConverter(unittest.TestCase): + + @classmethod + def setUpClass(self): + + # Define paths to input and output files + self.input_raster = os.path.join( + os.path.dirname(__file__), "test_data/rasters/raster.tif" + ) + self.output_raster = os.path.join( + os.path.dirname(__file__), "output/output_raster_8bit.tif" + ) + + def test_convert_to_8bit(self): + """ + Test the convert_to_8bit method of the RasterConverter class. + """ + # Create an instance of RasterConverter + converter = RasterConverter() + + # Ensure input file exists before running the test + self.assertTrue( + os.path.exists(self.input_raster), "Input raster file does not exist" + ) + + # Check if the input image is 32-bit using QGIS API + input_layer = QgsRasterLayer(self.input_raster, "Test Raster") + self.assertTrue(input_layer.isValid(), "Raster layer is not valid") + + # Get the raster band data type + input_provider = input_layer.dataProvider() + input_band_data_type = input_provider.dataType(1) + + self.assertEqual(input_band_data_type, Qgis.Float32, "Input raster is 32-bit") + + # Run the conversion + success = converter.convert_to_8bit(self.input_raster, self.output_raster) + + # Check if the conversion was successful + self.assertTrue(success, "Raster conversion failed") + + # Check if the output image is 8-bit using QGIS API + raster_layer = QgsRasterLayer(self.output_raster, "Test Raster") + self.assertTrue(raster_layer.isValid(), "Raster layer is not valid") + + # Get the raster band data type + provider = raster_layer.dataProvider() + band_data_type = provider.dataType(1) + + # Assert if the raster data type is 8-bit + self.assertEqual(band_data_type, Qgis.Byte, "Output raster is not 8-bit") + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_data/rasters/raster.tif b/test/test_data/rasters/raster.tif new file mode 100644 index 0000000..1ee74bd Binary files /dev/null and b/test/test_data/rasters/raster.tif differ