Skip to content

Commit

Permalink
Merge pull request #582 from djhoese/feat-cython3
Browse files Browse the repository at this point in the history
Fix Cython 3 and Numpy 2 compatibility
  • Loading branch information
djhoese authored Feb 14, 2024
2 parents 3887db6 + fa3771b commit fedb680
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 207 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
shell: bash -l {0}
run: |
python -m pip install \
-f https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com \
--index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/ \
--no-deps --pre --upgrade \
matplotlib \
numpy \
Expand All @@ -62,7 +62,8 @@ jobs:
git+https://github.com/mapbox/rasterio \
git+https://github.com/pyproj4/pyproj \
git+https://github.com/pydata/bottleneck \
git+https://github.com/pydata/xarray;
git+https://github.com/pydata/xarray \
git+https://github.com/shapely/shapely;
- name: Install pyresample
shell: bash -l {0}
Expand Down
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ repos:
- types-PyYAML
- types-requests
- type_extensions
args: ["--python-version", "3.9", "--ignore-missing-imports"]
- types-setuptools
# Typed libraries
- numpy
- pytest
args: [ --warn-unused-configs ]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
Expand Down
18 changes: 17 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools", "wheel", "numpy", "Cython", "versioneer-518"]
requires = ["setuptools", "wheel", "numpy", "Cython>=3", "versioneer-518"]
build-backend = "setuptools.build_meta"

[tool.ruff]
Expand All @@ -18,3 +18,19 @@ convention = "google"

[tool.ruff.mccabe]
max-complexity = 10

[tool.mypy]
python_version = "3.9"
# See https://github.com/python/mypy/issues/12286 for automatic multi-platform support
platform = "linux"
# platform = win32
# platform = darwin
plugins = ["numpy.typing.mypy_plugin"]
allow_untyped_decorators = false
ignore_missing_imports = true
no_implicit_optional = true
show_error_codes = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_unreachable = true
warn_unused_configs = true
2 changes: 1 addition & 1 deletion pyresample/area_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ def _round_poles(center, units, p):

def _distance_from_center_forward(
var: tuple,
center: tuple,
center: tuple | None,
p: Proj):
"""Convert distances in degrees to projection units."""
# Interprets radius and resolution as distances between latitudes/longitudes.
Expand Down
21 changes: 11 additions & 10 deletions pyresample/ewa/_fornav.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cimport cpython
cimport cython
cimport numpy

numpy.import_array()

cdef extern from "_fornav_templates.h":
ctypedef float weight_type
Expand Down Expand Up @@ -60,42 +61,42 @@ cdef extern from "_fornav_templates.h":
weight_type * wtab

cdef int initialize_weight(size_t chan_count, unsigned int weight_count, weight_type weight_min, weight_type weight_distance_max,
weight_type weight_delta_max, weight_type weight_sum_min, ewa_weight * ewaw) nogil
weight_type weight_delta_max, weight_type weight_sum_min, ewa_weight * ewaw) noexcept nogil
cdef void deinitialize_weight(ewa_weight * ewaw) nogil
cdef accum_type ** initialize_grid_accums(size_t chan_count, size_t grid_cols, size_t grid_rows) nogil
cdef weight_type ** initialize_grid_weights(size_t chan_count, size_t grid_cols, size_t grid_rows) nogil
cdef void deinitialize_grids(size_t chan_count, void ** grids) nogil
cdef accum_type ** initialize_grid_accums(size_t chan_count, size_t grid_cols, size_t grid_rows) noexcept nogil
cdef weight_type ** initialize_grid_weights(size_t chan_count, size_t grid_cols, size_t grid_rows) noexcept nogil
cdef void deinitialize_grids(size_t chan_count, void ** grids) noexcept nogil

cdef int compute_ewa_parameters[CR_TYPE](size_t swath_cols, size_t swath_rows,
CR_TYPE * uimg, CR_TYPE * vimg, ewa_weight * ewaw, ewa_parameters * ewap) nogil
CR_TYPE * uimg, CR_TYPE * vimg, ewa_weight * ewaw, ewa_parameters * ewap) noexcept nogil

cdef int compute_ewa[CR_TYPE, IMAGE_TYPE](
size_t chan_count, bint maximum_weight_mode,
size_t swath_cols, size_t swath_rows, size_t grid_cols, size_t grid_rows,
CR_TYPE * uimg, CR_TYPE * vimg,
IMAGE_TYPE ** images, IMAGE_TYPE img_fill, accum_type ** grid_accums, weight_type ** grid_weights,
ewa_weight * ewaw, ewa_parameters * ewap) nogil
ewa_weight * ewaw, ewa_parameters * ewap) noexcept nogil

