oafuncs 0.0.98.15__py3-none-any.whl → 0.0.98.16__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.
@@ -1,24 +1,52 @@
1
- #!/usr/bin/env python
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
5
+ from oafuncs.oa_tool import PEx
19
6
  from scipy.interpolate import griddata
20
7
 
21
- from oafuncs.oa_tool import PEx
8
+ _has_pykdtree = importlib.util.find_spec("pykdtree.kdtree") is not None
9
+
10
+
11
+ def _fill_nan_nearest(arr):
12
+ """用最近邻插值填充 NaN,优先用pykdtree加速"""
13
+ mask = np.isnan(arr)
14
+ if not mask.any():
15
+ return arr
16
+ if _has_pykdtree:
17
+ from pykdtree.kdtree import KDTree
18
+
19
+ valid_idx = np.array(np.where(~mask)).T
20
+ nan_idx = np.array(np.where(mask)).T
21
+ if len(valid_idx) == 0:
22
+ # 全是nan,直接返回
23
+ return arr
24
+ tree = KDTree(valid_idx)
25
+ dist, idx = tree.query(nan_idx, k=1)
26
+ filled = arr.copy()
27
+ # idx shape: (n_nan, 1),valid_idx shape: (n_valid, ndim)
28
+ # valid_idx[idx].T shape: (ndim, n_nan)
29
+ filled[tuple(nan_idx.T)] = arr[tuple(valid_idx[idx.flatten()].T)]
30
+ return filled
31
+ else:
32
+ from scipy.ndimage import distance_transform_edt
33
+
34
+ idx = distance_transform_edt(mask, return_distances=False, return_indices=True)
35
+ return arr[tuple(idx)]
36
+
37
+
38
+ def _data_clip(data, data_min, data_max):
39
+ """
40
+ 对data进行范围裁剪,超出范围的点设为nan,并用fill_nan_nearest填充,最后再次填充极端nan。
41
+ """
42
+ arr = np.asarray(data)
43
+ mask = np.isnan(arr) | (arr < data_min) | (arr > data_max)
44
+ if np.any(mask):
45
+ arr = np.where(mask, np.nan, arr)
46
+ arr = _fill_nan_nearest(arr)
47
+ if np.any(np.isnan(arr)):
48
+ arr = _fill_nan_nearest(arr)
49
+ return arr
22
50
 
23
51
 
24
52
  def _interp_single_worker(*args):
@@ -39,12 +67,9 @@ def _interp_single_worker(*args):
39
67
  result = griddata(valid_points, valid_data, target_points, method=interpolation_method)
40
68
  result = result.reshape(target_shape)
41
69
 
42
- # 检查插值结果中是否仍有 NaN,如果有,用最近邻插值填充
43
- if np.any(np.isnan(result)):
44
- # 使用最近邻方法填充剩余的 NaN
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
70
+ # 第二步:用data_clip裁剪并填充
71
+ data_min, data_max = np.nanmin(data_slice), np.nanmax(data_slice)
72
+ result = _data_clip(result, data_min, data_max)
48
73
 
49
74
  return result
50
75
 
