From 95ac9fcd5da7665006ab755aa655f7f51e804287 Mon Sep 17 00:00:00 2001 From: Jeff Osundwa Date: Thu, 3 Oct 2024 11:32:02 +0300 Subject: [PATCH 1/4] added convert to 8bit function and test --- geest/core/convert_to_8bit.py | 57 ++++++++++++++++++++++++++++++ test/test_convert_to_8bit.py | 53 +++++++++++++++++++++++++++ test/test_data/rasters/raster.tif | Bin 0 -> 396726 bytes 3 files changed, 110 insertions(+) create mode 100644 geest/core/convert_to_8bit.py create mode 100644 test/test_convert_to_8bit.py create mode 100644 test/test_data/rasters/raster.tif 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/test/test_convert_to_8bit.py b/test/test_convert_to_8bit.py new file mode 100644 index 0000000..ac2dac7 --- /dev/null +++ b/test/test_convert_to_8bit.py @@ -0,0 +1,53 @@ +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" + ) + + # 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 + ) # Assuming we're working with the first band (1-indexed) + + # 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 0000000000000000000000000000000000000000..1ee74bd8c2e7d2f0fa81b73dcf0f0b88a1e3f186 GIT binary patch literal 396726 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 z z>t@S{_rd$t3~c#hpR;vuZ@l#x*$?h#V<6;^`;MXedt>f;`Zxmv+YE?HTN%myy>@Fo a Date: Thu, 3 Oct 2024 11:36:13 +0300 Subject: [PATCH 2/4] add 32bit assertion --- test/test_convert_to_8bit.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/test_convert_to_8bit.py b/test/test_convert_to_8bit.py index ac2dac7..b942e90 100644 --- a/test/test_convert_to_8bit.py +++ b/test/test_convert_to_8bit.py @@ -29,6 +29,16 @@ def test_convert_to_8bit(self): 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) @@ -41,9 +51,7 @@ def test_convert_to_8bit(self): # Get the raster band data type provider = raster_layer.dataProvider() - band_data_type = provider.dataType( - 1 - ) # Assuming we're working with the first band (1-indexed) + 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") From 279cd3cb202a40d0a033fb3a406fc374f7983a70 Mon Sep 17 00:00:00 2001 From: Jeff Osundwa Date: Thu, 3 Oct 2024 11:55:29 +0300 Subject: [PATCH 3/4] added 8bit conversion to factor aggregation workflow --- .../core/workflows/factor_aggregation_workflow.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/geest/core/workflows/factor_aggregation_workflow.py b/geest/core/workflows/factor_aggregation_workflow.py index e466e39..25ae6a6 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,13 @@ 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 result == 0: QgsMessageLog.logMessage( "Raster aggregation completed successfully.", @@ -121,7 +129,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 +142,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( From 76cad6c512dfbab3760183558923d221829ec037 Mon Sep 17 00:00:00 2001 From: Jeff Osundwa Date: Thu, 3 Oct 2024 12:03:12 +0300 Subject: [PATCH 4/4] rm 32bit files --- geest/core/workflows/factor_aggregation_workflow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/geest/core/workflows/factor_aggregation_workflow.py b/geest/core/workflows/factor_aggregation_workflow.py index 25ae6a6..208ef72 100644 --- a/geest/core/workflows/factor_aggregation_workflow.py +++ b/geest/core/workflows/factor_aggregation_workflow.py @@ -121,6 +121,9 @@ def aggregate_vrt_files(self, vrt_files: list) -> None: # 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.",