From 15945019890ea512d95e37f7f2856ad9f23cf94e Mon Sep 17 00:00:00 2001 From: Romain Hugonnet Date: Thu, 14 Nov 2024 14:58:54 -0800 Subject: [PATCH] Revert making all `raster.array` non-public (#625) --- geoutils/interface/raster_point.py | 6 ++-- geoutils/raster/array.py | 12 +++---- geoutils/raster/multiraster.py | 4 +-- geoutils/raster/sampling.py | 4 +-- tests/test_raster/test_array.py | 52 ++++++++++++++---------------- tests/test_raster/test_raster.py | 2 +- 6 files changed, 38 insertions(+), 42 deletions(-) diff --git a/geoutils/interface/raster_point.py b/geoutils/interface/raster_point.py index 594e81a8..b9127f47 100644 --- a/geoutils/interface/raster_point.py +++ b/geoutils/interface/raster_point.py @@ -12,7 +12,7 @@ import geoutils as gu from geoutils._typing import NDArrayNum -from geoutils.raster.array import _get_mask_from_array +from geoutils.raster.array import get_mask_from_array from geoutils.raster.georeferencing import _default_nodata, _xy2ij from geoutils.raster.sampling import subsample_array @@ -168,11 +168,11 @@ def _raster_to_pointcloud( if skip_nodata: if source_raster.is_loaded: if source_raster.count == 1: - self_mask = _get_mask_from_array( + self_mask = get_mask_from_array( source_raster.data ) # This is to avoid the case where the mask is just "False" else: - self_mask = _get_mask_from_array( + self_mask = get_mask_from_array( source_raster.data[data_band - 1, :, :] ) # This is to avoid the case where the mask is just "False" valid_mask = ~self_mask diff --git a/geoutils/raster/array.py b/geoutils/raster/array.py index ed32b909..e1df06c5 100644 --- a/geoutils/raster/array.py +++ b/geoutils/raster/array.py @@ -10,7 +10,7 @@ from geoutils._typing import MArrayNum, NDArrayBool, NDArrayNum -def _get_mask_from_array(array: NDArrayNum | NDArrayBool | MArrayNum) -> NDArrayBool: +def get_mask_from_array(array: NDArrayNum | NDArrayBool | MArrayNum) -> NDArrayBool: """ Return the mask of invalid values, whether array is a ndarray with NaNs or a np.ma.masked_array. @@ -22,7 +22,7 @@ def _get_mask_from_array(array: NDArrayNum | NDArrayBool | MArrayNum) -> NDArray return mask.squeeze() -def _get_array_and_mask( +def get_array_and_mask( array: NDArrayNum | MArrayNum, check_shape: bool = True, copy: bool = True ) -> tuple[NDArrayNum, NDArrayBool]: """ @@ -59,19 +59,19 @@ def _get_array_and_mask( array_data = np.array(array).squeeze() if copy else np.asarray(array).squeeze() # Get the mask of invalid pixels and set nans if it is occupied. - invalid_mask = _get_mask_from_array(array) + invalid_mask = get_mask_from_array(array) if np.any(invalid_mask): array_data[invalid_mask] = np.nan return array_data, invalid_mask -def _get_valid_extent(array: NDArrayNum | NDArrayBool | MArrayNum) -> tuple[int, ...]: +def get_valid_extent(array: NDArrayNum | NDArrayBool | MArrayNum) -> tuple[int, ...]: """ Return (rowmin, rowmax, colmin, colmax), the first/last row/column of array with valid pixels """ if not array.dtype == "bool": - valid_mask = ~_get_mask_from_array(array) + valid_mask = ~get_mask_from_array(array) else: # Not sure why Mypy is not recognizing that the type of the array can only be bool here valid_mask = array # type: ignore @@ -80,7 +80,7 @@ def _get_valid_extent(array: NDArrayNum | NDArrayBool | MArrayNum) -> tuple[int, return rows_nonzero[0], rows_nonzero[-1], cols_nonzero[0], cols_nonzero[-1] -def _get_xy_rotated(raster: gu.Raster, along_track_angle: float) -> tuple[NDArrayNum, NDArrayNum]: +def get_xy_rotated(raster: gu.Raster, along_track_angle: float) -> tuple[NDArrayNum, NDArrayNum]: """ Rotate x, y axes of image to get along- and cross-track distances. :param raster: Raster to get x,y positions from. diff --git a/geoutils/raster/multiraster.py b/geoutils/raster/multiraster.py index 73851f9b..e73d58dd 100644 --- a/geoutils/raster/multiraster.py +++ b/geoutils/raster/multiraster.py @@ -12,7 +12,7 @@ import geoutils as gu from geoutils._typing import NDArrayNum -from geoutils.raster.array import _get_array_and_mask +from geoutils.raster.array import get_array_and_mask from geoutils.raster.geotransformations import _resampling_method_from_str from geoutils.raster.raster import RasterType, _default_nodata @@ -194,7 +194,7 @@ def stack_rasters( # Optionally calculate difference if diff: diff_to_ref = (reference_raster.data - reprojected_raster.data).squeeze() - diff_to_ref, _ = _get_array_and_mask(diff_to_ref) + diff_to_ref, _ = get_array_and_mask(diff_to_ref) data.append(diff_to_ref) else: # img_data, _ = get_array_and_mask(reprojected_raster.data.squeeze()) diff --git a/geoutils/raster/sampling.py b/geoutils/raster/sampling.py index a4559403..3014b682 100644 --- a/geoutils/raster/sampling.py +++ b/geoutils/raster/sampling.py @@ -7,7 +7,7 @@ import numpy as np from geoutils._typing import MArrayNum, NDArrayNum -from geoutils.raster.array import _get_mask_from_array +from geoutils.raster.array import get_mask_from_array @overload @@ -60,7 +60,7 @@ def subsample_array( rng = np.random.default_rng(random_state) # Remove invalid values and flatten array - mask = _get_mask_from_array(array) # -> need to remove .squeeze in get_mask + mask = get_mask_from_array(array) # -> need to remove .squeeze in get_mask valids = np.argwhere(~mask.flatten()).squeeze() # Get number of points to extract diff --git a/tests/test_raster/test_array.py b/tests/test_raster/test_array.py index 1e02c612..198640db 100644 --- a/tests/test_raster/test_array.py +++ b/tests/test_raster/test_array.py @@ -9,11 +9,7 @@ import rasterio as rio import geoutils as gu -from geoutils.raster.array import ( - _get_array_and_mask, - _get_valid_extent, - _get_xy_rotated, -) +from geoutils.raster.array import get_array_and_mask, get_valid_extent, get_xy_rotated class TestArray: @@ -59,13 +55,13 @@ def test_get_array_and_mask( # Validate that incorrect shapes raise the correct error. if not check_should_pass: with pytest.raises(ValueError, match="Invalid array shape given"): - _get_array_and_mask(array, check_shape=True) + get_array_and_mask(array, check_shape=True) # Stop the test here as the failure is now validated. return # Get a copy of the array and check its shape (it should always pass at this point) - arr, _ = _get_array_and_mask(array, copy=True, check_shape=True) + arr, _ = get_array_and_mask(array, copy=True, check_shape=True) # Validate that the array is a copy assert not np.shares_memory(arr, array) @@ -82,7 +78,7 @@ def test_get_array_and_mask( warnings.simplefilter("always") # Try to create a view. - arr_view, mask = _get_array_and_mask(array, copy=False) + arr_view, mask = get_array_and_mask(array, copy=False) # If it should be possible, validate that there were no warnings. if view_should_be_possible: @@ -108,21 +104,21 @@ def test_get_valid_extent(self) -> None: # For no invalid values, the function should return the edges # For the array - assert (0, 4, 0, 4) == _get_valid_extent(arr) + assert (0, 4, 0, 4) == get_valid_extent(arr) # For the masked-array - assert (0, 4, 0, 4) == _get_valid_extent(mask_ma) + assert (0, 4, 0, 4) == get_valid_extent(mask_ma) # 1/ First column: # If we mask it in the masked array mask_ma[0, :] = np.ma.masked - assert (1, 4, 0, 4) == _get_valid_extent(mask_ma) + assert (1, 4, 0, 4) == get_valid_extent(mask_ma) # If we changed the array to NaNs arr[0, :] = np.nan - assert (1, 4, 0, 4) == _get_valid_extent(arr) + assert (1, 4, 0, 4) == get_valid_extent(arr) mask_ma.data[0, :] = np.nan mask_ma.mask = False - assert (1, 4, 0, 4) == _get_valid_extent(mask_ma) + assert (1, 4, 0, 4) == get_valid_extent(mask_ma) # 2/ First row: arr = np.ones(shape=(5, 5)) @@ -130,14 +126,14 @@ def test_get_valid_extent(self) -> None: mask_ma = np.ma.masked_array(data=arr, mask=arr_mask) # If we mask it in the masked array mask_ma[:, 0] = np.ma.masked - assert (0, 4, 1, 4) == _get_valid_extent(mask_ma) + assert (0, 4, 1, 4) == get_valid_extent(mask_ma) # If we changed the array to NaNs arr[:, 0] = np.nan - assert (0, 4, 1, 4) == _get_valid_extent(arr) + assert (0, 4, 1, 4) == get_valid_extent(arr) mask_ma.data[:, 0] = np.nan mask_ma.mask = False - assert (0, 4, 1, 4) == _get_valid_extent(mask_ma) + assert (0, 4, 1, 4) == get_valid_extent(mask_ma) # 3/ Last column: arr = np.ones(shape=(5, 5)) @@ -146,14 +142,14 @@ def test_get_valid_extent(self) -> None: # If we mask it in the masked array mask_ma[-1, :] = np.ma.masked - assert (0, 3, 0, 4) == _get_valid_extent(mask_ma) + assert (0, 3, 0, 4) == get_valid_extent(mask_ma) # If we changed the array to NaNs arr[-1, :] = np.nan - assert (0, 3, 0, 4) == _get_valid_extent(arr) + assert (0, 3, 0, 4) == get_valid_extent(arr) mask_ma.data[-1, :] = np.nan mask_ma.mask = False - assert (0, 3, 0, 4) == _get_valid_extent(mask_ma) + assert (0, 3, 0, 4) == get_valid_extent(mask_ma) # 4/ Last row: arr = np.ones(shape=(5, 5)) @@ -162,14 +158,14 @@ def test_get_valid_extent(self) -> None: # If we mask it in the masked array mask_ma[:, -1] = np.ma.masked - assert (0, 4, 0, 3) == _get_valid_extent(mask_ma) + assert (0, 4, 0, 3) == get_valid_extent(mask_ma) # If we changed the array to NaNs arr[:, -1] = np.nan - assert (0, 4, 0, 3) == _get_valid_extent(arr) + assert (0, 4, 0, 3) == get_valid_extent(arr) mask_ma.data[:, -1] = np.nan mask_ma.mask = False - assert (0, 4, 0, 3) == _get_valid_extent(mask_ma) + assert (0, 4, 0, 3) == get_valid_extent(mask_ma) def test_get_xy_rotated(self) -> None: """Check the function to rotate array.""" @@ -184,27 +180,27 @@ def test_get_xy_rotated(self) -> None: xx, yy = r1.coords(grid=True, force_offset="ll") # Rotating the coordinates 90 degrees should be the same as rotating the array - xx90, yy90 = _get_xy_rotated(r1, along_track_angle=90) + xx90, yy90 = get_xy_rotated(r1, along_track_angle=90) assert np.allclose(np.rot90(xx90), xx) assert np.allclose(np.rot90(yy90), yy) # Same for 180 degrees - xx180, yy180 = _get_xy_rotated(r1, along_track_angle=180) + xx180, yy180 = get_xy_rotated(r1, along_track_angle=180) assert np.allclose(np.rot90(xx180, k=2), xx) assert np.allclose(np.rot90(yy180, k=2), yy) # Same for 270 degrees - xx270, yy270 = _get_xy_rotated(r1, along_track_angle=270) + xx270, yy270 = get_xy_rotated(r1, along_track_angle=270) assert np.allclose(np.rot90(xx270, k=3), xx) assert np.allclose(np.rot90(yy270, k=3), yy) # 360 degrees should get us back on our feet - xx360, yy360 = _get_xy_rotated(r1, along_track_angle=360) + xx360, yy360 = get_xy_rotated(r1, along_track_angle=360) assert np.allclose(xx360, xx) assert np.allclose(yy360, yy) # Test that the values make sense for 45 degrees - xx45, yy45 = _get_xy_rotated(r1, along_track_angle=45) + xx45, yy45 = get_xy_rotated(r1, along_track_angle=45) # Should have zero on the upper left corner for xx assert xx45[0, 0] == pytest.approx(0) # Then a multiple of sqrt2 along each dimension @@ -215,4 +211,4 @@ def test_get_xy_rotated(self) -> None: # Finally, yy should be rotated by 90 assert np.allclose(np.rot90(xx45), yy45) - xx, yy = _get_xy_rotated(r1, along_track_angle=90) + xx, yy = get_xy_rotated(r1, along_track_angle=90) diff --git a/tests/test_raster/test_raster.py b/tests/test_raster/test_raster.py index 404e4745..e4586188 100644 --- a/tests/test_raster/test_raster.py +++ b/tests/test_raster/test_raster.py @@ -1009,7 +1009,7 @@ def test_copy(self, example: str) -> None: # When passing the new array as a NaN ndarray, only the valid data is equal, because masked data is NaN in one # case, and -9999 in the other - r_arr = gu.raster.array._get_array_and_mask(r)[0] + r_arr = gu.raster.array.get_array_and_mask(r)[0] r2 = r.copy(new_array=r_arr) assert np.ma.allequal(r.data, r2.data) # If a nodata value exists, and we update the NaN pixels to be that nodata value, then the two Rasters should