cdef int compute_ewa_single[CR_TYPE, IMAGE_TYPE](
bint maximum_weight_mode,
size_t swath_cols, size_t swath_rows, size_t grid_cols, size_t grid_rows,
CR_TYPE * uimg, CR_TYPE * vimg,
IMAGE_TYPE * image, IMAGE_TYPE img_fill, accum_type * grid_accum, weight_type * grid_weight,
ewa_weight * ewaw, ewa_parameters * ewap) nogil
ewa_weight * ewaw, ewa_parameters * ewap) noexcept nogil

# For some reason cython can't deduce the type when using the template
# cdef int write_grid_image[GRID_TYPE](GRID_TYPE *output_image, GRID_TYPE fill, size_t grid_cols, size_t grid_rows,
# accum_type *grid_accum, weight_type *grid_weights,
# int maximum_weight_mode, weight_type weight_sum_min)
cdef unsigned int write_grid_image(numpy.float32_t * output_image, numpy.float32_t fill, size_t grid_cols, size_t grid_rows,
accum_type * grid_accum, weight_type * grid_weights,
int maximum_weight_mode, weight_type weight_sum_min) nogil
int maximum_weight_mode, weight_type weight_sum_min) noexcept nogil
cdef unsigned int write_grid_image(numpy.float64_t * output_image, numpy.float64_t fill, size_t grid_cols, size_t grid_rows,
accum_type * grid_accum, weight_type * grid_weights,
int maximum_weight_mode, weight_type weight_sum_min) nogil
int maximum_weight_mode, weight_type weight_sum_min) noexcept nogil
cdef unsigned int write_grid_image(numpy.int8_t * output_image, numpy.int8_t fill, size_t grid_cols, size_t grid_rows,
accum_type * grid_accum, weight_type * grid_weights,
int maximum_weight_mode, weight_type weight_sum_min) nogil
int maximum_weight_mode, weight_type weight_sum_min) noexcept nogil

ctypedef fused cr_dtype:
numpy.float32_t
Expand Down
2 changes: 2 additions & 0 deletions pyresample/ewa/_ll2cr.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ from pyproj import Proj
cimport cython
cimport numpy

numpy.import_array()

# column and rows can only be doubles for now until the PROJ.4 is linked directly so float->double casting can be done
# inside the loop
ctypedef fused cr_dtype:
Expand Down
4 changes: 2 additions & 2 deletions pyresample/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2114,8 +2114,8 @@ def update_hash(self, existing_hash: Optional[_Hash] = None) -> _Hash:
if existing_hash is None:
existing_hash = hashlib.sha1() # nosec: B324
existing_hash.update(self.crs_wkt.encode('utf-8'))
existing_hash.update(np.array(self.shape))
existing_hash.update(np.array(self.area_extent))
existing_hash.update(np.array(self.shape)) # type: ignore[arg-type]
existing_hash.update(np.array(self.area_extent)) # type: ignore[arg-type]
return existing_hash

@daskify_2in_2out
Expand Down
38 changes: 21 additions & 17 deletions pyresample/gradient/_gradient_search.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ ctypedef np.double_t DTYPE_t
cimport cython
from libc.math cimport fabs, isinf

np.import_array()

