oafuncs 0.0.98.15__py3-none-any.whl → 0.0.98.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oafuncs/_script/data_interp.py +86 -24
- oafuncs/_script/data_interp_geo.py +98 -0
- oafuncs/oa_data.py +4 -2
- {oafuncs-0.0.98.15.dist-info → oafuncs-0.0.98.17.dist-info}/METADATA +1 -1
- {oafuncs-0.0.98.15.dist-info → oafuncs-0.0.98.17.dist-info}/RECORD +8 -7
- {oafuncs-0.0.98.15.dist-info → oafuncs-0.0.98.17.dist-info}/WHEEL +0 -0
- {oafuncs-0.0.98.15.dist-info → oafuncs-0.0.98.17.dist-info}/licenses/LICENSE.txt +0 -0
- {oafuncs-0.0.98.15.dist-info → oafuncs-0.0.98.17.dist-info}/top_level.txt +0 -0
oafuncs/_script/data_interp.py
CHANGED
@@ -1,18 +1,4 @@
|
|
1
|
-
|
2
|
-
# coding=utf-8
|
3
|
-
"""
|
4
|
-
Author: Liu Kun && 16031215@qq.com
|
5
|
-
Date: 2025-04-25 16:22:52
|
6
|
-
LastEditors: Liu Kun && 16031215@qq.com
|
7
|
-
LastEditTime: 2025-04-25 16:22:52
|
8
|
-
FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\data_interp.py
|
9
|
-
Description:
|
10
|
-
EditPlatform: vscode
|
11
|
-
ComputerInfo: XPS 15 9510
|
12
|
-
SystemInfo: Windows 11
|
13
|
-
Python Version: 3.12
|
14
|
-
"""
|
15
|
-
|
1
|
+
import importlib.util
|
16
2
|
from typing import List, Union
|
17
3
|
|
18
4
|
import numpy as np
|
@@ -20,6 +6,85 @@ from scipy.interpolate import griddata
|
|
20
6
|
|
21
7
|
from oafuncs.oa_tool import PEx
|
22
8
|
|
9
|
+
# 检查 pykdtree 是否可用
|
10
|
+
_has_pykdtree = importlib.util.find_spec("pykdtree.kdtree") is not None
|
11
|
+
|
12
|
+
|
13
|
+
def _fill_nan_nearest(arr: np.ndarray) -> np.ndarray:
|
14
|
+
"""
|
15
|
+
用最近邻填充 NaN(只支持2D数组)
|
16
|
+
"""
|
17
|
+
# 基础检查:如果输入为None,直接返回None
|
18
|
+
if arr is None:
|
19
|
+
return None
|
20
|
+
|
21
|
+
# 确保是2D ndarray
|
22
|
+
arr = np.asarray(arr)
|
23
|
+
if arr.ndim != 2:
|
24
|
+
raise ValueError(f"_fill_nan_nearest 只支持2D数组,但输入的维度是 {arr.ndim}")
|
25
|
+
|
26
|
+
# 保存原始dtype并转为float
|
27
|
+
orig_dtype = arr.dtype
|
28
|
+
arr = arr.astype(float, copy=True) # 使用copy=True确保不修改原数据
|
29
|
+
|
30
|
+
# 检查是否有NaN需要填充
|
31
|
+
mask = np.isnan(arr)
|
32
|
+
if not mask.any():
|
33
|
+
return arr.copy()
|
34
|
+
|
35
|
+
try:
|
36
|
+
valid = np.array(np.where(~mask)).T
|
37
|
+
invalid = np.array(np.where(mask)).T
|
38
|
+
|
39
|
+
# 如果有效点为空,直接返回原数据
|
40
|
+
if valid.shape[0] == 0:
|
41
|
+
return arr.copy()
|
42
|
+
|
43
|
+
# 使用KDTree进行最近邻填充
|
44
|
+
if _has_pykdtree:
|
45
|
+
from pykdtree.kdtree import KDTree
|
46
|
+
|
47
|
+
tree = KDTree(valid)
|
48
|
+
_, idx = tree.query(invalid, k=1)
|
49
|
+
filled = arr.copy()
|
50
|
+
filled[tuple(invalid.T)] = arr[tuple(valid[idx.flatten()].T)]
|
51
|
+
else:
|
52
|
+
# 备用方法:使用scipy的distance_transform_edt
|
53
|
+
from scipy.ndimage import distance_transform_edt
|
54
|
+
|
55
|
+
idx = distance_transform_edt(mask, return_distances=False, return_indices=True)
|
56
|
+
filled = arr[tuple(idx)]
|
57
|
+
|
58
|
+
return filled.astype(orig_dtype)
|
59
|
+
except Exception as e:
|
60
|
+
import warnings
|
61
|
+
|
62
|
+
warnings.warn(f"Error in _fill_nan_nearest: {e}, shape={arr.shape}")
|
63
|
+
return arr.copy() # 发生异常返回原始数据
|
64
|
+
|
65
|
+
|
66
|
+
def _data_clip(data: np.ndarray, data_min, data_max) -> np.ndarray:
|
67
|
+
"""
|
68
|
+
将数据裁剪至 [data_min, data_max],超出或 NaN 用最近邻填补。
|
69
|
+
支持 1~4D。
|
70
|
+
"""
|
71
|
+
arr = np.array(data, copy=True) # 使用副本避免修改原数据
|
72
|
+
ndims = arr.ndim
|
73
|
+
if ndims != 2:
|
74
|
+
raise ValueError(f"_data_clip 只支持1~4维数组,但输入的维度是 {ndims}")
|
75
|
+
dtype = arr.dtype
|
76
|
+
|
77
|
+
# 检查是否需要裁剪
|
78
|
+
mask = np.isnan(arr) | (arr < data_min) | (arr > data_max)
|
79
|
+
if not np.any(mask):
|
80
|
+
return arr.astype(dtype)
|
81
|
+
|
82
|
+
# 将超出范围的值设为NaN
|
83
|
+
arr[mask] = np.nan
|
84
|
+
|
85
|
+
return _fill_nan_nearest(arr).astype(dtype)
|
86
|
+
|
87
|
+
|
23
88
|
|
24
89
|
def _interp_single_worker(*args):
|
25
90
|
"""
|
@@ -39,12 +104,9 @@ def _interp_single_worker(*args):
|
|
39
104
|
result = griddata(valid_points, valid_data, target_points, method=interpolation_method)
|
40
105
|
result = result.reshape(target_shape)
|
41
106
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
nan_mask = np.isnan(result)
|
46
|
-
result_nn = griddata(valid_points, valid_data, target_points[nan_mask.ravel()], method="nearest")
|
47
|
-
result.ravel()[nan_mask.ravel()] = result_nn
|
107
|
+
# 第二步:用data_clip裁剪并填充
|
108
|
+
data_min, data_max = np.nanmin(data_slice), np.nanmax(data_slice)
|
109
|
+
result = _data_clip(result, data_min, data_max)
|
48
110
|
|
49
111
|
return result
|
50
112
|
|
@@ -111,13 +173,13 @@ def interp_2d_func(
|
|
111
173
|
|
112
174
|
t, z, y, x = new_src_data.shape
|
113
175
|
|
114
|
-
|
176
|
+
params = []
|
115
177
|
target_shape = target_y_coordinates.shape
|
116
178
|
for t_index in range(t):
|
117
179
|
for z_index in range(z):
|
118
|
-
|
180
|
+
params.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
|
119
181
|
|
120
182
|
with PEx() as excutor:
|
121
|
-
result = excutor.run(_interp_single_worker,
|
183
|
+
result = excutor.run(_interp_single_worker, params)
|
122
184
|
|
123
185
|
return np.squeeze(np.array(result).reshape(t, z, *target_shape))
|
@@ -0,0 +1,98 @@
|
|
1
|
+
from typing import List, Union
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from scipy.interpolate import RectBivariateSpline
|
5
|
+
|
6
|
+
from oafuncs.oa_tool import PEx
|
7
|
+
from oafuncs.oa_data import data_clip
|
8
|
+
from oafuncs._script.data_interp import _fill_nan_nearest
|
9
|
+
|
10
|
+
|
11
|
+
def _interp_single_worker(*args):
|
12
|
+
"""
|
13
|
+
单slice插值worker,参数为(data_slice, sx, sy, tx, ty, interpolation_method, data_min, data_max)
|
14
|
+
"""
|
15
|
+
# 兼容PEx调用方式:args为tuple或list
|
16
|
+
if len(args) == 1 and isinstance(args[0], (tuple, list)):
|
17
|
+
args = args[0]
|
18
|
+
data_slice, sx, sy, tx, ty, interpolation_method, data_min, data_max = args
|
19
|
+
# 处理nan
|
20
|
+
if np.isnan(data_slice).any():
|
21
|
+
mask = np.isnan(data_slice)
|
22
|
+
if mask.any():
|
23
|
+
data_slice = _fill_nan_nearest(data_slice)
|
24
|
+
x1d = np.unique(sx[0, :])
|
25
|
+
y1d = np.unique(sy[:, 0])
|
26
|
+
if sx.shape != (len(y1d), len(x1d)) or sy.shape != (len(y1d), len(x1d)):
|
27
|
+
from scipy.interpolate import griddata
|
28
|
+
|
29
|
+
grid_points = np.column_stack((sx.ravel(), sy.ravel()))
|
30
|
+
grid_values = data_slice.ravel()
|
31
|
+
data_slice = griddata(grid_points, grid_values, (x1d[None, :], y1d[:, None]), method="linear")
|
32
|
+
if interpolation_method == "linear":
|
33
|
+
kx = ky = 1
|
34
|
+
else:
|
35
|
+
kx = ky = 3
|
36
|
+
interp_func = RectBivariateSpline(y1d, x1d, data_slice, kx=kx, ky=ky)
|
37
|
+
out = interp_func(ty[:, 0], tx[0, :])
|
38
|
+
# 优化裁剪逻辑:超出范围的点设为nan,再用fill_nan_nearest填充
|
39
|
+
arr = np.asarray(out)
|
40
|
+
arr = data_clip(arr,data_min,data_max)
|
41
|
+
return arr
|
42
|
+
|
43
|
+
|
44
|
+
def interp_2d_geo(
|
45
|
+
target_x_coordinates: Union[np.ndarray, List[float]],
|
46
|
+
target_y_coordinates: Union[np.ndarray, List[float]],
|
47
|
+
source_x_coordinates: Union[np.ndarray, List[float]],
|
48
|
+
source_y_coordinates: Union[np.ndarray, List[float]],
|
49
|
+
source_data: np.ndarray,
|
50
|
+
interpolation_method: str = "cubic",
|
51
|
+
) -> np.ndarray:
|
52
|
+
"""
|
53
|
+
更平滑的二维插值,采用RectBivariateSpline实现bicubic效果,接口与interp_2d兼容。
|
54
|
+
支持输入2D/3D/4D数据,最后两维为空间。
|
55
|
+
interpolation_method: "cubic"(默认,bicubic),"linear"(双线性)
|
56
|
+
插值后自动裁剪并用最近邻填充超限和NaN,范围取原始数据的nanmin/nanmax
|
57
|
+
"""
|
58
|
+
# 保证输入为ndarray
|
59
|
+
tx = np.asarray(target_x_coordinates)
|
60
|
+
ty = np.asarray(target_y_coordinates)
|
61
|
+
sx = np.asarray(source_x_coordinates)
|
62
|
+
sy = np.asarray(source_y_coordinates)
|
63
|
+
data = np.asarray(source_data)
|
64
|
+
|
65
|
+
if ty.ndim == 1:
|
66
|
+
tx, ty = np.meshgrid(tx, ty)
|
67
|
+
if sy.ndim == 1:
|
68
|
+
sx, sy = np.meshgrid(sx, sy)
|
69
|
+
|
70
|
+
if sx.shape != data.shape[-2:] or sy.shape != data.shape[-2:]:
|
71
|
+
raise ValueError("Shape of source_data does not match shape of source_x_coordinates or source_y_coordinates.")
|
72
|
+
|
73
|
+
data_dims = data.ndim
|
74
|
+
if data_dims < 2:
|
75
|
+
raise ValueError("Source data must have at least 2 dimensions.")
|
76
|
+
elif data_dims > 4:
|
77
|
+
raise ValueError("Source data has more than 4 dimensions, not supported.")
|
78
|
+
|
79
|
+
num_dims_to_add = 4 - data_dims
|
80
|
+
new_shape = (1,) * num_dims_to_add + data.shape
|
81
|
+
data4d = data.reshape(new_shape)
|
82
|
+
t, z, ny, nx = data4d.shape
|
83
|
+
|
84
|
+
data_min, data_max = np.nanmin(data), np.nanmax(data)
|
85
|
+
target_shape = ty.shape
|
86
|
+
|
87
|
+
# 并行参数准备
|
88
|
+
params = []
|
89
|
+
for ti in range(t):
|
90
|
+
for zi in range(z):
|
91
|
+
params.append((data4d[ti, zi], sx, sy, tx, ty, interpolation_method, data_min, data_max))
|
92
|
+
|
93
|
+
with PEx() as excutor:
|
94
|
+
result = excutor.run(_interp_single_worker, params)
|
95
|
+
|
96
|
+
result = np.array(result).reshape(t, z, *target_shape)
|
97
|
+
result = np.squeeze(result)
|
98
|
+
return result
|
oafuncs/oa_data.py
CHANGED
@@ -23,7 +23,7 @@ from rich import print
|
|
23
23
|
from scipy.interpolate import interp1d
|
24
24
|
|
25
25
|
|
26
|
-
__all__ = ["interp_along_dim", "interp_2d", "ensure_list", "mask_shapefile"]
|
26
|
+
__all__ = ["interp_along_dim", "interp_2d", "ensure_list", "mask_shapefile", "data_clip"]
|
27
27
|
|
28
28
|
|
29
29
|
def ensure_list(input_value: Any) -> List[str]:
|
@@ -114,7 +114,9 @@ def interp_along_dim(
|
|
114
114
|
return np.apply_along_axis(apply_interp_extrap, interpolation_axis, source_data)
|
115
115
|
|
116
116
|
|
117
|
-
|
117
|
+
def data_clip(data: np.ndarray, data_min: float, data_max: float) -> np.ndarray:
|
118
|
+
from ._script.data_interp import _data_clip
|
119
|
+
_data_clip(data, data_min, data_max)
|
118
120
|
|
119
121
|
|
120
122
|
def interp_2d(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
oafuncs/__init__.py,sha256=T_-VtnWWllV3Q91twT5Yt2sUapeA051QbPNnBxmg9nw,1456
|
2
2
|
oafuncs/oa_cmap.py,sha256=DimWT4Bg7uE5Lx8hSw1REp7whpsR2pFRStAwk1cowEM,11494
|
3
|
-
oafuncs/oa_data.py,sha256=
|
3
|
+
oafuncs/oa_data.py,sha256=NOp7Rmjjypb17CNjk3Ksz08R7LygCEP6K4mna9mSXa8,8626
|
4
4
|
oafuncs/oa_date.py,sha256=WhM6cyD4G3IeghjLTHhAMtlvJbA7kwQG2sHnxdTgyso,6303
|
5
5
|
oafuncs/oa_draw.py,sha256=Wj2QBgyIPpV_dxaDrH10jqj_puK9ZM9rd-si-3VrsrE,17631
|
6
6
|
oafuncs/oa_file.py,sha256=j9gXJgPOJsliu4IOUc4bc-luW4yBvQyNCEmMyDVjUwQ,16404
|
@@ -11,7 +11,8 @@ oafuncs/oa_tool.py,sha256=rpPkLqWhqMmqlCc5wjL8qMTg3gThCkSrYJckbX_0iJc,8631
|
|
11
11
|
oafuncs/_data/hycom.png,sha256=MadKs6Gyj5n9-TOu7L4atQfTXtF9dvN9w-tdU9IfygI,10945710
|
12
12
|
oafuncs/_data/oafuncs.png,sha256=o3VD7wm-kwDea5E98JqxXl04_78cBX7VcdUt7uQXGiU,3679898
|
13
13
|
oafuncs/_script/cprogressbar.py,sha256=UIgGcLFs-6IgWlITuBLaQqrpt4OAK3Mst5RlCiNfZdQ,15772
|
14
|
-
oafuncs/_script/data_interp.py,sha256=
|
14
|
+
oafuncs/_script/data_interp.py,sha256=_k8EMSiFxutrqEVTLsL4mEPE6ssYq8bzmBlksCZ9nAE,7428
|
15
|
+
oafuncs/_script/data_interp_geo.py,sha256=X89KxLYhpltWi0Sf96gIhBL3r1M5aExd_JCmgBmmvUc,3742
|
15
16
|
oafuncs/_script/email.py,sha256=lL4HGKrr524-g0xLlgs-4u7x4-u7DtgNoD9AL8XJKj4,3058
|
16
17
|
oafuncs/_script/netcdf_merge.py,sha256=9hCyxfeUHnBzs50_0v0jzVfxpMxTX4dNTo0pmsp_T6g,4226
|
17
18
|
oafuncs/_script/netcdf_modify.py,sha256=sGRUYNhfGgf9JV70rnBzw3bzuTRSXzBTL_RMDnDPeLQ,4552
|
@@ -38,8 +39,8 @@ oafuncs/oa_sign/__init__.py,sha256=QKqTFrJDFK40C5uvk48GlRRbGFzO40rgkYwu6dYxatM,5
|
|
38
39
|
oafuncs/oa_sign/meteorological.py,sha256=8091SHo2L8kl4dCFmmSH5NGVHDku5i5lSiLEG5DLnOQ,6489
|
39
40
|
oafuncs/oa_sign/ocean.py,sha256=xrW-rWD7xBWsB5PuCyEwQ1Q_RDKq2KCLz-LOONHgldU,5932
|
40
41
|
oafuncs/oa_sign/scientific.py,sha256=a4JxOBgm9vzNZKpJ_GQIQf7cokkraV5nh23HGbmTYKw,5064
|
41
|
-
oafuncs-0.0.98.
|
42
|
-
oafuncs-0.0.98.
|
43
|
-
oafuncs-0.0.98.
|
44
|
-
oafuncs-0.0.98.
|
45
|
-
oafuncs-0.0.98.
|
42
|
+
oafuncs-0.0.98.17.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
|
43
|
+
oafuncs-0.0.98.17.dist-info/METADATA,sha256=8-0Gp7bgVD7qR4sfSQtKmoCl5yV8f7YBnEROZV28oJY,4273
|
44
|
+
oafuncs-0.0.98.17.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
45
|
+
oafuncs-0.0.98.17.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
|
46
|
+
oafuncs-0.0.98.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|