From f57d0e02d22a514ae22ce6241ff5a90ca420a90e Mon Sep 17 00:00:00 2001 From: Claire Simpson Date: Mon, 16 Dec 2024 15:17:20 -0800 Subject: [PATCH 1/4] check for non-numeric keys in reclassify raster function; test --- src/pygeoprocessing/geoprocessing.py | 12 ++++++++++++ tests/test_geoprocessing.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/pygeoprocessing/geoprocessing.py b/src/pygeoprocessing/geoprocessing.py index 8972175e..9bfac019 100644 --- a/src/pygeoprocessing/geoprocessing.py +++ b/src/pygeoprocessing/geoprocessing.py @@ -2338,6 +2338,12 @@ def reclassify_raster( if ``values_required`` is ``True`` and a pixel value from ``base_raster_path_band`` is not a key in ``value_map``. + ValueError + - if ``value_map`` is empty + - if ``base_raster_path_band`` is formatted incorrectly + - if nodata value not set + TypeError + if there are non-numeric keys in ``value_map`` """ if len(value_map) == 0: @@ -2346,6 +2352,12 @@ def reclassify_raster( raise ValueError( "Expected a (path, band_id) tuple, instead got '%s'" % base_raster_path_band) + # raise error if there are any non-numeric keys in value_map + nonnumeric = [key for key in value_map + if not isinstance(key, (int, float, numpy.number))] + if nonnumeric: + raise TypeError(f"Non-numeric key(s) in value map: {nonnumeric}") + raster_info = get_raster_info(base_raster_path_band[0]) nodata = raster_info['nodata'][base_raster_path_band[1]-1] # If nodata was included in the value_map pop it from our lists diff --git a/tests/test_geoprocessing.py b/tests/test_geoprocessing.py index c5c6b8d2..9f2d8304 100644 --- a/tests/test_geoprocessing.py +++ b/tests/test_geoprocessing.py @@ -187,6 +187,27 @@ def test_reclassify_raster_missing_pixel_value(self): actual_message = str(cm.exception) self.assertIn(expected_message, actual_message) + def test_reclassify_raster_nonnumeric_key(self): + """PGP.geoprocessing: test reclassify raster with non-numeric key + in value_map.""" + n_pixels = 9 + pixel_matrix = numpy.ones((n_pixels, n_pixels), numpy.float32) + target_nodata = -1 + raster_path = os.path.join(self.workspace_dir, 'raster.tif') + target_path = os.path.join(self.workspace_dir, 'target.tif') + _array_to_raster( + pixel_matrix, target_nodata, raster_path) + + value_map = {1: 2, None: 3, "s": 4, numpy.nan: 5, numpy.float32(99): 6} + target_nodata = -1 + with self.assertRaises(TypeError) as e: + pygeoprocessing.reclassify_raster( + (raster_path, 1), value_map, target_path, gdal.GDT_Float32, + target_nodata, values_required=False) + expected_message = "Non-numeric key(s) in value map: [None, 's']" + actual_message = str(e.exception) + self.assertIn(expected_message, actual_message) + def test_reclassify_raster(self): """PGP.geoprocessing: test reclassify raster.""" n_pixels = 9 From b057a2351bb5004005449ff36b6b759e1fdca086 Mon Sep 17 00:00:00 2001 From: Claire Simpson Date: Tue, 17 Dec 2024 09:24:16 -0800 Subject: [PATCH 2/4] update history; remove redundant line --- HISTORY.rst | 2 ++ tests/test_geoprocessing.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index cceb6de7..e12fece9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,8 @@ Release History * Dropped support for Python 3.8. Added support for Python 3.13 and GDAL 3.9. https://github.com/natcap/pygeoprocessing/issues/415 +* Added validation to ``reclassify_raster`` to raise an error with a descriptive + message if ``value_map`` contains non-numeric keys. 2.4.6 (2024-10-15) ------------------ diff --git a/tests/test_geoprocessing.py b/tests/test_geoprocessing.py index 9f2d8304..612e5a50 100644 --- a/tests/test_geoprocessing.py +++ b/tests/test_geoprocessing.py @@ -197,9 +197,8 @@ def test_reclassify_raster_nonnumeric_key(self): target_path = os.path.join(self.workspace_dir, 'target.tif') _array_to_raster( pixel_matrix, target_nodata, raster_path) - + value_map = {1: 2, None: 3, "s": 4, numpy.nan: 5, numpy.float32(99): 6} - target_nodata = -1 with self.assertRaises(TypeError) as e: pygeoprocessing.reclassify_raster( (raster_path, 1), value_map, target_path, gdal.GDT_Float32, From bcc63c25cf60286d9ac48a50be94f117a0689b64 Mon Sep 17 00:00:00 2001 From: Claire Simpson Date: Tue, 17 Dec 2024 09:26:29 -0800 Subject: [PATCH 3/4] updated history --- HISTORY.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e12fece9..194627fd 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,8 +5,8 @@ Release History * Dropped support for Python 3.8. Added support for Python 3.13 and GDAL 3.9. https://github.com/natcap/pygeoprocessing/issues/415 -* Added validation to ``reclassify_raster`` to raise an error with a descriptive - message if ``value_map`` contains non-numeric keys. +* Added validation to ``reclassify_raster`` to raise a ``TypeError`` with a + descriptive message if ``value_map`` contains non-numeric keys. 2.4.6 (2024-10-15) ------------------ From 47cafc458ae95f432915e2bb041864b643d09a6a Mon Sep 17 00:00:00 2001 From: Claire Simpson Date: Tue, 17 Dec 2024 13:23:03 -0800 Subject: [PATCH 4/4] removed redundant target_nodata specifications --- tests/test_geoprocessing.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_geoprocessing.py b/tests/test_geoprocessing.py index 612e5a50..1dd30d61 100644 --- a/tests/test_geoprocessing.py +++ b/tests/test_geoprocessing.py @@ -177,7 +177,6 @@ def test_reclassify_raster_missing_pixel_value(self): value_map = { test_value: 100, } - target_nodata = -1 with self.assertRaises( pygeoprocessing.ReclassificationMissingValuesError) as cm: pygeoprocessing.reclassify_raster( @@ -222,7 +221,6 @@ def test_reclassify_raster(self): value_map = { test_value: 100, } - target_nodata = -1 pygeoprocessing.reclassify_raster( (raster_path, 1), value_map, target_path, gdal.GDT_Float32, target_nodata, values_required=True) @@ -245,7 +243,6 @@ def test_reclassify_raster_no_raster_path_band(self): value_map = { test_value: 100, } - target_nodata = -1 # we expect a value error because we didn't pass a (path, band) # for the first argument with self.assertRaises(ValueError): @@ -267,7 +264,6 @@ def test_reclassify_raster_empty_value_map(self): empty_value_map = { } - target_nodata = -1 with self.assertRaises(ValueError): pygeoprocessing.reclassify_raster( (raster_path, 1), empty_value_map, target_path, @@ -2118,7 +2114,6 @@ def test_align_and_resize_raster_stack_int_with_vectors(self): pixel_a_matrix, target_nodata, base_a_path) pixel_b_matrix = numpy.ones((15, 15), numpy.int16) - target_nodata = -1 base_b_path = os.path.join(self.workspace_dir, 'base_b.tif') _array_to_raster( pixel_b_matrix, target_nodata, base_b_path) @@ -2225,7 +2220,6 @@ def test_align_and_resize_raster_stack_no_overlap(self): pixel_a_matrix, target_nodata, base_a_path, origin=[-10*30, 10*30]) pixel_b_matrix = numpy.ones((15, 15), numpy.int16) - target_nodata = -1 base_b_path = os.path.join(self.workspace_dir, 'base_b.tif') _array_to_raster(pixel_b_matrix, target_nodata, base_b_path) @@ -2268,7 +2262,6 @@ def test_align_and_resize_raster_stack_union(self): pixel_a_matrix, target_nodata, base_a_path, pixel_size=(30, -30)) pixel_b_matrix = numpy.ones((10, 10), numpy.int16) - target_nodata = -1 base_b_path = os.path.join(self.workspace_dir, 'base_b.tif') _array_to_raster( pixel_b_matrix, target_nodata, base_b_path, pixel_size=(60, -60)) @@ -2308,7 +2301,6 @@ def test_align_and_resize_raster_stack_bb(self): pixel_a_matrix, target_nodata, base_a_path, pixel_size=(30, -30)) pixel_b_matrix = numpy.ones((10, 10), numpy.int16) - target_nodata = -1 base_b_path = os.path.join(self.workspace_dir, 'base_b.tif') _array_to_raster( pixel_b_matrix, target_nodata, base_b_path, pixel_size=(30, -30))