Skip to content

Commit

Permalink
Revert making all raster.array non-public (#625)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhugonnet authored Nov 14, 2024
1 parent c80ef6c commit 1594501
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 42 deletions.
6 changes: 3 additions & 3 deletions geoutils/interface/raster_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions geoutils/raster/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]:
"""
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions geoutils/raster/multiraster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions geoutils/raster/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
52 changes: 24 additions & 28 deletions tests/test_raster/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -108,36 +104,36 @@ 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))
arr_mask = np.zeros(shape=(5, 5), dtype=bool)
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))
Expand All @@ -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))
Expand All @@ -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."""
Expand All @@ -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
Expand All @@ -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)
2 changes: 1 addition & 1 deletion tests/test_raster/test_raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1594501

Please sign in to comment.