oafuncs 0.0.98.31__py3-none-any.whl → 0.0.98.32__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/cprogressbar.py +3 -1
- oafuncs/_script/email.py +0 -19
- oafuncs/oa_cmap.py +7 -0
- oafuncs/oa_date.py +58 -54
- oafuncs/oa_down/hycom_3hourly.py +2 -2
- oafuncs/oa_draw.py +221 -224
- oafuncs/oa_file.py +22 -10
- {oafuncs-0.0.98.31.dist-info → oafuncs-0.0.98.32.dist-info}/METADATA +1 -1
- {oafuncs-0.0.98.31.dist-info → oafuncs-0.0.98.32.dist-info}/RECORD +12 -12
- {oafuncs-0.0.98.31.dist-info → oafuncs-0.0.98.32.dist-info}/WHEEL +1 -1
- {oafuncs-0.0.98.31.dist-info → oafuncs-0.0.98.32.dist-info}/licenses/LICENSE.txt +0 -0
- {oafuncs-0.0.98.31.dist-info → oafuncs-0.0.98.32.dist-info}/top_level.txt +0 -0
oafuncs/_script/cprogressbar.py
CHANGED
@@ -300,11 +300,13 @@ class ColorProgressBar:
|
|
300
300
|
# 获取终端宽度
|
301
301
|
try:
|
302
302
|
term_width = self.bar_length or (shutil.get_terminal_size().columns if self._is_terminal else 80)
|
303
|
+
# print(f'Terminal width: {term_width}') # 调试输出
|
303
304
|
except (AttributeError, OSError):
|
304
305
|
term_width = 80 # 默认终端宽度
|
305
306
|
|
306
307
|
# 确保有效宽度不小于最低限制
|
307
|
-
effective_width = max(15, term_width - 40)
|
308
|
+
# effective_width = max(15, term_width - 40)
|
309
|
+
effective_width = max(15, int(term_width * 0.6)) # 保留40个字符用于其他信息
|
308
310
|
if effective_width < 10:
|
309
311
|
warnings.warn("Terminal width is too small for proper progress bar rendering.")
|
310
312
|
effective_width = 10 # 设置最低宽度限制
|
oafuncs/_script/email.py
CHANGED
@@ -1,21 +1,3 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
# coding=utf-8
|
3
|
-
"""
|
4
|
-
Author: Liu Kun && 16031215@qq.com
|
5
|
-
Date: 2025-04-04 20:21:59
|
6
|
-
LastEditors: Liu Kun && 16031215@qq.com
|
7
|
-
LastEditTime: 2025-04-04 20:21:59
|
8
|
-
FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\email.py
|
9
|
-
Description:
|
10
|
-
EditPlatform: vscode
|
11
|
-
ComputerInfo: XPS 15 9510
|
12
|
-
SystemInfo: Windows 11
|
13
|
-
Python Version: 3.12
|
14
|
-
"""
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
1
|
import random
|
20
2
|
import smtplib
|
21
3
|
from email.header import Header
|
@@ -26,7 +8,6 @@ from rich import print
|
|
26
8
|
|
27
9
|
__all__ = ["send"]
|
28
10
|
|
29
|
-
|
30
11
|
def _email_info():
|
31
12
|
email_dict = {
|
32
13
|
"liukun0312@vip.qq.com": [4, 13, -10, 2, -10, 4, -7, -8, 8, -1, 3, -2, -11, -6, -9, -7],
|
oafuncs/oa_cmap.py
CHANGED
@@ -258,10 +258,17 @@ def get(colormap_name: Optional[str] = None, show_available: bool = False) -> Op
|
|
258
258
|
"cool_1": ["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC"],
|
259
259
|
"warm_1": ["#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"],
|
260
260
|
# ---------------------------------------------------------------------------
|
261
|
+
# suitable for diverging color maps, wind vector
|
261
262
|
"diverging_2": ["#4A235A", "#1F618D", "#1ABC9C", "#A9DFBF", "#F2F3F4", "#FDEBD0", "#F5B041", "#E74C3C", "#78281F"],
|
262
263
|
"cool_2": ["#4A235A", "#1F618D", "#1ABC9C", "#A9DFBF", "#F2F3F4"],
|
263
264
|
"warm_2": ["#F2F3F4", "#FDEBD0", "#F5B041", "#E74C3C", "#78281F"],
|
264
265
|
# ---------------------------------------------------------------------------
|
266
|
+
"diverging_3": ["#1a66f2", "#5DADE2", "#48C9B0", "#A9DFBF", "#F2F3F4", "#FFDAB9", "#FF9E80", "#FF6F61", "#FF1744"],
|
267
|
+
"cool_3": ["#1a66f2", "#5DADE2", "#48C9B0", "#A9DFBF", "#F2F3F4"],
|
268
|
+
"warm_3": ["#F2F3F4", "#FFDAB9", "#FF9E80", "#FF6F61", "#FF1744"],
|
269
|
+
# ---------------------------------------------------------------------------
|
270
|
+
"diverging_4": ["#5DADE2", "#A2D9F7", "#D6EAF8", "#F2F3F4", "#FADBD8", "#F1948A", "#E74C3C"],
|
271
|
+
# ----------------------------------------------------------------------------
|
265
272
|
"colorful_1": ["#6d00db", "#9800cb", "#F2003C", "#ff4500", "#ff7f00", "#FE28A2", "#FFC0CB", "#DDA0DD", "#40E0D0", "#1a66f2", "#00f7fb", "#8fff88", "#E3FF00"],
|
266
273
|
}
|
267
274
|
|
oafuncs/oa_date.py
CHANGED
@@ -1,20 +1,6 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
# coding=utf-8
|
3
|
-
"""
|
4
|
-
Author: Liu Kun && 16031215@qq.com
|
5
|
-
Date: 2025-03-27 16:56:57
|
6
|
-
LastEditors: Liu Kun && 16031215@qq.com
|
7
|
-
LastEditTime: 2025-04-04 12:58:15
|
8
|
-
FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_date.py
|
9
|
-
Description:
|
10
|
-
EditPlatform: vscode
|
11
|
-
ComputerInfo: XPS 15 9510
|
12
|
-
SystemInfo: Windows 11
|
13
|
-
Python Version: 3.12
|
14
|
-
"""
|
15
|
-
|
16
1
|
import calendar
|
17
2
|
import datetime
|
3
|
+
import functools
|
18
4
|
from typing import List, Optional
|
19
5
|
|
20
6
|
from rich import print
|
@@ -64,15 +50,16 @@ def hour_range(start_time: str, end_time: str, hour_interval: int = 6) -> List[s
|
|
64
50
|
date_s += datetime.timedelta(hours=hour_interval)
|
65
51
|
return date_list
|
66
52
|
|
53
|
+
|
67
54
|
def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", output_format: Optional[str] = None) -> str:
|
68
55
|
"""
|
69
56
|
Adjust a given base time by adding a specified time delta.
|
70
57
|
|
71
58
|
Args:
|
72
|
-
base_time (str): Base time in the format "
|
73
|
-
Missing parts are
|
59
|
+
base_time (str): Base time in the format "yyyy" to "yyyymmddHHMMSS".
|
60
|
+
Missing parts are padded with appropriate defaults.
|
74
61
|
time_delta (int): The amount of time to add.
|
75
|
-
delta_unit (str): The unit of time to add ("seconds", "minutes", "hours", "days").
|
62
|
+
delta_unit (str): The unit of time to add ("seconds", "minutes", "hours", "days", "months", "years").
|
76
63
|
output_format (str, optional): Custom output format for the adjusted time. Defaults to None.
|
77
64
|
|
78
65
|
Returns:
|
@@ -84,17 +71,31 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
|
|
84
71
|
>>> adjust_time("20240101000000", 2, "hours", "%Y-%m-%d %H:%M:%S")
|
85
72
|
'2024-01-01 02:00:00'
|
86
73
|
>>> adjust_time("20240101000000", 30, "minutes")
|
87
|
-
'
|
74
|
+
'20240101003000'
|
88
75
|
"""
|
89
76
|
# Normalize the input time to "yyyymmddHHMMSS" format
|
90
77
|
time_format = "%Y%m%d%H%M%S"
|
91
|
-
if len(base_time) == 4:
|
92
|
-
base_time += "0101"
|
93
|
-
elif len(base_time) == 6:
|
94
|
-
base_time += "01"
|
95
|
-
base_time = base_time.ljust(14, "0")
|
96
78
|
|
97
|
-
|
79
|
+
# Pad the time string to full format
|
80
|
+
if len(base_time) == 4: # yyyy
|
81
|
+
base_time += "0101000000"
|
82
|
+
elif len(base_time) == 6: # yyyymm
|
83
|
+
base_time += "01000000"
|
84
|
+
elif len(base_time) == 8: # yyyymmdd
|
85
|
+
base_time += "000000"
|
86
|
+
elif len(base_time) == 10: # yyyymmddhh
|
87
|
+
base_time += "0000"
|
88
|
+
elif len(base_time) == 12: # yyyymmddhhmm
|
89
|
+
base_time += "00"
|
90
|
+
elif len(base_time) == 14: # yyyymmddhhmmss
|
91
|
+
pass # Already complete
|
92
|
+
else:
|
93
|
+
raise ValueError(f"Invalid base_time format. Expected 4-14 digits, got {len(base_time)}")
|
94
|
+
|
95
|
+
try:
|
96
|
+
time_obj = datetime.datetime.strptime(base_time, time_format)
|
97
|
+
except ValueError as e:
|
98
|
+
raise ValueError(f"Invalid date format: {base_time}. Error: {e}")
|
98
99
|
|
99
100
|
# Add the specified amount of time
|
100
101
|
if delta_unit == "seconds":
|
@@ -114,8 +115,18 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
|
|
114
115
|
time_obj = time_obj.replace(year=year, month=month, day=day)
|
115
116
|
elif delta_unit == "years":
|
116
117
|
# Handle year addition separately
|
117
|
-
|
118
|
-
|
118
|
+
try:
|
119
|
+
year = time_obj.year + time_delta
|
120
|
+
# Handle leap year edge case for Feb 29
|
121
|
+
if time_obj.month == 2 and time_obj.day == 29:
|
122
|
+
if not calendar.isleap(year):
|
123
|
+
time_obj = time_obj.replace(year=year, day=28)
|
124
|
+
else:
|
125
|
+
time_obj = time_obj.replace(year=year)
|
126
|
+
else:
|
127
|
+
time_obj = time_obj.replace(year=year)
|
128
|
+
except ValueError as e:
|
129
|
+
raise ValueError(f"Invalid year calculation: {e}")
|
119
130
|
else:
|
120
131
|
raise ValueError("Invalid time unit. Use 'seconds', 'minutes', 'hours', 'days', 'months', or 'years'.")
|
121
132
|
|
@@ -123,19 +134,9 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
|
|
123
134
|
if output_format:
|
124
135
|
return time_obj.strftime(output_format)
|
125
136
|
else:
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
default_format = "%Y%m%d%H%M"
|
130
|
-
elif delta_unit == "hours":
|
131
|
-
default_format = "%Y%m%d%H"
|
132
|
-
elif delta_unit == "days":
|
133
|
-
default_format = "%Y%m%d"
|
134
|
-
elif delta_unit == "months":
|
135
|
-
default_format = "%Y%m"
|
136
|
-
elif delta_unit == "years":
|
137
|
-
default_format = "%Y"
|
138
|
-
return time_obj.strftime(default_format)
|
137
|
+
# Use default format based on delta_unit
|
138
|
+
format_map = {"seconds": "%Y%m%d%H%M%S", "minutes": "%Y%m%d%H%M%S", "hours": "%Y%m%d%H", "days": "%Y%m%d", "months": "%Y%m", "years": "%Y"}
|
139
|
+
return time_obj.strftime(format_map[delta_unit])
|
139
140
|
|
140
141
|
|
141
142
|
class timeit:
|
@@ -154,26 +155,29 @@ class timeit:
|
|
154
155
|
Example:
|
155
156
|
@timeit(log_to_file=True, display_time=True)
|
156
157
|
def example_function():
|
157
|
-
|
158
|
+
import time
|
158
159
|
time.sleep(2)
|
159
160
|
"""
|
160
161
|
|
161
|
-
def __init__(self,
|
162
|
-
self.func = func
|
162
|
+
def __init__(self, log_to_file: bool = False, display_time: bool = True):
|
163
163
|
self.log_to_file = log_to_file
|
164
164
|
self.display_time = display_time
|
165
165
|
|
166
|
-
def __call__(self,
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
166
|
+
def __call__(self, func):
|
167
|
+
@functools.wraps(func)
|
168
|
+
def wrapper(*args, **kwargs):
|
169
|
+
start_time = datetime.datetime.now()
|
170
|
+
result = func(*args, **kwargs)
|
171
|
+
end_time = datetime.datetime.now()
|
172
|
+
elapsed_time = (end_time - start_time).total_seconds()
|
173
|
+
|
174
|
+
if self.display_time:
|
175
|
+
print(f"[bold green]Function '{func.__name__}' executed in {elapsed_time:.2f} seconds.[/bold green]")
|
171
176
|
|
172
|
-
|
173
|
-
|
177
|
+
if self.log_to_file:
|
178
|
+
with open("execution_time.log", "a", encoding="utf-8") as log_file:
|
179
|
+
log_file.write(f"{datetime.datetime.now()} - Function '{func.__name__}' executed in {elapsed_time:.2f} seconds.\n")
|
174
180
|
|
175
|
-
|
176
|
-
with open("execution_time.log", "a") as log_file:
|
177
|
-
log_file.write(f"{datetime.datetime.now()} - Function '{self.func.__name__}' executed in {elapsed_time:.2f} seconds.\n")
|
181
|
+
return result
|
178
182
|
|
179
|
-
return
|
183
|
+
return wrapper
|
oafuncs/oa_down/hycom_3hourly.py
CHANGED
@@ -719,7 +719,7 @@ class _HycomDownloader:
|
|
719
719
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
720
720
|
# print(f"{timestamp} - INFO - ", end="") # Output log prefix without newline
|
721
721
|
# print("[bold #3dfc40]Success")
|
722
|
-
print(f"{timestamp} -
|
722
|
+
print(f"{timestamp} - INFO - [bold #3dfc40]Success")
|
723
723
|
return
|
724
724
|
|
725
725
|
except Exception as e:
|
@@ -736,7 +736,7 @@ class _HycomDownloader:
|
|
736
736
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
737
737
|
# print(f"{timestamp} - ERROR - ", end="")
|
738
738
|
# print("[bold red]Failed")
|
739
|
-
print(f"{timestamp} -
|
739
|
+
print(f"{timestamp} - INFO - [bold red]Failure")
|
740
740
|
return
|
741
741
|
|
742
742
|
async def run(self):
|
oafuncs/oa_draw.py
CHANGED
@@ -15,16 +15,16 @@ Python Version: 3.11
|
|
15
15
|
|
16
16
|
import warnings
|
17
17
|
|
18
|
-
import cv2
|
19
18
|
import cartopy.crs as ccrs
|
20
19
|
import cartopy.feature as cfeature
|
20
|
+
import cv2
|
21
21
|
import matplotlib as mpl
|
22
22
|
import matplotlib.pyplot as plt
|
23
23
|
import numpy as np
|
24
24
|
from cartopy.mpl.ticker import LatitudeFormatter, LongitudeFormatter
|
25
25
|
from rich import print
|
26
26
|
|
27
|
-
__all__ = ["fig_minus", "gif", "
|
27
|
+
__all__ = ["fig_minus", "gif", "movie", "setup_map", "MidpointNormalize"]
|
28
28
|
|
29
29
|
warnings.filterwarnings("ignore")
|
30
30
|
|
@@ -43,15 +43,24 @@ def fig_minus(x_axis: plt.Axes = None, y_axis: plt.Axes = None, colorbar: mpl.co
|
|
43
43
|
plt.Axes | mpl.colorbar.Colorbar | None: The modified axis or colorbar object.
|
44
44
|
|
45
45
|
Example:
|
46
|
-
>>> fig_minus(x_axis=ax,
|
46
|
+
>>> fig_minus(x_axis=ax, decimal_places=2, add_spacing=True)
|
47
47
|
"""
|
48
|
+
current_ticks = None
|
49
|
+
target_object = None
|
50
|
+
|
48
51
|
# Determine which object to use and get its ticks
|
49
52
|
if x_axis is not None:
|
50
53
|
current_ticks = x_axis.get_xticks()
|
51
|
-
|
54
|
+
target_object = x_axis
|
55
|
+
elif y_axis is not None:
|
52
56
|
current_ticks = y_axis.get_yticks()
|
53
|
-
|
57
|
+
target_object = y_axis
|
58
|
+
elif colorbar is not None:
|
54
59
|
current_ticks = colorbar.get_ticks()
|
60
|
+
target_object = colorbar
|
61
|
+
else:
|
62
|
+
print("[yellow]Warning:[/yellow] No valid axis or colorbar provided.")
|
63
|
+
return None
|
55
64
|
|
56
65
|
# Find index for adding space to non-negative values if needed
|
57
66
|
if add_spacing:
|
@@ -75,326 +84,314 @@ def fig_minus(x_axis: plt.Axes = None, y_axis: plt.Axes = None, colorbar: mpl.co
|
|
75
84
|
# Apply formatted ticks to the appropriate object
|
76
85
|
if x_axis is not None:
|
77
86
|
x_axis.set_xticklabels(out_ticks)
|
78
|
-
|
87
|
+
elif y_axis is not None:
|
79
88
|
y_axis.set_yticklabels(out_ticks)
|
80
|
-
|
89
|
+
elif colorbar is not None:
|
81
90
|
colorbar.set_ticklabels(out_ticks)
|
82
91
|
|
83
92
|
print("[green]Axis tick labels updated successfully.[/green]")
|
84
|
-
return
|
93
|
+
return target_object
|
85
94
|
|
86
95
|
|
87
|
-
def gif(image_paths: list[str], output_gif_name: str, frame_duration: float =
|
96
|
+
def gif(image_paths: list[str], output_gif_name: str, frame_duration: float = 0.2, resize_dimensions: tuple[int, int] = None) -> None:
|
88
97
|
"""Create a GIF from a list of images.
|
89
98
|
|
90
99
|
Args:
|
91
100
|
image_paths (list[str]): List of image file paths.
|
92
101
|
output_gif_name (str): Name of the output GIF file.
|
93
|
-
frame_duration (float): Duration of each frame in
|
102
|
+
frame_duration (float): Duration of each frame in seconds. Defaults to 0.2.
|
94
103
|
resize_dimensions (tuple[int, int], optional): Resize dimensions (width, height). Defaults to None.
|
95
104
|
|
96
105
|
Returns:
|
97
106
|
None
|
98
107
|
|
99
108
|
Example:
|
100
|
-
>>> gif(['image1.png', 'image2.png'], 'output.gif', frame_duration=
|
109
|
+
>>> gif(['image1.png', 'image2.png'], 'output.gif', frame_duration=0.5, resize_dimensions=(800, 600))
|
101
110
|
"""
|
102
111
|
import imageio.v2 as imageio
|
103
|
-
import numpy as np
|
104
112
|
from PIL import Image
|
105
113
|
|
114
|
+
if not image_paths:
|
115
|
+
print("[red]Error:[/red] Image paths list is empty.")
|
116
|
+
return
|
117
|
+
|
106
118
|
frames = []
|
107
119
|
|
108
|
-
#
|
120
|
+
# Get target dimensions
|
109
121
|
if resize_dimensions is None and image_paths:
|
110
|
-
# 使用第一张图片的尺寸作为标准
|
111
122
|
with Image.open(image_paths[0]) as img:
|
112
123
|
resize_dimensions = img.size
|
113
124
|
|
114
|
-
#
|
125
|
+
# Read and resize all images
|
115
126
|
for image_name in image_paths:
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
127
|
+
try:
|
128
|
+
with Image.open(image_name) as img:
|
129
|
+
if resize_dimensions:
|
130
|
+
img = img.resize(resize_dimensions, Image.LANCZOS)
|
131
|
+
frames.append(np.array(img))
|
132
|
+
except Exception as e:
|
133
|
+
print(f"[yellow]Warning:[/yellow] Failed to read image {image_name}: {e}")
|
134
|
+
continue
|
120
135
|
|
121
|
-
|
122
|
-
|
136
|
+
if not frames:
|
137
|
+
print("[red]Error:[/red] No valid images found.")
|
138
|
+
return
|
139
|
+
|
140
|
+
# Create GIF
|
123
141
|
try:
|
124
|
-
# 先尝试直接使用 frame_duration 参数(以秒为单位)
|
125
142
|
imageio.mimsave(output_gif_name, frames, format="GIF", duration=frame_duration)
|
143
|
+
print(f"[green]GIF created successfully![/green] Size: {resize_dimensions}, Frame duration: {frame_duration}s")
|
126
144
|
except Exception as e:
|
127
|
-
print(f"[
|
128
|
-
# 如果失败,尝试使用 fps 参数(fps = 1/frame_duration)
|
129
|
-
fps = 1.0 / frame_duration if frame_duration > 0 else 5.0
|
130
|
-
imageio.mimsave(output_gif_name, frames, format="GIF", fps=fps)
|
145
|
+
print(f"[red]Error:[/red] Failed to create GIF: {e}")
|
131
146
|
|
132
|
-
print(f"[green]GIF created successfully![/green] Size: {resize_dimensions}, Frame interval: {frame_duration} ms")
|
133
|
-
return
|
134
147
|
|
135
|
-
|
136
|
-
|
137
|
-
"""
|
138
|
-
从图像文件列表创建视频。
|
148
|
+
def movie(image_files: list[str], output_video_path: str, fps: int) -> None:
|
149
|
+
"""Create a video from a list of image files.
|
139
150
|
|
140
151
|
Args:
|
141
|
-
image_files (list):
|
142
|
-
output_video_path (str):
|
143
|
-
fps (int):
|
152
|
+
image_files (list[str]): List of image file paths in order.
|
153
|
+
output_video_path (str): Output video file path (e.g., 'output.mp4').
|
154
|
+
fps (int): Video frame rate.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
None
|
158
|
+
|
159
|
+
Example:
|
160
|
+
>>> movie(['img1.jpg', 'img2.jpg'], 'output.mp4', fps=30)
|
144
161
|
"""
|
145
162
|
if not image_files:
|
146
|
-
print("
|
163
|
+
print("[red]Error:[/red] Image files list is empty.")
|
147
164
|
return
|
148
165
|
|
149
|
-
#
|
166
|
+
# Read first image to get frame dimensions
|
150
167
|
try:
|
151
168
|
frame = cv2.imread(image_files[0])
|
152
169
|
if frame is None:
|
153
|
-
print(f"
|
170
|
+
print(f"[red]Error:[/red] Cannot read first image: {image_files[0]}")
|
154
171
|
return
|
155
172
|
height, width, layers = frame.shape
|
156
173
|
size = (width, height)
|
157
|
-
print(f"
|
174
|
+
print(f"Video dimensions set to: {size}")
|
158
175
|
except Exception as e:
|
159
|
-
print(f"
|
176
|
+
print(f"[red]Error:[/red] Error reading first image: {e}")
|
160
177
|
return
|
161
178
|
|
162
|
-
#
|
163
|
-
|
164
|
-
# 对于 .avi 文件,常用 'XVID' 或 'MJPG'
|
165
|
-
fourcc = cv2.VideoWriter_fourcc(*"mp4v") # 或者尝试 'avc1', 'XVID' 等
|
179
|
+
# Create VideoWriter object
|
180
|
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
166
181
|
out = cv2.VideoWriter(output_video_path, fourcc, fps, size)
|
167
182
|
|
168
183
|
if not out.isOpened():
|
169
|
-
print(f"
|
170
|
-
print("
|
184
|
+
print(f"[red]Error:[/red] Cannot open video file for writing: {output_video_path}")
|
185
|
+
print("Please check if the codec is supported and the path is valid.")
|
171
186
|
return
|
172
187
|
|
173
|
-
print(f"
|
188
|
+
print(f"Starting to write images to video: {output_video_path}...")
|
189
|
+
successful_frames = 0
|
190
|
+
|
174
191
|
for i, filename in enumerate(image_files):
|
175
192
|
try:
|
176
193
|
frame = cv2.imread(filename)
|
177
194
|
if frame is None:
|
178
|
-
print(f"
|
195
|
+
print(f"[yellow]Warning:[/yellow] Skipping unreadable image: {filename}")
|
179
196
|
continue
|
180
|
-
|
197
|
+
|
198
|
+
# Ensure frame dimensions match initialization
|
181
199
|
current_height, current_width, _ = frame.shape
|
182
200
|
if (current_width, current_height) != size:
|
183
|
-
print(f"警告:图像 {filename} 的尺寸 ({current_width}, {current_height}) 与初始尺寸 {size} 不同。将调整大小。")
|
184
201
|
frame = cv2.resize(frame, size)
|
185
202
|
|
186
203
|
out.write(frame)
|
187
|
-
|
204
|
+
successful_frames += 1
|
205
|
+
|
206
|
+
# Print progress
|
188
207
|
if (i + 1) % 50 == 0 or (i + 1) == len(image_files):
|
189
|
-
print(f"
|
208
|
+
print(f"Processed {i + 1}/{len(image_files)} frames")
|
190
209
|
|
191
210
|
except Exception as e:
|
192
|
-
print(f"
|
193
|
-
continue
|
211
|
+
print(f"[yellow]Warning:[/yellow] Error processing image {filename}: {e}")
|
212
|
+
continue
|
194
213
|
|
195
|
-
#
|
214
|
+
# Release resources
|
196
215
|
out.release()
|
197
|
-
print(f"
|
198
|
-
|
199
|
-
|
200
|
-
def
|
201
|
-
|
216
|
+
print(f"[green]Video created successfully:[/green] {output_video_path} ({successful_frames} frames)")
|
217
|
+
|
218
|
+
|
219
|
+
def setup_map(
|
220
|
+
axes: plt.Axes,
|
221
|
+
longitude_data: np.ndarray = None,
|
222
|
+
latitude_data: np.ndarray = None,
|
223
|
+
map_projection: ccrs.Projection = ccrs.PlateCarree(),
|
224
|
+
# Map features
|
225
|
+
show_land: bool = True,
|
226
|
+
show_ocean: bool = True,
|
227
|
+
show_coastline: bool = True,
|
228
|
+
show_borders: bool = False,
|
229
|
+
land_color: str = "lightgrey",
|
230
|
+
ocean_color: str = "lightblue",
|
231
|
+
coastline_linewidth: float = 0.5,
|
232
|
+
# Gridlines and ticks
|
233
|
+
show_gridlines: bool = False,
|
234
|
+
longitude_ticks: list[float] = None,
|
235
|
+
latitude_ticks: list[float] = None,
|
236
|
+
tick_decimals: int = 0,
|
237
|
+
# Gridline styling
|
238
|
+
grid_color: str = "k",
|
239
|
+
grid_alpha: float = 0.5,
|
240
|
+
grid_style: str = "--",
|
241
|
+
grid_width: float = 0.5,
|
242
|
+
# Label options
|
243
|
+
show_labels: bool = True,
|
244
|
+
left_labels: bool = True,
|
245
|
+
bottom_labels: bool = True,
|
246
|
+
right_labels: bool = False,
|
247
|
+
top_labels: bool = False,
|
248
|
+
) -> plt.Axes:
|
249
|
+
"""Setup a complete cartopy map with customizable features.
|
202
250
|
|
203
251
|
Args:
|
204
|
-
|
205
|
-
latitudes (list[float], optional): List of latitude values to format.
|
206
|
-
decimal_places (int, optional): Number of decimal places to display. Defaults to 2.
|
207
|
-
|
208
|
-
Returns:
|
209
|
-
tuple[list[str], list[str]] | list[str]: Formatted longitude and/or latitude labels.
|
210
|
-
Returns a tuple of two lists if both longitudes and latitudes are provided,
|
211
|
-
otherwise returns a single list of formatted values.
|
212
|
-
|
213
|
-
Examples:
|
214
|
-
>>> add_lonlat_unit(longitudes=[120, 180], latitudes=[30, 60], decimal_places=1)
|
215
|
-
(['120.0°E', '180.0°'], ['30.0°N', '60.0°N'])
|
216
|
-
>>> add_lonlat_unit(longitudes=[120, -60])
|
217
|
-
['120.00°E', '60.00°W']
|
218
|
-
"""
|
219
|
-
|
220
|
-
def _format_longitude(longitude_values: list[float]) -> list[str] | str:
|
221
|
-
"""Format longitude values to string labels with directional indicators.
|
222
|
-
|
223
|
-
Converts numerical longitude values to formatted strings with degree symbols
|
224
|
-
and East/West indicators. Values outside the -180 to 180 range are normalized.
|
225
|
-
|
226
|
-
Args:
|
227
|
-
longitude_values: List of longitude values to format.
|
228
|
-
|
229
|
-
Returns:
|
230
|
-
List of formatted strings if input contains multiple values,
|
231
|
-
or a single string if input contains just one value.
|
232
|
-
"""
|
233
|
-
out_list = []
|
234
|
-
for x in longitude_values:
|
235
|
-
if x > 180 or x < -180:
|
236
|
-
print(f"[yellow]Warning:[/yellow] Longitude value {x} outside normal range (-180 to 180)")
|
237
|
-
x = ((x + 180) % 360) - 180 # Normalize to -180 to 180 range
|
238
|
-
|
239
|
-
degrees = round(abs(x), decimal_places)
|
240
|
-
direction = "E" if x >= 0 else "W"
|
241
|
-
out_list.append(f"{degrees:.{decimal_places}f}°{direction}" if x != 0 and x != 180 else f"{degrees}°")
|
242
|
-
return out_list if len(out_list) > 1 else out_list[0]
|
243
|
-
|
244
|
-
def _format_latitude(latitude_values: list[float]) -> list[str] | str:
|
245
|
-
"""Format latitude values to string labels with directional indicators.
|
246
|
-
|
247
|
-
Converts numerical latitude values to formatted strings with degree symbols
|
248
|
-
and North/South indicators. Values outside the -90 to 90 range are normalized.
|
249
|
-
|
250
|
-
Args:
|
251
|
-
latitude_values (list[float]): List of latitude values to format
|
252
|
-
|
253
|
-
Returns:
|
254
|
-
list[str] | str: List of formatted strings if input contains multiple values,
|
255
|
-
or a single string if input contains just one value
|
256
|
-
"""
|
257
|
-
out_list = []
|
258
|
-
for y in latitude_values:
|
259
|
-
if y > 90 or y < -90:
|
260
|
-
print(f"[yellow]Warning:[/yellow] Latitude value {y} outside valid range (-90 to 90)")
|
261
|
-
y = min(max(y % 180 - 90, -90), 90) # Normalize to -90 to 90 range
|
262
|
-
|
263
|
-
degrees = round(abs(y), decimal_places)
|
264
|
-
direction = "N" if y >= 0 else "S"
|
265
|
-
out_list.append(f"{degrees:.{decimal_places}f}°{direction}" if y != 0 else f"{degrees}°")
|
266
|
-
return out_list if len(out_list) > 1 else out_list[0]
|
267
|
-
|
268
|
-
# Input validation
|
269
|
-
if longitudes is not None and not isinstance(longitudes, list):
|
270
|
-
longitudes = [longitudes] # Convert single value to list
|
271
|
-
if latitudes is not None and not isinstance(latitudes, list):
|
272
|
-
latitudes = [latitudes] # Convert single value to list
|
273
|
-
|
274
|
-
if longitudes and latitudes:
|
275
|
-
result = _format_longitude(longitudes), _format_latitude(latitudes)
|
276
|
-
elif longitudes:
|
277
|
-
result = _format_longitude(longitudes)
|
278
|
-
elif latitudes:
|
279
|
-
result = _format_latitude(latitudes)
|
280
|
-
else:
|
281
|
-
result = []
|
282
|
-
|
283
|
-
print("[green]Longitude and latitude values formatted successfully.[/green]")
|
284
|
-
return result
|
285
|
-
|
286
|
-
|
287
|
-
def add_gridlines(axes: plt.Axes, longitude_lines: list[float] = None, latitude_lines: list[float] = None, map_projection: ccrs.Projection = ccrs.PlateCarree(), line_color: str = "k", line_alpha: float = 0.5, line_style: str = "--", line_width: float = 0.5) -> tuple[plt.Axes, mpl.ticker.Locator]:
|
288
|
-
"""Add gridlines to a map.
|
289
|
-
|
290
|
-
Args:
|
291
|
-
axes (plt.Axes): The axes to add gridlines to.
|
292
|
-
longitude_lines (list[float], optional): List of longitude positions for gridlines.
|
293
|
-
latitude_lines (list[float], optional): List of latitude positions for gridlines.
|
294
|
-
map_projection (ccrs.Projection, optional): Coordinate reference system. Defaults to PlateCarree.
|
295
|
-
line_color (str, optional): Line color. Defaults to "k".
|
296
|
-
line_alpha (float, optional): Line transparency. Defaults to 0.5.
|
297
|
-
line_style (str, optional): Line style. Defaults to "--".
|
298
|
-
line_width (float, optional): Line width. Defaults to 0.5.
|
299
|
-
|
300
|
-
Returns:
|
301
|
-
tuple[plt.Axes, mpl.ticker.Locator]: The axes and gridlines objects.
|
302
|
-
|
303
|
-
Example:
|
304
|
-
>>> add_gridlines(axes, longitude_lines=[0, 30], latitude_lines=[-90, 90], map_projection=ccrs.PlateCarree())
|
305
|
-
>>> axes, gl = add_gridlines(axes, longitude_lines=[0, 30], latitude_lines=[-90, 90])
|
306
|
-
"""
|
307
|
-
from matplotlib import ticker as mticker
|
308
|
-
|
309
|
-
# add gridlines
|
310
|
-
gl = axes.gridlines(crs=map_projection, draw_labels=True, linewidth=line_width, color=line_color, alpha=line_alpha, linestyle=line_style)
|
311
|
-
gl.right_labels = False
|
312
|
-
gl.top_labels = False
|
313
|
-
gl.xformatter = LongitudeFormatter(zero_direction_label=False)
|
314
|
-
gl.yformatter = LatitudeFormatter()
|
315
|
-
|
316
|
-
if longitude_lines is not None:
|
317
|
-
gl.xlocator = mticker.FixedLocator(np.array(longitude_lines))
|
318
|
-
if latitude_lines is not None:
|
319
|
-
gl.ylocator = mticker.FixedLocator(np.array(latitude_lines))
|
320
|
-
|
321
|
-
# print("[green]Gridlines added successfully.[/green]")
|
322
|
-
return axes, gl
|
323
|
-
|
324
|
-
|
325
|
-
def add_cartopy(axes: plt.Axes, longitude_data: np.ndarray = None, latitude_data: np.ndarray = None, map_projection: ccrs.Projection = ccrs.PlateCarree(), show_gridlines: bool = True, land_color: str = "lightgrey", ocean_color: str = "lightblue", coastline_linewidth: float = 0.5) -> None:
|
326
|
-
"""Add cartopy features to a map.
|
327
|
-
|
328
|
-
Args:
|
329
|
-
axes (plt.Axes): The axes to add map features to.
|
252
|
+
axes (plt.Axes): The axes to setup as a map.
|
330
253
|
longitude_data (np.ndarray, optional): Array of longitudes to set map extent.
|
331
254
|
latitude_data (np.ndarray, optional): Array of latitudes to set map extent.
|
332
255
|
map_projection (ccrs.Projection, optional): Coordinate reference system. Defaults to PlateCarree.
|
333
|
-
|
256
|
+
|
257
|
+
show_land (bool, optional): Whether to show land features. Defaults to True.
|
258
|
+
show_ocean (bool, optional): Whether to show ocean features. Defaults to True.
|
259
|
+
show_coastline (bool, optional): Whether to show coastlines. Defaults to True.
|
260
|
+
show_borders (bool, optional): Whether to show country borders. Defaults to False.
|
334
261
|
land_color (str, optional): Color of land. Defaults to "lightgrey".
|
335
262
|
ocean_color (str, optional): Color of oceans. Defaults to "lightblue".
|
336
263
|
coastline_linewidth (float, optional): Line width for coastlines. Defaults to 0.5.
|
337
264
|
|
338
|
-
|
339
|
-
|
265
|
+
show_gridlines (bool, optional): Whether to show gridlines. Defaults to False.
|
266
|
+
longitude_ticks (list[float], optional): Longitude tick positions.
|
267
|
+
latitude_ticks (list[float], optional): Latitude tick positions.
|
268
|
+
tick_decimals (int, optional): Number of decimal places for tick labels. Defaults to 0.
|
340
269
|
|
341
|
-
|
342
|
-
|
343
|
-
|
270
|
+
grid_color (str, optional): Gridline color. Defaults to "k".
|
271
|
+
grid_alpha (float, optional): Gridline transparency. Defaults to 0.5.
|
272
|
+
grid_style (str, optional): Gridline style. Defaults to "--".
|
273
|
+
grid_width (float, optional): Gridline width. Defaults to 0.5.
|
344
274
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
# axes.add_feature(cfeature.BORDERS, linewidth=coastline_linewidth, linestyle=":")
|
275
|
+
show_labels (bool, optional): Whether to show coordinate labels. Defaults to True.
|
276
|
+
left_labels (bool, optional): Show labels on left side. Defaults to True.
|
277
|
+
bottom_labels (bool, optional): Show labels on bottom. Defaults to True.
|
278
|
+
right_labels (bool, optional): Show labels on right side. Defaults to False.
|
279
|
+
top_labels (bool, optional): Show labels on top. Defaults to False.
|
351
280
|
|
352
|
-
|
353
|
-
|
354
|
-
axes, gl = add_gridlines(axes, map_projection=map_projection)
|
281
|
+
Returns:
|
282
|
+
plt.Axes: The configured map axes.
|
355
283
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
284
|
+
Examples:
|
285
|
+
>>> # Basic map setup
|
286
|
+
>>> ax = setup_map(ax)
|
287
|
+
|
288
|
+
>>> # Map with gridlines and custom extent
|
289
|
+
>>> ax = setup_map(ax, longitude_data=lon, latitude_data=lat, show_gridlines=True)
|
290
|
+
|
291
|
+
>>> # Customized map
|
292
|
+
>>> ax = setup_map(
|
293
|
+
... ax,
|
294
|
+
... show_gridlines=True,
|
295
|
+
... longitude_ticks=[0, 30, 60],
|
296
|
+
... latitude_ticks=[-30, 0, 30],
|
297
|
+
... land_color='wheat',
|
298
|
+
... ocean_color='lightcyan'
|
299
|
+
... )
|
300
|
+
"""
|
301
|
+
from matplotlib import ticker as mticker
|
361
302
|
|
362
|
-
#
|
303
|
+
# Add map features
|
304
|
+
if show_land:
|
305
|
+
axes.add_feature(cfeature.LAND, facecolor=land_color)
|
306
|
+
if show_ocean:
|
307
|
+
axes.add_feature(cfeature.OCEAN, facecolor=ocean_color)
|
308
|
+
if show_coastline:
|
309
|
+
axes.add_feature(cfeature.COASTLINE, linewidth=coastline_linewidth)
|
310
|
+
if show_borders:
|
311
|
+
axes.add_feature(cfeature.BORDERS, linewidth=coastline_linewidth, linestyle=":")
|
312
|
+
|
313
|
+
# Setup coordinate formatting
|
314
|
+
lon_formatter = LongitudeFormatter(zero_direction_label=False, number_format=f".{tick_decimals}f")
|
315
|
+
lat_formatter = LatitudeFormatter(number_format=f".{tick_decimals}f")
|
316
|
+
|
317
|
+
# Handle gridlines and ticks
|
318
|
+
if show_gridlines:
|
319
|
+
# Add gridlines with labels
|
320
|
+
gl = axes.gridlines(crs=map_projection, draw_labels=show_labels, linewidth=grid_width, color=grid_color, alpha=grid_alpha, linestyle=grid_style)
|
321
|
+
|
322
|
+
# Configure label positions
|
323
|
+
gl.left_labels = left_labels
|
324
|
+
gl.bottom_labels = bottom_labels
|
325
|
+
gl.right_labels = right_labels
|
326
|
+
gl.top_labels = top_labels
|
327
|
+
|
328
|
+
# Set formatters
|
329
|
+
gl.xformatter = lon_formatter
|
330
|
+
gl.yformatter = lat_formatter
|
331
|
+
|
332
|
+
# Set custom tick positions if provided
|
333
|
+
if longitude_ticks is not None:
|
334
|
+
gl.xlocator = mticker.FixedLocator(np.array(longitude_ticks))
|
335
|
+
if latitude_ticks is not None:
|
336
|
+
gl.ylocator = mticker.FixedLocator(np.array(latitude_ticks))
|
337
|
+
|
338
|
+
elif show_labels:
|
339
|
+
# Add tick labels without gridlines
|
340
|
+
# Use current tick positions if not provided
|
341
|
+
if longitude_ticks is None:
|
342
|
+
longitude_ticks = axes.get_xticks()
|
343
|
+
if latitude_ticks is None:
|
344
|
+
latitude_ticks = axes.get_yticks()
|
345
|
+
|
346
|
+
# Set tick positions and formatters
|
347
|
+
axes.set_xticks(longitude_ticks, crs=map_projection)
|
348
|
+
axes.set_yticks(latitude_ticks, crs=map_projection)
|
349
|
+
axes.xaxis.set_major_formatter(lon_formatter)
|
350
|
+
axes.yaxis.set_major_formatter(lat_formatter)
|
351
|
+
|
352
|
+
# Set map extent if data is provided
|
363
353
|
if longitude_data is not None and latitude_data is not None:
|
364
354
|
lon_min, lon_max = np.nanmin(longitude_data), np.nanmax(longitude_data)
|
365
355
|
lat_min, lat_max = np.nanmin(latitude_data), np.nanmax(latitude_data)
|
366
356
|
axes.set_extent([lon_min, lon_max, lat_min, lat_max], crs=map_projection)
|
367
357
|
|
368
|
-
# print("[green]Cartopy features added successfully.[/green]")
|
369
358
|
return axes
|
370
359
|
|
371
360
|
|
372
361
|
class MidpointNormalize(mpl.colors.Normalize):
|
373
|
-
"""Custom normalization class to center
|
362
|
+
"""Custom normalization class to center a specific value.
|
374
363
|
|
375
364
|
Args:
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
365
|
+
vmin (float, optional): Minimum data value. Defaults to None.
|
366
|
+
vmax (float, optional): Maximum data value. Defaults to None.
|
367
|
+
vcenter (float, optional): Center value for normalization. Defaults to 0.
|
368
|
+
clip (bool, optional): Whether to clip data outside the range. Defaults to False.
|
380
369
|
|
381
370
|
Example:
|
382
|
-
>>> norm = MidpointNormalize(
|
371
|
+
>>> norm = MidpointNormalize(vmin=-2, vmax=1, vcenter=0)
|
383
372
|
"""
|
384
373
|
|
385
|
-
def __init__(self,
|
386
|
-
self.vcenter =
|
387
|
-
super().__init__(
|
374
|
+
def __init__(self, vmin: float = None, vmax: float = None, vcenter: float = 0, clip: bool = False) -> None:
|
375
|
+
self.vcenter = vcenter
|
376
|
+
super().__init__(vmin, vmax, clip)
|
388
377
|
|
389
|
-
def __call__(self,
|
378
|
+
def __call__(self, value: np.ndarray, clip: bool = None) -> np.ma.MaskedArray:
|
379
|
+
# Use the clip parameter from initialization if not provided
|
380
|
+
if clip is None:
|
381
|
+
clip = self.clip
|
382
|
+
|
390
383
|
x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.0]
|
391
|
-
|
392
|
-
|
393
|
-
|
384
|
+
result = np.interp(value, x, y)
|
385
|
+
|
386
|
+
# Apply clipping if requested
|
387
|
+
if clip:
|
388
|
+
result = np.clip(result, 0, 1)
|
389
|
+
|
390
|
+
return np.ma.masked_array(result)
|
391
|
+
|
392
|
+
def inverse(self, value: np.ndarray) -> np.ndarray:
|
394
393
|
y, x = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
|
395
|
-
return np.interp(
|
396
|
-
|
397
|
-
# print("[green]Midpoint normalization applied successfully.[/green]")
|
394
|
+
return np.interp(value, x, y)
|
398
395
|
|
399
396
|
|
400
397
|
if __name__ == "__main__":
|
oafuncs/oa_file.py
CHANGED
@@ -67,12 +67,16 @@ def link_file(source_pattern: str, destination: str) -> None:
|
|
67
67
|
else:
|
68
68
|
dst_file = destination
|
69
69
|
|
70
|
-
|
70
|
+
# Ensure destination directory exists
|
71
|
+
dst_dir = os.path.dirname(dst_file)
|
72
|
+
if dst_dir:
|
73
|
+
os.makedirs(dst_dir, exist_ok=True)
|
71
74
|
|
72
|
-
if
|
75
|
+
# Remove existing file/link if it exists
|
76
|
+
if os.path.exists(dst_file) or os.path.islink(dst_file):
|
73
77
|
os.remove(dst_file)
|
78
|
+
|
74
79
|
os.symlink(src_file, dst_file)
|
75
|
-
|
76
80
|
print(f"[green]Successfully created symbolic link:[/green] [bold]{src_file}[/bold] -> [bold]{dst_file}[/bold]")
|
77
81
|
except Exception as e:
|
78
82
|
print(f"[red]Failed to create symbolic link:[/red] [bold]{src_file}[/bold]. Error: {e}")
|
@@ -103,8 +107,12 @@ def copy_file(source_pattern: str, destination: str) -> None:
|
|
103
107
|
else:
|
104
108
|
dst_file = destination
|
105
109
|
|
106
|
-
|
110
|
+
# Ensure destination directory exists
|
111
|
+
dst_dir = os.path.dirname(dst_file)
|
112
|
+
if dst_dir:
|
113
|
+
os.makedirs(dst_dir, exist_ok=True)
|
107
114
|
|
115
|
+
# Remove existing destination if it exists
|
108
116
|
if os.path.exists(dst_file):
|
109
117
|
if os.path.isdir(dst_file):
|
110
118
|
shutil.rmtree(dst_file)
|
@@ -147,15 +155,19 @@ def move_file(source_pattern: str, destination: str) -> None:
|
|
147
155
|
else:
|
148
156
|
dst_file = destination
|
149
157
|
|
150
|
-
|
158
|
+
# Ensure destination directory exists
|
159
|
+
dst_dir = os.path.dirname(dst_file)
|
160
|
+
if dst_dir:
|
161
|
+
os.makedirs(dst_dir, exist_ok=True)
|
151
162
|
|
163
|
+
# Remove existing destination if it exists
|
152
164
|
if os.path.exists(dst_file):
|
153
165
|
if os.path.isdir(dst_file):
|
154
166
|
shutil.rmtree(dst_file)
|
155
167
|
else:
|
156
168
|
os.remove(dst_file)
|
169
|
+
|
157
170
|
shutil.move(src_file, dst_file)
|
158
|
-
|
159
171
|
print(f"[green]Successfully moved:[/green] [bold]{src_file}[/bold] -> [bold]{dst_file}[/bold]")
|
160
172
|
except Exception as e:
|
161
173
|
print(f"[red]Failed to move:[/red] [bold]{src_file}[/bold]. Error: {e}")
|
@@ -352,13 +364,13 @@ def mean_size(parent_dir: Union[str, os.PathLike], file_pattern: str, max_files:
|
|
352
364
|
>>> avg_size = mean_size("/data/logs", "*.log", max_files=10, unit="MB")
|
353
365
|
>>> print(f"Average log file size is {avg_size} MB")
|
354
366
|
"""
|
355
|
-
#
|
367
|
+
# Get file list
|
356
368
|
flist = find_file(parent_dir, file_pattern)
|
357
369
|
if not flist:
|
358
370
|
print(f"[yellow]No files found matching pattern:[/yellow] [bold]{file_pattern}[/bold] in [bold]{parent_dir}[/bold]")
|
359
371
|
return 0.0
|
360
372
|
|
361
|
-
#
|
373
|
+
# Handle max_files limit
|
362
374
|
if max_files is not None:
|
363
375
|
try:
|
364
376
|
max_files = int(max_files)
|
@@ -369,11 +381,11 @@ def mean_size(parent_dir: Union[str, os.PathLike], file_pattern: str, max_files:
|
|
369
381
|
except (ValueError, TypeError):
|
370
382
|
print(f"[yellow]Invalid max_files value:[/yellow] [bold]{max_files}[/bold], using all files")
|
371
383
|
|
372
|
-
#
|
384
|
+
# Get file sizes and filter out zero-size files
|
373
385
|
size_list = [file_size(f, unit) for f in flist]
|
374
386
|
valid_sizes = [size for size in size_list if size > 0]
|
375
387
|
|
376
|
-
#
|
388
|
+
# Calculate average
|
377
389
|
if valid_sizes:
|
378
390
|
avg_size = sum(valid_sizes) / len(valid_sizes)
|
379
391
|
if len(valid_sizes) < len(size_list):
|
@@ -1,18 +1,18 @@
|
|
1
1
|
oafuncs/__init__.py,sha256=T_-VtnWWllV3Q91twT5Yt2sUapeA051QbPNnBxmg9nw,1456
|
2
|
-
oafuncs/oa_cmap.py,sha256=
|
2
|
+
oafuncs/oa_cmap.py,sha256=KKEM3Dx0RYYFdLaj0eX6p1iQ1IcKR0RZmt7a3bMGO3Y,13609
|
3
3
|
oafuncs/oa_data.py,sha256=Wp7Yn6n-WNddAp1UtwK5CSEdGSrzzgOH5gtaOHBaxtw,8065
|
4
|
-
oafuncs/oa_date.py,sha256=
|
5
|
-
oafuncs/oa_draw.py,sha256=
|
6
|
-
oafuncs/oa_file.py,sha256=
|
4
|
+
oafuncs/oa_date.py,sha256=aU2wVIWXyWoRiSQ9dg8sHvShFTxw86RrgbV3Q6tDjD4,6841
|
5
|
+
oafuncs/oa_draw.py,sha256=xry1Roty5P4LD_VXLSzEwfh_Yx3RwCLz-xib1H9MwmI,15172
|
6
|
+
oafuncs/oa_file.py,sha256=fLb0gRhq2AiPl-5ASDHMrx6Z267FmhqNcTV7CdCxTdI,16934
|
7
7
|
oafuncs/oa_help.py,sha256=_4AZgRDq5Or0vauNvq5IDDHIBoBfdOQtzak-mG1wwAw,4537
|
8
8
|
oafuncs/oa_nc.py,sha256=TMrrPBdPz1IJ-7dMOD_gzzTbG6FHSgm-ZfdlvoJDtcY,15245
|
9
9
|
oafuncs/oa_python.py,sha256=NkopwkYFGSEuVljnTBvXCl6o2CeyRNBqRXSsUl3euEE,5192
|
10
10
|
oafuncs/oa_tool.py,sha256=Zuaoa92wll0YqXGRf0oF_c7wlATtl7bvjCuLt9VLXp0,8046
|
11
11
|
oafuncs/_data/hycom.png,sha256=MadKs6Gyj5n9-TOu7L4atQfTXtF9dvN9w-tdU9IfygI,10945710
|
12
12
|
oafuncs/_data/oafuncs.png,sha256=o3VD7wm-kwDea5E98JqxXl04_78cBX7VcdUt7uQXGiU,3679898
|
13
|
-
oafuncs/_script/cprogressbar.py,sha256=
|
13
|
+
oafuncs/_script/cprogressbar.py,sha256=nIOs42t6zURLTNjJglavqrI2TKO9GWD0AmZ_DOBmXDU,15949
|
14
14
|
oafuncs/_script/data_interp.py,sha256=EiZbt6n5BEaRKcng88UgX7TFPhKE6TLVZniS01awXjg,5146
|
15
|
-
oafuncs/_script/email.py,sha256=
|
15
|
+
oafuncs/_script/email.py,sha256=l5xDgdVj8O5V0J2SwjsHKdUuxOH2jZvwdMO_P0dImHU,2684
|
16
16
|
oafuncs/_script/netcdf_merge.py,sha256=tM9ePqLiEsE7eIsNM5XjEYeXwxjYOdNz5ejnEuI7xKw,6066
|
17
17
|
oafuncs/_script/netcdf_modify.py,sha256=sGRUYNhfGgf9JV70rnBzw3bzuTRSXzBTL_RMDnDPeLQ,4552
|
18
18
|
oafuncs/_script/netcdf_write.py,sha256=GvyUyUhzMonzSp3y4pT8ZAfbQrsh5J3dLnmINYJKhuE,21422
|
@@ -22,7 +22,7 @@ oafuncs/_script/plot_dataset.py,sha256=4jhUZNIc2DLHnk5GH0rotzhOq0cAMmB7rhMJKorYO
|
|
22
22
|
oafuncs/_script/replace_file_content.py,sha256=eCFZjnZcwyRvy6b4mmIfBna-kylSZTyJRfgXd6DdCjk,5982
|
23
23
|
oafuncs/oa_down/User_Agent-list.txt,sha256=pHaMlElMvZ8TG4vf4BqkZYKqe0JIGkr4kCN0lM1Y9FQ,514295
|
24
24
|
oafuncs/oa_down/__init__.py,sha256=kRX5eTUCbAiz3zTaQM1501paOYS_3fizDN4Pa0mtNUA,585
|
25
|
-
oafuncs/oa_down/hycom_3hourly.py,sha256=
|
25
|
+
oafuncs/oa_down/hycom_3hourly.py,sha256=pfUMZp_Tmfh3LbOV5Gd87998ZenuAFOAnxy1Vxi1NpM,55071
|
26
26
|
oafuncs/oa_down/hycom_3hourly_proxy.py,sha256=1eaoJGI_m-7w4ZZ3n7NGxkZaeFdsm0d3U-hyw8RFNbc,54563
|
27
27
|
oafuncs/oa_down/idm.py,sha256=4z5IvgfTyIKEI1kOtqXZwN7Jnfjwp6qDBOIoVyOLp0I,1823
|
28
28
|
oafuncs/oa_down/literature.py,sha256=2bF9gSKQbzcci9LcKE81j8JEjIJwON7jbwQB3gDDA3E,11331
|
@@ -38,8 +38,8 @@ oafuncs/oa_sign/__init__.py,sha256=QKqTFrJDFK40C5uvk48GlRRbGFzO40rgkYwu6dYxatM,5
|
|
38
38
|
oafuncs/oa_sign/meteorological.py,sha256=8091SHo2L8kl4dCFmmSH5NGVHDku5i5lSiLEG5DLnOQ,6489
|
39
39
|
oafuncs/oa_sign/ocean.py,sha256=xrW-rWD7xBWsB5PuCyEwQ1Q_RDKq2KCLz-LOONHgldU,5932
|
40
40
|
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.
|
41
|
+
oafuncs-0.0.98.32.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
|
42
|
+
oafuncs-0.0.98.32.dist-info/METADATA,sha256=qCDKj6VKcdl60LTnWfzGqEDg0opylNPBgfhs6dHddfQ,4326
|
43
|
+
oafuncs-0.0.98.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
44
|
+
oafuncs-0.0.98.32.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
|
45
|
+
oafuncs-0.0.98.32.dist-info/RECORD,,
|
File without changes
|
File without changes
|