@@ -111,13 +136,13 @@ def interp_2d_func(
111
136
 
112
137
  t, z, y, x = new_src_data.shape
113
138
 
114
- paras = []
139
+ params = []
115
140
  target_shape = target_y_coordinates.shape
116
141
  for t_index in range(t):
117
142
  for z_index in range(z):
118
- paras.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
143
+ params.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
119
144
 
120
145
  with PEx() as excutor:
121
- result = excutor.run(_interp_single_worker, paras)
146
+ result = excutor.run(_interp_single_worker, params)
122
147
 
123
148
  return np.squeeze(np.array(result).reshape(t, z, *target_shape))
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """
4
+ Author: Liu Kun && 16031215@qq.com
5
+ Date: 2025-04-26 11:54:21
6
+ LastEditors: Liu Kun && 16031215@qq.com
7
+ LastEditTime: 2025-04-26 11:54:22
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\data_interp_geo.py
9
+ Description:
10
+ EditPlatform: vscode
11
+ ComputerInfo: XPS 15 9510
12
+ SystemInfo: Windows 11
13
+ Python Version: 3.12
14
+ """
15
+
16
+ import importlib.util
17
+ from typing import List, Union
18
+
19
+ import numpy as np
20
+ from scipy.interpolate import RectBivariateSpline
21
+
22
+ from oafuncs.oa_tool import PEx
23
+
24
+ _has_pykdtree = importlib.util.find_spec("pykdtree.kdtree") is not None
25
+
26
+
27
+ def fill_nan_nearest(arr):
28
+ """用最近邻插值填充 NaN,优先用pykdtree加速"""
29
+ mask = np.isnan(arr)
30
+ if not mask.any():
31
+ return arr
32
+ if _has_pykdtree:
33
+ from pykdtree.kdtree import KDTree
34
+
35
+ valid_idx = np.array(np.where(~mask)).T
36
+ nan_idx = np.array(np.where(mask)).T
37
+ if len(valid_idx) == 0:
38
+ # 全是nan,直接返回
39
+ return arr
40
+ tree = KDTree(valid_idx)
41
+ dist, idx = tree.query(nan_idx, k=1)
42
+ filled = arr.copy()
43
+ # idx shape: (n_nan, 1),valid_idx shape: (n_valid, ndim)
44
+ # valid_idx[idx].T shape: (ndim, n_nan)
45
+ filled[tuple(nan_idx.T)] = arr[tuple(valid_idx[idx.flatten()].T)]
46
+ return filled
47
+ else:
48
+ from scipy.ndimage import distance_transform_edt
49
+
50
+ idx = distance_transform_edt(mask, return_distances=False, return_indices=True)
51
+ return arr[tuple(idx)]
52
+
53
+
54
+ def _interp_single_worker(*args):
55
+ """
56
+ 单slice插值worker,参数为(data_slice, sx, sy, tx, ty, interpolation_method, data_min, data_max)
57
+ """
58
+ # 兼容PEx调用方式:args为tuple或list
59
+ if len(args) == 1 and isinstance(args[0], (tuple, list)):
60
+ args = args[0]
61
+ data_slice, sx, sy, tx, ty, interpolation_method, data_min, data_max = args
62
+ # 处理nan
63
+ if np.isnan(data_slice).any():
64
+ mask = np.isnan(data_slice)
65
+ if mask.any():
66
+ data_slice = fill_nan_nearest(data_slice)
67
+ x1d = np.unique(sx[0, :])
68
+ y1d = np.unique(sy[:, 0])
69
+ if sx.shape != (len(y1d), len(x1d)) or sy.shape != (len(y1d), len(x1d)):
70
+ from scipy.interpolate import griddata
71
+
72
+ grid_points = np.column_stack((sx.ravel(), sy.ravel()))
73
+ grid_values = data_slice.ravel()
74
+ data_slice = griddata(grid_points, grid_values, (x1d[None, :], y1d[:, None]), method="linear")
75
+ if interpolation_method == "linear":
76
+ kx = ky = 1
77
+ else:
78
+ kx = ky = 3
79
+ interp_func = RectBivariateSpline(y1d, x1d, data_slice, kx=kx, ky=ky)
80
+ out = interp_func(ty[:, 0], tx[0, :])
81
+ # 优化裁剪逻辑:超出范围的点设为nan,再用fill_nan_nearest填充
82
+ arr = np.asarray(out)
83
+ mask = np.isnan(arr) | (arr < data_min) | (arr > data_max)
84
+ if np.any(mask):
85
+ arr = np.where(mask, np.nan, arr)
86
+ arr = fill_nan_nearest(arr)
87
+ # 最后再填充nan(极端情况)
88
+ if np.any(np.isnan(arr)):
89
+ arr = fill_nan_nearest(arr)
90
+ return arr
91
+
92
+
93
+ def interp_2d_geo(
94
+ target_x_coordinates: Union[np.ndarray, List[float]],
95
+ target_y_coordinates: Union[np.ndarray, List[float]],
96
+ source_x_coordinates: Union[np.ndarray, List[float]],
97
+ source_y_coordinates: Union[np.ndarray, List[float]],
98
+ source_data: np.ndarray,
99
+ interpolation_method: str = "cubic",
100
+ ) -> np.ndarray:
101
+ """
102
+ 更平滑的二维插值,采用RectBivariateSpline实现bicubic效果,接口与interp_2d兼容。
103
+ 支持输入2D/3D/4D数据,最后两维为空间。
104
+ interpolation_method: "cubic"(默认,bicubic),"linear"(双线性)
105
+ 插值后自动裁剪并用最近邻填充超限和NaN,范围取原始数据的nanmin/nanmax
106
+ """
107
+ # 保证输入为ndarray
108
+ tx = np.asarray(target_x_coordinates)
109
+ ty = np.asarray(target_y_coordinates)
110
+ sx = np.asarray(source_x_coordinates)
111
+ sy = np.asarray(source_y_coordinates)
112
+ data = np.asarray(source_data)
113
+
114
+ if ty.ndim == 1:
115
+ tx, ty = np.meshgrid(tx, ty)
116
+ if sy.ndim == 1:
117
+ sx, sy = np.meshgrid(sx, sy)
118
+
119
+ if sx.shape != data.shape[-2:] or sy.shape != data.shape[-2:]:
120
+ raise ValueError("Shape of source_data does not match shape of source_x_coordinates or source_y_coordinates.")
121
+
122
+ data_dims = data.ndim
123
+ if data_dims < 2:
124
+ raise ValueError("Source data must have at least 2 dimensions.")
125
+ elif data_dims > 4:
126
+ raise ValueError("Source data has more than 4 dimensions, not supported.")
127
+
128
+ num_dims_to_add = 4 - data_dims
129
+ new_shape = (1,) * num_dims_to_add + data.shape
130
+ data4d = data.reshape(new_shape)
131
+ t, z, ny, nx = data4d.shape
132
+
133
+ data_min, data_max = np.nanmin(data), np.nanmax(data)
134
+ target_shape = ty.shape
135
+
136
+ # 并行参数准备
137
+ params = []
138
+ for ti in range(t):
139
+ for zi in range(z):
140
+ params.append((data4d[ti, zi], sx, sy, tx, ty, interpolation_method, data_min, data_max))
141
+
142
+ with PEx() as excutor:
143
+ result = excutor.run(_interp_single_worker, params)
144
+
145
+ result = np.array(result).reshape(t, z, *target_shape)
146
+ result = np.squeeze(result)
147
+ 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
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.15
3
+ Version: 0.0.98.16
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -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=hngaxAi_r6PsHXzSeT3DMY_QdChWjuBMPOZNFvWU388,8442
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=70U-Jsoxd5g-7dEQt4IaDRuRV-M_1lEKMGOci86vSVE,5431
14
+ oafuncs/_script/data_interp.py,sha256=W4kYZmkZxWJqKfZVdQOd8jUv0y11t6k8tsukRQt670I,6373
15
+ oafuncs/_script/data_interp_geo.py,sha256=Fv-l8MeKhx6e7UVtzk2b6Pb2Cfcg-RNnMFmkcd0ng64,5296
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.15.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
42
- oafuncs-0.0.98.15.dist-info/METADATA,sha256=jTAHHAY0xOxy2z13wYD4x4JOOSfsUxrz1WVyzXNfi9o,4273
43
- oafuncs-0.0.98.15.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
44
- oafuncs-0.0.98.15.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
45
- oafuncs-0.0.98.15.dist-info/RECORD,,
42
+ oafuncs-0.0.98.16.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
43
+ oafuncs-0.0.98.16.dist-info/METADATA,sha256=yPzl7v8ASdNRSjiXasAGuxPKA9rM_zCIQBQHfmz4I0k,4273
44
+ oafuncs-0.0.98.16.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
45
+ oafuncs-0.0.98.16.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
46
+ oafuncs-0.0.98.16.dist-info/RECORD,,