OAFuncs 0.0.98.60__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.
Files changed (46) hide show
  1. oafuncs/__init__.py +47 -0
  2. oafuncs/_data/hycom.png +0 -0
  3. oafuncs/_data/oafuncs.png +0 -0
  4. oafuncs/_script/cprogressbar.py +468 -0
  5. oafuncs/_script/data_interp.py +96 -0
  6. oafuncs/_script/email.py +48 -0
  7. oafuncs/_script/netcdf_merge.py +132 -0
  8. oafuncs/_script/netcdf_modify.py +99 -0
  9. oafuncs/_script/netcdf_write.py +482 -0
  10. oafuncs/_script/parallel.py +269 -0
  11. oafuncs/_script/parallel_bak.py +230 -0
  12. oafuncs/_script/plot_dataset.py +377 -0
  13. oafuncs/_script/process_roms.py +620 -0
  14. oafuncs/_script/replace_file_content.py +136 -0
  15. oafuncs/oa_cmap.py +336 -0
  16. oafuncs/oa_data.py +149 -0
  17. oafuncs/oa_date.py +201 -0
  18. oafuncs/oa_down/User_Agent-list.txt +5087 -0
  19. oafuncs/oa_down/__init__.py +8 -0
  20. oafuncs/oa_down/hycom_3hourly.py +1236 -0
  21. oafuncs/oa_down/idm.py +41 -0
  22. oafuncs/oa_down/literature.py +374 -0
  23. oafuncs/oa_down/read_proxy.py +93 -0
  24. oafuncs/oa_down/test_ua.py +40 -0
  25. oafuncs/oa_down/user_agent.py +17 -0
  26. oafuncs/oa_draw.py +461 -0
  27. oafuncs/oa_file.py +434 -0
  28. oafuncs/oa_geo.py +229 -0
  29. oafuncs/oa_help.py +139 -0
  30. oafuncs/oa_linux.py +193 -0
  31. oafuncs/oa_model/__init__.py +19 -0
  32. oafuncs/oa_model/roms.py +42 -0
  33. oafuncs/oa_model/wrf/__init__.py +18 -0
  34. oafuncs/oa_model/wrf/little_r.py +186 -0
  35. oafuncs/oa_nc.py +488 -0
  36. oafuncs/oa_python.py +109 -0
  37. oafuncs/oa_sign/__init__.py +6 -0
  38. oafuncs/oa_sign/meteorological.py +152 -0
  39. oafuncs/oa_sign/ocean.py +142 -0
  40. oafuncs/oa_sign/scientific.py +123 -0
  41. oafuncs/oa_tool.py +233 -0
  42. oafuncs-0.0.98.60.dist-info/METADATA +190 -0
  43. oafuncs-0.0.98.60.dist-info/RECORD +46 -0
  44. oafuncs-0.0.98.60.dist-info/WHEEL +5 -0
  45. oafuncs-0.0.98.60.dist-info/licenses/LICENSE.txt +19 -0
  46. oafuncs-0.0.98.60.dist-info/top_level.txt +1 -0
