Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix scipy 1.14 compatibility and trapz usage #228

Merged
merged 8 commits into from
Jun 28, 2024
3 changes: 3 additions & 0 deletions pyspectral/blackbody.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ def blackbody_rad2temp(wavelength, radiance):
The derived temperature in Kelvin.
"""
if getattr(wavelength, "dtype", None) != radiance.dtype:
# avoid a wavelength numpy scalar upcasting radiances (ex. 32-bit to 64-bit float)
wavelength = radiance.dtype.type(wavelength)
with np.errstate(invalid='ignore'):
return PLANCK_C1 / (wavelength * np.log(PLANCK_C2 / (radiance * wavelength**5) + 1.0))

Expand Down
2 changes: 1 addition & 1 deletion pyspectral/near_infrared_reflectance.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def reflectance_from_tbs(self, sun_zenith, tb_near_ir, tb_thermal, **kwargs):
mu0 = np.cos(np.deg2rad(sunz))

# mu0 = np.where(np.less(mu0, 0.1), 0.1, mu0)
self._solar_radiance = self.solar_flux * mu0 / np.pi
self._solar_radiance = (self.solar_flux * mu0 / np.pi).astype(tb_nir.dtype)

# CO2 correction to the 3.9 radiance, only if tbs of a co2 band around
# 13.4 micron is provided:
Expand Down
10 changes: 7 additions & 3 deletions pyspectral/radiance_tb_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@
from numbers import Number

import numpy as np
from scipy import integrate

from pyspectral.blackbody import C_SPEED, H_PLANCK, K_BOLTZMANN, blackbody, blackbody_wn
from pyspectral.rsr_reader import RelativeSpectralResponse
from pyspectral.utils import BANDNAMES, WAVE_LENGTH, WAVE_NUMBER, convert2wavenumber, get_bandname_from_wavelength

try:
from scipy.integrate import trapezoid
except ImportError:
from scipy.integrate import trapz as trapezoid

Check warning on line 39 in pyspectral/radiance_tb_conversion.py

View check run for this annotation

Codecov / codecov/patch

pyspectral/radiance_tb_conversion.py#L38-L39

Added lines #L38 - L39 were not covered by tests

LOG = logging.getLogger(__name__)

BLACKBODY_FUNC = {WAVE_LENGTH: blackbody,
Expand Down Expand Up @@ -221,9 +225,9 @@

planck = self.blackbody_function(self.wavelength_or_wavenumber, tb_) * self.response
if normalized:
radiance = integrate.trapz(planck, self.wavelength_or_wavenumber) / self.rsr_integral
radiance = trapezoid(planck, self.wavelength_or_wavenumber) / self.rsr_integral
else:
radiance = integrate.trapz(planck, self.wavelength_or_wavenumber)
radiance = trapezoid(planck, self.wavelength_or_wavenumber)

return {'radiance': radiance,
'unit': unit,
Expand Down
74 changes: 33 additions & 41 deletions pyspectral/tests/test_rayleigh.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ def mocked_rsr():
yield mymock


def _create_dask_array(input_data, dtype):
return da.from_array(np.array(input_data, dtype=dtype))


class TestRayleighDask:
"""Class for testing pyspectral.rayleigh - with dask-arrays as input."""

Expand All @@ -159,51 +163,34 @@ def test_get_reflectance_dask_redband_outside_clip(self, fake_lut_hdf5):
assert isinstance(refl_corr, da.Array)

@pytest.mark.parametrize("dtype", [np.float32, np.float64])
def test_get_reflectance_dask(self, fake_lut_hdf5, dtype):
@pytest.mark.parametrize("use_dask", [False, True])
@pytest.mark.parametrize(
("input_data", "exp_result"),
[
(([67.0, 32.0], [45.0, 18.0], [150.0, 110.0], [14.0, 5.0]), TEST_RAYLEIGH_RESULT1),
(([60.0, 20.0], [49.0, 26.0], [140.0, 130.0], [12.0, 8.0]), TEST_RAYLEIGH_RESULT2),
]
)
def test_get_reflectance(self, fake_lut_hdf5, dtype, use_dask, input_data, exp_result):
"""Test getting the reflectance correction with dask inputs."""
sun_zenith = da.array([67., 32.], dtype=dtype)
sat_zenith = da.array([45., 18.], dtype=dtype)
azidiff = da.array([150., 110.], dtype=dtype)
redband_refl = da.array([14., 5.], dtype=dtype)
array_func = np.array if not use_dask else _create_dask_array
sun_zenith = array_func(input_data[0], dtype=dtype)
sat_zenith = array_func(input_data[1], dtype=dtype)
azidiff = array_func(input_data[2], dtype=dtype)
redband_refl = array_func(input_data[3], dtype=dtype)
rayl = _create_rayleigh()
with mocked_rsr():
refl_corr = rayl.get_reflectance(sun_zenith, sat_zenith, azidiff, 'ch3', redband_refl)
np.testing.assert_allclose(refl_corr, TEST_RAYLEIGH_RESULT1.astype(dtype), atol=4.0e-06)
assert isinstance(refl_corr, da.Array)
assert refl_corr.dtype == dtype # check that the dask array's dtype is equal
assert refl_corr.compute().dtype == dtype # check that the final numpy array's dtype is equal

sun_zenith = da.array([60., 20.], dtype=dtype)
sat_zenith = da.array([49., 26.], dtype=dtype)
azidiff = da.array([140., 130.], dtype=dtype)
redband_refl = da.array([12., 8.], dtype=dtype)
with mocked_rsr():
refl_corr = rayl.get_reflectance(sun_zenith, sat_zenith, azidiff, 'ch3', redband_refl)
np.testing.assert_allclose(refl_corr, TEST_RAYLEIGH_RESULT2.astype(dtype), atol=4.0e-06)
assert isinstance(refl_corr, da.Array)
assert refl_corr.dtype == dtype # check that the dask array's dtype is equal
assert refl_corr.compute().dtype == dtype # check that the final numpy array's dtype is equal

def test_get_reflectance_numpy_dask(self, fake_lut_hdf5):
"""Test getting the reflectance correction with dask inputs."""
sun_zenith = np.array([67., 32.])
sat_zenith = np.array([45., 18.])
azidiff = np.array([150., 110.])
redband_refl = np.array([14., 5.])
rayl = _create_rayleigh()
with mocked_rsr():
refl_corr = rayl.get_reflectance(sun_zenith, sat_zenith, azidiff, 'ch3', redband_refl)
np.testing.assert_allclose(refl_corr, TEST_RAYLEIGH_RESULT1)
assert isinstance(refl_corr, np.ndarray)
if use_dask:
assert isinstance(refl_corr, da.Array)
refl_corr_np = refl_corr.compute()
assert refl_corr_np.dtype == refl_corr.dtype # check that the final numpy array's dtype is equal
refl_corr = refl_corr_np

sun_zenith = np.array([60., 20.])
sat_zenith = np.array([49., 26.])
azidiff = np.array([140., 130.])
redband_refl = np.array([12., 8.])
with mocked_rsr():
refl_corr = rayl.get_reflectance(sun_zenith, sat_zenith, azidiff, 'ch3', redband_refl)
np.testing.assert_allclose(refl_corr, TEST_RAYLEIGH_RESULT2)
assert isinstance(refl_corr, np.ndarray)
np.testing.assert_allclose(refl_corr, exp_result.astype(dtype), atol=4.0e-06)
assert refl_corr.dtype == dtype # check that the dask array's dtype is equal

def test_get_reflectance_wvl_outside_range(self, fake_lut_hdf5):
"""Test getting the reflectance correction with wavelength outside correction range."""
Expand Down Expand Up @@ -347,14 +334,19 @@ def test_get_reflectance_redband_outside_clip(self, fake_lut_hdf5):
TEST_RAYLEIGH_RESULT5),
]
)
def test_get_reflectance(self, fake_lut_hdf5, sun_zenith, sat_zenith, azidiff, redband_refl, exp_result):
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
def test_get_reflectance(self, fake_lut_hdf5, sun_zenith, sat_zenith, azidiff, redband_refl, exp_result, dtype):
"""Test getting the reflectance correction."""
rayl = _create_rayleigh()
with mocked_rsr():
refl_corr = rayl.get_reflectance(
sun_zenith, sat_zenith, azidiff, 'ch3', redband_refl)
sun_zenith.astype(dtype),
sat_zenith.astype(dtype),
azidiff.astype(dtype),
'ch3',
redband_refl.astype(dtype))
assert isinstance(refl_corr, np.ndarray)
np.testing.assert_allclose(refl_corr, exp_result)
np.testing.assert_allclose(refl_corr, exp_result.astype(dtype), atol=4.0e-06)

@patch('pyspectral.rayleigh.da', None)
def test_get_reflectance_no_rsr(self, fake_lut_hdf5):
Expand Down
12 changes: 6 additions & 6 deletions pyspectral/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ def test_get_wave_range(self):
@pytest.mark.parametrize(
("input_value", "exp_except"),
[
(np.string_("hey"), False),
(np.array([np.string_("hey")]), False),
(np.array(np.string_("hey")), False),
(np.bytes_("hey"), False),
(np.array([np.bytes_("hey")]), False),
(np.array(np.bytes_("hey")), False),
("hey", False),
(np.array([np.string_("hey"), np.string_("hey")]), True),
(np.array([np.bytes_("hey"), np.bytes_("hey")]), True),
(5, True),
],
)
Expand All @@ -247,8 +247,8 @@ def test_np2str(input_value, exp_except):
[
(b"Hello", "Hello"),
("Hello", "Hello"),
(np.string_("Hello"), "Hello"),
(np.array(np.string_("Hello")), np.array(np.string_("Hello"))),
(np.bytes_("Hello"), "Hello"),
(np.array(np.bytes_("Hello")), np.array(np.bytes_("Hello"))),
]
)
def test_bytes2string(input_value, exp_result):
Expand Down
9 changes: 6 additions & 3 deletions pyspectral/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,20 +506,23 @@ def convert2str(value):


def np2str(value):
"""Convert an `numpy.string_` to str.
"""Convert an ``numpy.bytes_`` to str.

Note: ``numpy.string_`` was deprecated in numpy 2.0 in favor of
``numpy.bytes_``.

Args:
value (ndarray): scalar or 1-element numpy array to convert
Raises:
ValueError: if value is array larger than 1-element or it is not of
type `numpy.string_` or it is not a numpy array
type `numpy.bytes_` or it is not a numpy array

"""
if isinstance(value, str):
return value

if hasattr(value, 'dtype') and \
issubclass(value.dtype.type, (np.str_, np.string_, np.object_)) \
issubclass(value.dtype.type, (np.str_, np.bytes_, np.object_)) \
and value.size == 1:
value = value.item()
# python 3 - was scalar numpy array of bytes
Expand Down