@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void nn(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) nogil:
cdef inline void nn(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) noexcept nogil:
cdef int nnl, nnp
cdef size_t z_size = res.shape[0]
cdef size_t i
Expand All @@ -53,7 +54,7 @@ cdef inline void nn(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, doub

@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void bil(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) nogil:
cdef inline void bil(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) noexcept nogil:
cdef int l_a, l_b, p_a, p_b
cdef double w_l, w_p
cdef size_t z_size = res.shape[0]
Expand Down Expand Up @@ -83,14 +84,14 @@ cdef inline void bil(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, dou

@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void indices_xy(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) nogil:
cdef inline void indices_xy(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) noexcept nogil:
cdef int nnl, nnp
cdef size_t z_size = res.shape[0]
cdef size_t i
res[1] = dl + l0
res[0] = dp + p0

ctypedef void (*FN)(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) nogil
ctypedef void (*FN)(const DTYPE_t[:, :, :] data, int l0, int p0, double dl, double dp, int lmax, int pmax, DTYPE_t[:] res) noexcept nogil

@cython.boundscheck(False)
@cython.wraparound(False)
Expand All @@ -103,7 +104,7 @@ cpdef one_step_gradient_search(const DTYPE_t [:, :, :] data,
DTYPE_t [:, :] yp,
DTYPE_t [:, :] dst_x,
DTYPE_t [:, :] dst_y,
method='bilinear'):
str method='bilinear'):
"""Gradient search, simple case variant."""
cdef FN fun
if method == 'bilinear':
Expand All @@ -121,20 +122,19 @@ cpdef one_step_gradient_search(const DTYPE_t [:, :, :] data,
# output image array --> needs to be (lines, pixels) --> y,x
image = np.full([z_size, y_size, x_size], np.nan, dtype=DTYPE)
cdef DTYPE_t [:, :, :] image_view = image
cdef size_t [:] elements = np.arange(x_size, dtype=np.uintp)
with nogil:
one_step_gradient_search_no_gil(data,
src_x, src_y,
xl, xp, yl, yp,
dst_x, dst_y,
x_size, y_size,
fun, image_view,
elements)
fun, image_view)
# return the output image
return image

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef void one_step_gradient_search_no_gil(const DTYPE_t[:, :, :] data,
const DTYPE_t[:, :] src_x,
const DTYPE_t[:, :] src_y,
Expand All @@ -147,8 +147,7 @@ cdef void one_step_gradient_search_no_gil(const DTYPE_t[:, :, :] data,
const size_t x_size,
const size_t y_size,
FN fun,
DTYPE_t[:, :, :] result_array,
size_t[:] elements) nogil:
DTYPE_t[:, :, :] result_array) noexcept nogil:

# pixel max ---> data is expected in [lines, pixels]
cdef int pmax = src_x.shape[1] - 1
Expand All @@ -163,12 +162,19 @@ cdef void one_step_gradient_search_no_gil(const DTYPE_t[:, :, :] data,
cdef int l_a, l_b, p_a, p_b
cdef size_t i, j, elt
cdef double dx, dy, d, dl, dp
cdef int col_step = -1
# number of iterations
cdef int cnt = 0
for i in range(y_size):
elements = elements[::-1]
for elt in range(x_size):
j = elements[elt]
# swap column iteration direction for every row
if col_step == -1:
j = 0
col_step = 1
else:
j = x_size - 1
col_step = -1

for _ in range(x_size):
if isinf(dst_x[i, j]):
continue
cnt = 0
Expand Down Expand Up @@ -210,7 +216,7 @@ cdef void one_step_gradient_search_no_gil(const DTYPE_t[:, :, :] data,
# increment...
l0 = int(l0 + dl)
p0 = int(p0 + dp)

j += col_step

@cython.boundscheck(False)
@cython.wraparound(False)
Expand All @@ -235,7 +241,6 @@ cpdef one_step_gradient_indices(DTYPE_t [:, :] src_x,
# output indices arrays --> needs to be (lines, pixels) --> y,x
indices = np.full([2, y_size, x_size], np.nan, dtype=DTYPE)
cdef DTYPE_t [:, :, :] indices_view = indices
cdef size_t [:] elements = np.arange(x_size, dtype=np.uintp)

# fake_data is not going to be used anyway as we just fill in the indices
cdef DTYPE_t [:, :, :] fake_data = np.full([1, 1, 1], np.nan, dtype=DTYPE)
Expand All @@ -246,6 +251,5 @@ cpdef one_step_gradient_indices(DTYPE_t [:, :] src_x,
xl, xp, yl, yp,
dst_x, dst_y,
x_size, y_size,
indices_xy, indices_view,
elements)
indices_xy, indices_view)
return indices
4 changes: 2 additions & 2 deletions pyresample/kd_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data,


def _get_empty_sample(
data: npt.ArrayLike,
data: npt.NDArray,
output_shape: tuple | list,
is_multi_channel: bool,
fill_value: float | None
Expand All @@ -671,7 +671,7 @@ def _get_empty_sample(
return np.ones(output_shape, dtype=data.dtype) * fill_value


def _validate_input_array(data: npt.ArrayLike, valid_input_index: npt.ArrayLike) -> None:
def _validate_input_array(data: npt.NDArray, valid_input_index: npt.NDArray) -> None:
if not isinstance(data, np.ndarray):
raise TypeError('data must be numpy array')
elif valid_input_index.ndim != 1:
Expand Down
Loading

0 comments on commit fedb680

Please sign in to comment.