oafuncs/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+
4
+ # 会导致OAFuncs直接导入所有函数,不符合模块化设计
5
+ # from oafuncs.oa_s.oa_cmap import *
6
+ # from oafuncs.oa_s.oa_data import *
7
+ # from oafuncs.oa_s.oa_draw import *
8
+ # from oafuncs.oa_s.oa_file import *
9
+ # from oafuncs.oa_s.oa_help import *
10
+ # from oafuncs.oa_s.oa_nc import *
11
+ # from oafuncs.oa_s.oa_python import *
12
+
13
+ # ------------------- 2024-12-13 12:31:06 -------------------
14
+ # path: My_Funcs/OAFuncs/oafuncs/
15
+ from .oa_cmap import *
16
+ from .oa_data import *
17
+
18
+ # ------------------- 2024-12-13 12:31:06 -------------------
19
+ # path: My_Funcs/OAFuncs/oafuncs/oa_down/
20
+ from .oa_down import *
21
+ from .oa_draw import *
22
+ from .oa_file import *
23
+ from .oa_help import *
24
+
25
+ # ------------------- 2024-12-13 12:31:06 -------------------
26
+ # path: My_Funcs/OAFuncs/oafuncs/oa_model/
27
+ from .oa_model import *
28
+ from .oa_nc import *
29
+ from .oa_python import *
30
+
31
+ # ------------------- 2024-12-13 12:31:06 -------------------
32
+ # path: My_Funcs/OAFuncs/oafuncs/oa_sign/
33
+ from .oa_sign import *
34
+
35
+ # ------------------- 2024-12-13 12:31:06 -------------------
36
+ # path: My_Funcs/OAFuncs/oafuncs/oa_tool/
37
+ from .oa_tool import *
38
+ # ------------------- 2025-03-09 16:28:01 -------------------
39
+ # path: My_Funcs/OAFuncs/oafuncs/_script/
40
+ # from ._script import *
41
+ # ------------------- 2025-03-16 15:56:01 -------------------
42
+ from .oa_date import *
43
+ # ------------------- 2025-03-27 16:56:57 -------------------
44
+ from .oa_geo import *
45
+ # ------------------- 2025-09-04 14:08:26 -------------------
46
+ from .oa_linux import *
47
+ # ------------------- 2025-09-14 12:30:00 -------------------
Binary file
Binary file
@@ -0,0 +1,468 @@
1
+ import random
2
+ import re
3
+ import shutil
4
+ import sys
5
+ import threading
6
+ import time
7
+ import warnings
8
+ from collections import deque
9
+ from typing import Any, Callable, Deque, Iterable, List, NamedTuple, Optional, Union
10
+
11
+ import numpy as np
12
+ from oafuncs.oa_cmap import get as get_cmap
13
+
14
+ try:
15
+ # import matplotlib
16
+ from matplotlib.colors import LinearSegmentedColormap, to_hex, to_rgb
17
+ except ImportError:
18
+ raise ImportError("This module requires matplotlib. Install with: pip install matplotlib")
19
+
20
+
21
+ class ProgressSample(NamedTuple):
22
+ """进度采样点"""
23
+
24
+ timestamp: float
25
+ """采样时间戳"""
26
+ completed: float
27
+ """已完成步数"""
28
+
29
+
30
+ class Task:
31
+ """进度任务信息类"""
32
+
33
+ def __init__(
34
+ self,
35
+ description: str,
36
+ total: Optional[float],
37
+ completed: float = 0,
38
+ visible: bool = True,
39
+ color: Any = "cyan",
40
+ get_time: Callable[[], float] = time.time,
41
+ ):
42
+ self.description = description
43
+ self.total = total
44
+ self.completed = completed
45
+ self.visible = visible
46
+ self.color = color
47
+ self._get_time = get_time
48
+
49
+ self.start_time: Optional[float] = None
50
+ self.stop_time: Optional[float] = None
51
+ self.finished_time: Optional[float] = None
52
+ self.finished_speed: Optional[float] = None
53
+ self._progress: Deque[ProgressSample] = deque(maxlen=1000)
54
+
55
+ def get_time(self) -> float:
56
+ """获取当前时间"""
57
+ return self._get_time()
58
+
59
+ def start(self) -> None:
60
+ """开始任务"""
61
+ if self.start_time is None:
62
+ self.start_time = self.get_time()
63
+
64
+ def stop(self) -> None:
65
+ """停止任务"""
66
+ if self.stop_time is None:
67
+ self.stop_time = self.get_time()
68
+
69
+ @property
70
+ def started(self) -> bool:
71
+ """任务是否已开始"""
72
+ return self.start_time is not None
73
+
74
+ @property
75
+ def finished(self) -> bool:
76
+ """任务是否已完成"""
77
+ return self.finished_time is not None
78
+
79
+ @property
80
+ def percentage(self) -> float:
81
+ """完成百分比"""
82
+ if not self.total:
83
+ return 0.0
84
+ completed = (self.completed / self.total) * 100.0
85
+ return min(100.0, max(0.0, completed))
86
+
87
+ @property
88
+ def elapsed(self) -> Optional[float]:
89
+ """已用时间"""
90
+ if self.start_time is None:
91
+ return None
92
+ if self.stop_time is not None:
93
+ return self.stop_time - self.start_time
94
+ return self.get_time() - self.start_time
95
+
96
+ @property
97
+ def remaining(self) -> Optional[float]:
98
+ """剩余步数"""
99
+ if self.total is None:
100
+ return None
101
+ return self.total - self.completed
102
+
103
+ @property
104
+ def speed(self) -> Optional[float]:
105
+ """估计速度(步数/秒)"""
106
+ if self.start_time is None:
107
+ return None
108
+
109
+ progress = self._progress
110
+ if not progress:
111
+ return None
112
+
113
+ total_time = progress[-1].timestamp - progress[0].timestamp
114
+ if total_time < 0.001:
115
+ return None
116
+
117
+ iter_progress = iter(progress)
118
+ next(iter_progress)
119
+ total_completed = sum(sample.completed for sample in iter_progress)
120
+
121
+ speed = total_completed / total_time
122
+ return speed
123
+
124
+ @property
125
+ def time_remaining(self) -> Optional[float]:
126
+ """预估剩余时间"""
127
+ if self.finished:
128
+ return 0.0
129
+
130
+ speed = self.speed
131
+ if not speed:
132
+ return None
133
+
134
+ remaining = self.remaining
135
+ if remaining is None:
136
+ return None
137
+
138
+ estimate = remaining / speed
139
+ return estimate
140
+
141
+
142
+ class ColorProgressBar:
143
+ """彩色进度条,支持多种终端环境和颜色渐变效果"""
144
+
145
+ def __init__(
146
+ self,
147
+ iterable: Iterable,
148
+ description: str = "Working ...",
149
+ total: Optional[float] = None,
150
+ completed: float = 0,
151
+ color: Any = "green",
152
+ cmap: Union[str, List[str]] = None,
153
+ update_interval: float = 0.1,
154
+ bar_length: int = None,
155
+ speed_estimate_period: float = 30.0,
156
+ next_line: bool = False,
157
+ ):
158
+ self.iterable = iterable
159
+ self.description = description
160
+ self.color = color
161
+ self.cmap = cmap
162
+ self.update_interval = update_interval
163
+ self.bar_length = bar_length
164
+ self.speed_estimate_period = speed_estimate_period
165
+ self.next_line = next_line
166
+
167
+ # 线程安全锁
168
+ self._lock = threading.RLock()
169
+
170
+ # 尝试获取总数
171
+ if total is None and hasattr(iterable, "__len__"):
172
+ total = len(iterable)
173
+
174
+ # 创建任务
175
+ self.task = Task(
176
+ description=description,
177
+ total=total,
178
+ completed=completed,
179
+ color=color,
180
+ )
181
+
182
+ # 输出和渲染相关
183
+ self._file = sys.stdout
184
+ self._gradient_colors = self._generate_gradient() if cmap and self.task.total else None
185
+ self._last_update_time = 0
186
+
187
+ # 检测终端环境
188
+ self._is_terminal = hasattr(self._file, "isatty") and self._file.isatty()
189
+ self._is_jupyter = "ipykernel" in sys.modules
190
+
191
+ # 输出样式
192
+ # filled_list = ["▊", "█", "▓", "▒", "░", "#", "=", ">", "▌", "▍", "▎", "▏", "*"]
193
+ filled_list = ["█", "▓", "▒", "░", "#", "=", ">", "*"]
194
+ self.filled = random.choice(filled_list)
195
+
196
+ def _generate_gradient(self) -> Optional[List[str]]:
197
+ """生成渐变色列表(修复内置colormap支持)"""
198
+ try:
199
+ if isinstance(self.cmap, list):
200
+ cmap = LinearSegmentedColormap.from_list("custom_cmap", self.cmap)
201
+ elif hasattr(self.cmap, "__call__") and hasattr(self.cmap, "N"):
202
+ # 直接处理已经是colormap对象的情况
203
+ cmap = self.cmap
204
+ else:
205
+ cmap = get_cmap(self.cmap) # 使用oafuncs.oa_cmap.get获取内置colormap;也可获取原本自定义的colormap
206
+
207
+ return [to_hex(cmap(i)) for i in np.linspace(0, 1, int(self.task.total))]
208
+ except Exception as e:
209
+ warnings.warn(f"Colormap generation failed: {str(e)}. cmap type: {type(self.cmap)}")
210
+ return None
211
+
212
+ def _hex_to_ansi(self, hex_color: str) -> str:
213
+ """将颜色转换为ANSI真彩色代码"""
214
+ try:
215
+ rgb = [int(x * 255) for x in to_rgb(hex_color)]
216
+ return f"\033[38;2;{rgb[0]};{rgb[1]};{rgb[2]}m"
217
+ except ValueError as e:
218
+ warnings.warn(f"Invalid color value: {e}, falling back to cyan")
219
+ return "\033[96m"
220
+
221
+ def _resolve_color(self, index: int) -> str:
222
+ """解析当前应使用的颜色"""
223
+ if self._gradient_colors:
224
+ # 确保索引不超过颜色数
225
+ index = min(index, len(self._gradient_colors) - 1)
226
+ try:
227
+ return self._hex_to_ansi(self._gradient_colors[index])
228
+ except (IndexError, ValueError):
229
+ pass
230
+
231
+ return self._process_color_value(self.task.color)
232
+
233
+ def _process_color_value(self, color: Any) -> str:
234
+ """处理颜色输入格式"""
235
+ preset_map = {
236
+ "red": "\033[91m",
237
+ "green": "\033[92m",
238
+ "yellow": "\033[93m",
239
+ "cyan": "\033[96m",
240
+ "blue": "\033[94m",
241
+ "magenta": "\033[95m",
242
+ "white": "\033[97m",
243
+ }
244
+
245
+ if color in preset_map:
246
+ return preset_map[color]
247
+
248
+ try:
249
+ hex_color = to_hex(color)
250
+ return self._hex_to_ansi(hex_color)
251
+ except (ValueError, TypeError) as e:
252
+ warnings.warn(f"Color parsing failed: {e}, using cyan")
253
+ return preset_map["cyan"]
254
+
255
+ def _strip_ansi(self, text: str) -> str:
256
+ """移除所有ANSI转义序列"""
257
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
258
+ return ansi_escape.sub("", text)
259
+
260
+ def _format_bar(self, progress: float, width: int) -> str:
261
+ """格式化进度条显示"""
262
+ filled = self.filled
263
+ empty = " "
264
+ # 为其他信息保留更多空间
265
+ max_width = max(10, width - 60) # 至少保留10个字符的进度条
266
+ filled_length = int(round(max_width * progress))
267
+ return filled * filled_length + empty * (max_width - filled_length)
268
+
269
+ def update(self, advance: float = 1) -> None:
270
+ """更新进度"""
271
+ with self._lock:
272
+ current_time = time.time()
273
+ old_sample_time = current_time - self.speed_estimate_period
274
+
275
+ completed_start = self.task.completed
276
+ self.task.completed += advance
277
+ update_completed = self.task.completed - completed_start
278
+
279
+ # 更新速度采样
280
+ progress = self.task._progress
281
+
282
+ # 清理旧的采样数据
283
+ while progress and progress[0].timestamp < old_sample_time:
284
+ progress.popleft()
285
+
286
+ # 添加新采样点
287
+ if update_completed > 0:
288
+ progress.append(ProgressSample(current_time, update_completed))
289
+
290
+ # 检查是否完成
291
+ if self.task.total is not None and self.task.completed >= self.task.total and self.task.finished_time is None:
292
+ self.task.finished_time = self.task.elapsed
293
+ self.task.finished_speed = self.task.speed
294
+
295
+ def render(self) -> str:
296
+ """渲染进度条"""
297
+ with self._lock:
298
+ # 应该在锁保护下访问task对象
299
+ task = self.task
300
+
301
+ # 获取终端宽度
302
+ try:
303
+ term_width = self.bar_length or (shutil.get_terminal_size().columns if self._is_terminal else 80)
304
+ # print(f'Terminal width: {term_width}') # 调试输出
305
+ except (AttributeError, OSError):
306
+ term_width = 80 # 默认终端宽度
307
+
308
+ # 确保有效宽度不小于最低限制
309
+ # effective_width = max(15, term_width - 40)
310
+ effective_width = max(15, int(term_width * 0.6)) # 保留40个字符用于其他信息
311
+ if effective_width < 10:
312
+ warnings.warn("Terminal width is too small for proper progress bar rendering.")
313
+ effective_width = 10 # 设置最低宽度限制
314
+
315
+ # 计算进度信息
316
+ progress = task.completed / task.total if task.total else 0
317
+ index = int(task.completed)
318
+ current_color = self._resolve_color(index) if self._gradient_colors else self._resolve_color(0)
319
+ reset_code = "\033[0m"
320
+
321
+ # 计算时间和速度信息
322
+ elapsed = task.elapsed or 0
323
+ remaining = task.time_remaining
324
+ speed = task.speed or 0
325
+
326
+ # 调整时间单位
327
+ if elapsed >= 3600:
328
+ elapsed_info = f"Elapsed: {elapsed / 3600:.1f}h"
329
+ elif elapsed >= 60:
330
+ elapsed_info = f"Elapsed: {elapsed / 60:.1f}m"
331
+ else:
332
+ elapsed_info = f"Elapsed: {elapsed:.1f}s"
333
+
334
+ time_info = f"ETA: {remaining:.1f}s" if task.total and remaining and remaining > 0 else elapsed_info
335
+
336
+ # 构建进度条视觉部分
337
+ bar = self._format_bar(progress, effective_width)
338
+
339
+ # 获取当前时间
340
+ current_time = time.strftime("%H:%M:%S", time.localtime())
341
+
342
+ # 组织显示文本
343
+ count_info = f"{int(task.completed)}/{int(task.total)}" if task.total else str(int(task.completed))
344
+ percent = f"{progress:.1%}" if task.total else ""
345
+ rate_info = f"{speed:.1f}it/s" if speed else ""
346
+
347
+ # 构建完整的进度条行
348
+ line = f"{current_time} {self.description} {current_color}[{bar}]{reset_code} {count_info} {percent} [{time_info} | {rate_info}]"
349
+
350
+ # 确保不超出终端宽度
351
+ if len(self._strip_ansi(line)) > term_width:
352
+ line = line[: term_width - 3] + "..."
353
+
354
+ return line
355
+
356
+ def refresh(self) -> None:
357
+ """刷新显示进度条"""
358
+ if not self._is_terminal and not self._is_jupyter:
359
+ return
360
+
361
+ with self._lock:
362
+ line = self.render()
363
+
364
+ # 根据环境选择不同的输出方式
365
+ if self._is_jupyter:
366
+ # Jupyter环境,使用换行模式
367
+ self._file.write(f"{line}\n")
368
+ elif self._is_terminal:
369
+ # 标准终端环境,覆盖同一行
370
+ if self.next_line:
371
+ self._file.write(f"\r{line}\n")
372
+ else:
373
+ self._file.write(f"\r{line}")
374
+ else:
375
+ # 非交互式环境,仅在完成时输出
376
+ if self.task.finished:
377
+ self._file.write(f"{line}\n")
378
+
379
+ # 强制刷新输出
380
+ self._file.flush()
381
+
382
+ def __iter__(self):
383
+ """迭代器实现,支持进度显示"""
384
+ self.task.start()
385
+ self._last_update_time = time.time()
386
+
387
+ try:
388
+ # 迭代原始可迭代对象
389
+ for i, item in enumerate(self.iterable):
390
+ yield item
391
+
392
+ # 更新进度
393
+ self.update(1)
394
+
395
+ # 判断是否需要刷新显示
396
+ now = time.time()
397
+ should_refresh = (now - self._last_update_time >= self.update_interval) or (self.task.total and self.task.completed >= self.task.total)
398
+
399
+ if should_refresh:
400
+ self.refresh()
401
+ self._last_update_time = now
402
+
403
+ finally:
404
+ # 完成后进行清理
405
+ if self._is_terminal and not self._is_jupyter and not self.next_line:
406
+ self._file.write("\n")
407
+ self._file.flush()
408
+
409
+ @classmethod
410
+ def gradient_color(cls, colors: List[str], n: int) -> List[str]:
411
+ """生成渐变色列表"""
412
+ cmap = LinearSegmentedColormap.from_list("gradient", colors)
413
+ return [to_hex(cmap(i)) for i in np.linspace(0, 1, n)]
414
+
415
+
416
+ def cpbar(
417
+ iterable,
418
+ description="Working...",
419
+ total=None,
420
+ completed=0,
421
+ color="cyan",
422
+ cmap=None,
423
+ update_interval=0.1,
424
+ bar_length=None,
425
+ speed_estimate_period=30.0,
426
+ next_line=False,
427
+ ):
428
+ """便捷函数,返回 ColorProgressBar 对象"""
429
+ return ColorProgressBar(
430
+ iterable=iterable,
431
+ description=description,
432
+ total=total,
433
+ completed=completed,
434
+ color=color,
435
+ cmap=cmap,
436
+ update_interval=update_interval,
437
+ bar_length=bar_length,
438
+ speed_estimate_period=speed_estimate_period,
439
+ next_line=next_line,
440
+ )
441
+
442
+
443
+ # 验证示例
444
+ if __name__ == "__main__":
445
+ for _ in cpbar(range(20), description="Diverging:", cmap="diverging_1", next_line=True):
446
+ print("Processing...")
447
+ time.sleep(0.1)
448
+
449
+ for _ in ColorProgressBar(range(20), description="Viridis:", cmap="viridis"):
450
+ time.sleep(0.1)
451
+
452
+ # 使用自定义渐变色
453
+ for _ in ColorProgressBar(range(50), description="Custom_cmap:", cmap=["#FF0000", "#0000FF"]):
454
+ time.sleep(0.1)
455
+
456
+ # 测试无法获取长度的迭代器
457
+ def infinite_generator():
458
+ i = 0
459
+ while True:
460
+ yield i
461
+ i += 1
462
+
463
+ # 限制为20个元素,但进度条不知道总长度
464
+ gen = infinite_generator()
465
+ for i, _ in enumerate(ColorProgressBar(gen, description="Unknown_length:")):
466
+ if i >= 20:
467
+ break
468
+ time.sleep(0.1)
@@ -0,0 +1,96 @@
1
+ from typing import List, Union
2
+
3
+ import numpy as np
4
+ from scipy.interpolate import griddata
5
+
6
+ from oafuncs.oa_tool import PEx
7
+
8
+
9
+ def _interp_single_worker(*args):
10
+ """
11
+ 用于PEx并行的单slice插值worker。
12
+ 参数: data_slice, origin_points, target_points, interpolation_method, target_shape
13
+ """
14
+ data_slice, origin_points, target_points, interpolation_method, target_shape = args
15
+ # 过滤掉包含 NaN 的点
16
+ valid_mask = ~np.isnan(data_slice.ravel())
17
+ valid_data = data_slice.ravel()[valid_mask]
18
+ valid_points = origin_points[valid_mask]
19
+
20
+ if len(valid_data) < 10: # 如果有效数据太少,用均值填充
21
+ return np.full(target_shape, np.nanmean(data_slice))
22
+
23
+ # 使用有效数据进行插值
24
+ result = griddata(valid_points, valid_data, target_points, method=interpolation_method).reshape(target_shape)
25
+ # 对仍为 NaN 的点用最近邻填充
26
+ if np.isnan(result).any():
27
+ nn = griddata(valid_points, valid_data, target_points, method="nearest").reshape(target_shape)
28
+ result[np.isnan(result)] = nn[np.isnan(result)]
29
+
30
+ return result
31
+
32
+
33
+ def interp_2d_func(target_x_coordinates: Union[np.ndarray, List[float]], target_y_coordinates: Union[np.ndarray, List[float]], source_x_coordinates: Union[np.ndarray, List[float]], source_y_coordinates: Union[np.ndarray, List[float]], source_data: np.ndarray, interpolation_method: str = "cubic") -> np.ndarray:
34
+ """
35
+ Perform 2D interpolation on the last two dimensions of a multi-dimensional array.
36
+
37
+ Args:
38
+ target_x_coordinates (Union[np.ndarray, List[float]]): Target grid's x-coordinates.
39
+ target_y_coordinates (Union[np.ndarray, List[float]]): Target grid's y-coordinates.
40
+ source_x_coordinates (Union[np.ndarray, List[float]]): Original grid's x-coordinates.
41
+ source_y_coordinates (Union[np.ndarray, List[float]]): Original grid's y-coordinates.
42
+ source_data (np.ndarray): Multi-dimensional array with the last two dimensions as spatial.
43
+ interpolation_method (str, optional): Interpolation method. Defaults to "cubic".
44
+ >>> optional: 'linear', 'nearest', 'cubic', 'quintic', etc.
45
+
46
+ Returns:
47
+ np.ndarray: Interpolated data array.
48
+
49
+ Raises:
50
+ ValueError: If input shapes are invalid.
51
+
52
+ Examples:
53
+ >>> target_x_coordinates = np.array([1, 2, 3])
54
+ >>> target_y_coordinates = np.array([4, 5, 6])
55
+ >>> source_x_coordinates = np.array([7, 8, 9])
56
+ >>> source_y_coordinates = np.array([10, 11, 12])
57
+ >>> source_data = np.random.rand(3, 3)
58
+ >>> result = interp_2d_func(target_x_coordinates, target_y_coordinates, source_x_coordinates, source_y_coordinates, source_data)
59
+ >>> print(result.shape) # Expected output: (3, 3)
60
+ """
61
+ if len(target_y_coordinates.shape) == 1:
62
+ target_x_coordinates, target_y_coordinates = np.meshgrid(target_x_coordinates, target_y_coordinates)
63
+ if len(source_y_coordinates.shape) == 1:
64
+ source_x_coordinates, source_y_coordinates = np.meshgrid(source_x_coordinates, source_y_coordinates)
65
+
66
+ if source_x_coordinates.shape != source_data.shape[-2:] or source_y_coordinates.shape != source_data.shape[-2:]:
67
+ raise ValueError("[red]Shape of source_data does not match shape of source_x_coordinates or source_y_coordinates.[/red]")
68
+
69
+ target_points = np.column_stack((np.array(target_x_coordinates).ravel(), np.array(target_y_coordinates).ravel()))
70
+ origin_points = np.column_stack((np.array(source_x_coordinates).ravel(), np.array(source_y_coordinates).ravel()))
71
+
72
+ data_dims = len(source_data.shape)
73
+ # Ensure source_data is 4D for consistent processing (t, z, y, x)
74
+ if data_dims < 2:
75
+ raise ValueError(f"[red]Source data must have at least 2 dimensions, but got {data_dims}.[/red]")
76
+ elif data_dims > 4:
77
+ # Or handle cases with more than 4 dimensions if necessary
78
+ raise ValueError(f"Source data has {data_dims} dimensions, but this function currently supports only up to 4.")
79
+
80
+ # Reshape to 4D by adding leading dimensions of size 1 if needed
81
+ num_dims_to_add = 4 - data_dims
82
+ new_shape = (1,) * num_dims_to_add + source_data.shape
83
+ new_src_data = source_data.reshape(new_shape)
84
+
85
+ t, z, y, x = new_src_data.shape
86
+
87
+ params = []
88
+ target_shape = target_y_coordinates.shape
89
+ for t_index in range(t):
90
+ for z_index in range(z):
91
+ params.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
92
+
93
+ with PEx() as excutor:
94
+ result = excutor.run(_interp_single_worker, params)
95
+
96
+ return np.squeeze(np.array(result).reshape(t, z, *target_shape))
@@ -0,0 +1,48 @@
1
+ from rich import print
2
+
3
+
4
+
5
+ def _send_message(msg_from, password, msg_to, title, content):
6
+ from email.header import Header
7
+ from email.mime.multipart import MIMEMultipart
8
+ from email.mime.text import MIMEText
9
+ import smtplib
10
+ # 1. 连接邮箱服务器
11
+ con = smtplib.SMTP_SSL("smtp.qq.com", 465)
12
+
13
+ # 2. 登录邮箱
14
+ # msg_from, password = _email_info()
15
+ # con.login(msg_from, _decode_password(password))
16
+ con.login(msg_from, password)
17
+
18
+ # 3. 准备数据
19
+ # 创建邮件对象
20
+ msg = MIMEMultipart()
21
+
22
+ # 设置邮件主题
23
+ subject = Header(title, "utf-8").encode()
24
+ msg["Subject"] = subject
25
+
26
+ # 设置邮件发送者
27
+ msg["From"] = msg_from
28
+
29
+ # 设置邮件接受者
30
+ msg["To"] = msg_to
31
+
32
+ # or
33
+ # content = '发送内容'
34
+ msg.attach(MIMEText(content, "plain", "utf-8"))
35
+
36
+ # 4.发送邮件
37
+ con.sendmail(msg_from, msg_to, msg.as_string())
38
+ con.quit()
39
+
40
+ print(f"已通过{msg_from}成功向{msg_to}发送邮件!")
41
+ print("发送内容为:\n{}\n\n".format(content))
42
+
43
+
44
+
45
+
46
+
47
+ if __name__ == "__main__":
48
+ pass