From 5e6138011ca01cc08e23a9cc4fe83309fd74a37f Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 27 Aug 2024 13:53:28 -0700 Subject: [PATCH] Removed post-processing function form Layers class and moved into testing code --- city_metrix/layers/layer.py | 11 ------ city_metrix/layers/ndvi_sentinel2_gee.py | 19 ---------- .../layers_for_br_lauro_de_freitas.qgz | Bin 24185 -> 24158 bytes ....py => test_write_layers_to_qgis_files.py} | 7 +--- tests/test_layer_dimensions.py | 21 +++++++++++ tests/tools.py | 35 ++++++++++++++++++ tools/xarray_tools.py | 18 --------- 7 files changed, 58 insertions(+), 53 deletions(-) rename tests/resources/layer_dumps_for_br_lauro_de_freitas/{test_write_layers_to_geotiff.py => test_write_layers_to_qgis_files.py} (97%) create mode 100644 tests/test_layer_dimensions.py create mode 100644 tests/tools.py delete mode 100644 tools/xarray_tools.py diff --git a/city_metrix/layers/layer.py b/city_metrix/layers/layer.py index 1c40664..01ad6e4 100644 --- a/city_metrix/layers/layer.py +++ b/city_metrix/layers/layer.py @@ -37,15 +37,6 @@ def get_data(self, bbox: Tuple[float]) -> Union[xr.DataArray, gpd.GeoDataFrame]: """ ... - @abstractmethod - def prepare_for_map_rendering(self, data, **kwargs) -> Union[xr.DataArray, gpd.GeoDataFrame]: - """ - Applies the standard post-processing adjustment used for rendering of the layer - :param are specific to the layer - :return: A rioxarray-format DataArray or a GeoPandas DataFrame - """ - return data - def mask(self, *layers): """ Apply layers as masks @@ -83,7 +74,6 @@ def write(self, bbox, output_path, tile_degrees=None, **kwargs): file_names = [] for tile in tiles["geometry"]: data = self.aggregate.get_data(tile.bounds) - data = self.prepare_for_map_rendering(data, **kwargs) file_name = f"{output_path}/{uuid4()}.tif" file_names.append(file_name) @@ -91,7 +81,6 @@ def write(self, bbox, output_path, tile_degrees=None, **kwargs): write_layer(file_name, data) else: data = self.aggregate.get_data(bbox) - data = self.prepare_for_map_rendering(data, **kwargs) write_layer(output_path, data) diff --git a/city_metrix/layers/ndvi_sentinel2_gee.py b/city_metrix/layers/ndvi_sentinel2_gee.py index 8458cb2..c5b21b9 100644 --- a/city_metrix/layers/ndvi_sentinel2_gee.py +++ b/city_metrix/layers/ndvi_sentinel2_gee.py @@ -1,5 +1,4 @@ import ee -from tools.xarray_tools import convert_ratio_to_percentage from .layer import Layer, get_image_collection class NdviSentinel2(Layer): @@ -45,21 +44,3 @@ def calculate_ndvi(image): xdata = ndvi_data.to_dataarray() return xdata - - def prepare_for_map_rendering(self, data, ndvi_threshold=0.4, convert_to_percentage=True, **kwargs): - """ - Applies the standard post-processing adjustment used for rendering of NDVI including masking - to a threshold and conversion to percentage values. - :param ndvi_threshold: (float) minimum threshold for keeping values - :param convert_to_percentage: (bool) controls whether NDVI values are converted to a percentage - :return: A rioxarray-format DataArray - """ - # Remove values less than the specified threshold - if ndvi_threshold is not None: - data = data.where(data >= ndvi_threshold) - - # Convert to percentage in byte data_type - if convert_to_percentage is True: - data = convert_ratio_to_percentage(data) - - return data diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz b/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz index c0e237d2aebdf43b04800b384d412afb2eae3917..759515e6e49022f8cbd0bbc699ed843efc6440b7 100644 GIT binary patch delta 22259 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 bde0XJpq4JZ-Tv3xF+s=T7JUs0oGiS}rK4;Ck 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 diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_geotiff.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py similarity index 97% rename from tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_geotiff.py rename to tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py index c7072f5..5e0efb9 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_geotiff.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py @@ -1,8 +1,6 @@ # This code is mostly intended for manual execution # Execution configuration is specified in the conftest file - import pytest -import os from city_metrix.layers import ( Albedo, @@ -23,7 +21,7 @@ TreeCanopyHeight, TreeCover, UrbanLandUse, - WorldPop + WorldPop, Layer ) from .conftest import RUN_DUMPS, prep_output_path, verify_file_is_populated @@ -86,8 +84,7 @@ def test_write_natural_areas(target_folder, bbox_info): @pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') def test_write_ndvi_sentinel2_gee(target_folder, bbox_info): file_path = prep_output_path(target_folder, 'ndvi_sentinel2_gee.tif') - (NdviSentinel2(year=2023) - .write(bbox_info.bounds, file_path, tile_degrees=None, ndvi_threshold=0.4, convert_to_percentage=True)) + NdviSentinel2(year=2023).write(bbox_info.bounds, file_path, tile_degrees=None) assert verify_file_is_populated(file_path) @pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') diff --git a/tests/test_layer_dimensions.py b/tests/test_layer_dimensions.py new file mode 100644 index 0000000..84fa669 --- /dev/null +++ b/tests/test_layer_dimensions.py @@ -0,0 +1,21 @@ +from city_metrix.layers import NdviSentinel2 +from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 +from tests.tools import post_process_layer + +COUNTRY_CODE_FOR_BBOX = 'BRA' +BBOX = BBOX_BRA_LAURO_DE_FREITAS_1 + +def test_ndvi_dimensions(): + data = NdviSentinel2(year=2023).get_data(BBOX) + data_for_map = post_process_layer(data, value_threshold=0.4, convert_to_percentage=True) + + expected_size = 37213 + actual_size = data_for_map.size + expected_min = 0 + actual_min = data_for_map.values.min() + expected_max = 85 + actual_max = data_for_map.values.max() + + assert actual_size == expected_size + assert actual_min == expected_min + assert actual_max == expected_max diff --git a/tests/tools.py b/tests/tools.py new file mode 100644 index 0000000..99425df --- /dev/null +++ b/tests/tools.py @@ -0,0 +1,35 @@ +import numpy as np + +def post_process_layer(data, value_threshold=0.4, convert_to_percentage=True): + """ + Applies the standard post-processing adjustment used for rendering of NDVI including masking + to a threshold and conversion to percentage values. + :param value_threshold: (float) minimum threshold for keeping values + :param convert_to_percentage: (bool) controls whether NDVI values are converted to a percentage + :return: A rioxarray-format DataArray + """ + # Remove values less than the specified threshold + if value_threshold is not None: + data = data.where(data >= value_threshold) + + # Convert to percentage in byte data_type + if convert_to_percentage is True: + data = convert_ratio_to_percentage(data) + + return data + +def convert_ratio_to_percentage(data): + """ + Converts xarray variable from a ratio to a percentage + :param data: (xarray) xarray to be converted + :return: A rioxarray-format DataArray + """ + + # convert to percentage and to bytes for efficient storage + values_as_percent = np.round(data * 100).astype(np.uint8) + + # reset CRS + source_crs = data.rio.crs + values_as_percent.rio.write_crs(source_crs, inplace=True) + + return values_as_percent diff --git a/tools/xarray_tools.py b/tools/xarray_tools.py deleted file mode 100644 index 2ef773d..0000000 --- a/tools/xarray_tools.py +++ /dev/null @@ -1,18 +0,0 @@ -import numpy as np -from rasterio import uint8 - -def convert_ratio_to_percentage(data): - """ - Converts xarray variable from a ratio to a percentage - :param data: (xarray) xarray to be converted - :return: A rioxarray-format DataArray - """ - - # convert to percentage and to bytes for efficient storage - values_as_percent = np.round(data * 100).astype(uint8) - - # reset CRS - source_crs = data.rio.crs - values_as_percent.rio.write_crs(source_crs, inplace=True) - - return values_as_percent