Skip to content

Commit fe940d9

Browse files
committed
Preserve float64 precision in convolve_2d (#1096)
All four backends hardcoded .astype(float32), silently truncating float64 input. Now integer inputs are promoted to float32 (avoiding overflow) while float64 inputs keep their precision. Output dtype matches the promoted input.
1 parent 443ed78 commit fe940d9

File tree

1 file changed

+15
-7
lines changed

1 file changed

+15
-7
lines changed

xrspatial/convolution.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ class cupy(object):
1717
ndarray = False
1818

1919

20+
def _promote_float(dtype):
21+
"""Return at least float32; preserve float64."""
22+
if np.issubdtype(dtype, np.floating):
23+
return dtype
24+
return np.float32
25+
26+
2027
DEFAULT_UNIT = 'meter'
2128
METER = 1
2229
FOOT = 0.3048
@@ -286,16 +293,15 @@ def custom_kernel(kernel):
286293
@jit(nopython=True, nogil=True)
287294
def _convolve_2d_numpy(data, kernel):
288295
# apply kernel to data image.
289-
# TODO: handle nan
290-
data = data.astype(np.float32)
296+
# Caller must ensure data is a float type (float32 or float64).
291297
nx = data.shape[0]
292298
ny = data.shape[1]
293299
nkx = kernel.shape[0]
294300
nky = kernel.shape[1]
295301
wkx = nkx // 2
296302
wky = nky // 2
297303

298-
out = np.zeros(data.shape, dtype=np.float32)
304+
out = np.empty_like(data)
299305
out[:] = np.nan
300306
for i in prange(wkx, nx-wkx):
301307
iimin = max(i - wkx, 0)
@@ -315,6 +321,7 @@ def _convolve_2d_numpy(data, kernel):
315321

316322

317323
def _convolve_2d_numpy_boundary(data, kernel, boundary='nan'):
324+
data = data.astype(_promote_float(data.dtype))
318325
if boundary == 'nan':
319326
return _convolve_2d_numpy(data, kernel)
320327
pad_h = kernel.shape[0] // 2
@@ -329,7 +336,7 @@ def _convolve_2d_numpy_boundary(data, kernel, boundary='nan'):
329336

330337

331338
def _convolve_2d_dask_numpy(data, kernel, boundary='nan'):
332-
data = data.astype(np.float32)
339+
data = data.astype(_promote_float(data.dtype))
333340
pad_h = kernel.shape[0] // 2
334341
pad_w = kernel.shape[1] // 2
335342
_func = partial(_convolve_2d_numpy, kernel=kernel)
@@ -393,16 +400,17 @@ def _convolve_2d_cupy(data, kernel, boundary='nan'):
393400
c1 = -pad_w if pad_w else None
394401
return result[r0:r1, c0:c1]
395402

396-
data = data.astype(cupy.float32)
397-
out = cupy.empty(data.shape, dtype='f4')
403+
fdtype = _promote_float(data.dtype)
404+
data = data.astype(fdtype)
405+
out = cupy.empty(data.shape, dtype=fdtype)
398406
out[:, :] = cupy.nan
399407
griddim, blockdim = cuda_args(data.shape)
400408
_convolve_2d_cuda[griddim, blockdim](data, kernel, cupy.asarray(out))
401409
return out
402410

403411

404412
def _convolve_2d_dask_cupy(data, kernel, boundary='nan'):
405-
data = data.astype(cupy.float32)
413+
data = data.astype(_promote_float(data.dtype))
406414
pad_h = kernel.shape[0] // 2
407415
pad_w = kernel.shape[1] // 2
408416
_func = partial(_convolve_2d_cupy, kernel=kernel)

0 commit comments

Comments
 (0)