cht_utils 2.0.0__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.
- cht_utils/__init__.py +28 -0
- cht_utils/cog/__init__.py +6 -0
- cht_utils/cog/geotiff_to_cog.py +79 -0
- cht_utils/cog/netcdf_to_cog.py +85 -0
- cht_utils/cog/xyz_to_cog.py +86 -0
- cht_utils/colors/__init__.py +6 -0
- cht_utils/colors/colors.py +117 -0
- cht_utils/fileio/__init__.py +21 -0
- cht_utils/fileio/deltares_ini.py +326 -0
- cht_utils/fileio/json_js.py +72 -0
- cht_utils/fileio/pli_file.py +233 -0
- cht_utils/fileio/tekal.py +234 -0
- cht_utils/fileio/xml.py +184 -0
- cht_utils/fileio/yaml.py +39 -0
- cht_utils/fileops/__init__.py +25 -0
- cht_utils/fileops/fileops.py +344 -0
- cht_utils/interpolation/__init__.py +5 -0
- cht_utils/interpolation/interpolation.py +152 -0
- cht_utils/maps/__init__.py +2 -0
- cht_utils/maps/fileops.py +191 -0
- cht_utils/maps/flood_map.py +1231 -0
- cht_utils/maps/topobathy_map.py +463 -0
- cht_utils/maps/utils.py +700 -0
- cht_utils/physics/__init__.py +8 -0
- cht_utils/physics/deshoal.py +63 -0
- cht_utils/physics/disper.py +91 -0
- cht_utils/physics/runup_vo21.py +229 -0
- cht_utils/physics/waves.py +59 -0
- cht_utils/probabilistic/__init__.py +5 -0
- cht_utils/probabilistic/prob_maps.py +263 -0
- cht_utils/remote/__init__.py +4 -0
- cht_utils/remote/s3.py +380 -0
- cht_utils/remote/sftp.py +192 -0
- cht_utils-2.0.0.dist-info/METADATA +30 -0
- cht_utils-2.0.0.dist-info/RECORD +39 -0
- cht_utils-2.0.0.dist-info/WHEEL +5 -0
- cht_utils-2.0.0.dist-info/licenses/LICENSE +21 -0
- cht_utils-2.0.0.dist-info/top_level.txt +1 -0
- cht_utils-2.0.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Common file and directory operations using pathlib and shutil."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Union
|
|
6
|
+
|
|
7
|
+
PathLike = Union[str, Path]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def move(src: PathLike, dst: PathLike) -> None:
|
|
11
|
+
"""Move files to a destination.
|
|
12
|
+
|
|
13
|
+
If *src* contains glob wildcards, all matching files are moved into *dst*
|
|
14
|
+
(which must be a directory). Without wildcards, *dst* may be a directory
|
|
15
|
+
or a new file name (rename).
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
src : str or Path
|
|
20
|
+
Source glob pattern (e.g. ``"data/*.nc"``) or single file/directory.
|
|
21
|
+
dst : str or Path
|
|
22
|
+
Destination directory or new name.
|
|
23
|
+
"""
|
|
24
|
+
src = Path(src)
|
|
25
|
+
dst = Path(dst)
|
|
26
|
+
if _has_glob(src):
|
|
27
|
+
for f in src.parent.glob(src.name):
|
|
28
|
+
target = dst / f.name
|
|
29
|
+
if target.exists():
|
|
30
|
+
if target.is_dir():
|
|
31
|
+
shutil.rmtree(target)
|
|
32
|
+
else:
|
|
33
|
+
target.unlink()
|
|
34
|
+
shutil.move(str(f), str(dst))
|
|
35
|
+
else:
|
|
36
|
+
if src.exists():
|
|
37
|
+
# If dst is a directory, the final target is dst/src.name
|
|
38
|
+
target = dst / src.name if dst.is_dir() else dst
|
|
39
|
+
if target.exists():
|
|
40
|
+
if target.is_dir():
|
|
41
|
+
shutil.rmtree(target)
|
|
42
|
+
else:
|
|
43
|
+
target.unlink()
|
|
44
|
+
shutil.move(str(src), str(dst))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def copy(src: PathLike, dst: PathLike) -> None:
|
|
48
|
+
"""Copy files or directories to a destination.
|
|
49
|
+
|
|
50
|
+
If *src* contains glob wildcards, all matching files are copied into *dst*
|
|
51
|
+
(which must be a directory). Without wildcards, *dst* may be a directory
|
|
52
|
+
or a new file name.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
src : str or Path
|
|
57
|
+
Source glob pattern or single file/directory.
|
|
58
|
+
dst : str or Path
|
|
59
|
+
Destination directory or new name.
|
|
60
|
+
"""
|
|
61
|
+
src = Path(src)
|
|
62
|
+
dst = Path(dst)
|
|
63
|
+
if _has_glob(src):
|
|
64
|
+
for f in src.parent.glob(src.name):
|
|
65
|
+
_copy_single(f, dst / f.name)
|
|
66
|
+
else:
|
|
67
|
+
if not src.exists():
|
|
68
|
+
print(f"{src} does not exist")
|
|
69
|
+
return
|
|
70
|
+
if dst.is_dir():
|
|
71
|
+
_copy_single(src, dst / src.name)
|
|
72
|
+
else:
|
|
73
|
+
_copy_single(src, dst)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def delete(src: Union[PathLike, List[PathLike]]) -> None:
|
|
77
|
+
"""Delete files or directories matching glob patterns.
|
|
78
|
+
|
|
79
|
+
Handles both files (``unlink``) and directories (``rmtree``).
|
|
80
|
+
Silently skips paths that don't exist.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
src : str, Path, or list thereof
|
|
85
|
+
Glob pattern(s) or explicit path(s) to delete.
|
|
86
|
+
"""
|
|
87
|
+
patterns = src if isinstance(src, list) else [src]
|
|
88
|
+
for pattern in patterns:
|
|
89
|
+
p = Path(pattern)
|
|
90
|
+
if _has_glob(p):
|
|
91
|
+
matches = list(p.parent.glob(p.name))
|
|
92
|
+
else:
|
|
93
|
+
matches = [p] if p.exists() else []
|
|
94
|
+
for f in matches:
|
|
95
|
+
try:
|
|
96
|
+
if f.is_dir():
|
|
97
|
+
shutil.rmtree(f)
|
|
98
|
+
else:
|
|
99
|
+
f.unlink()
|
|
100
|
+
except OSError:
|
|
101
|
+
print(f"Could not delete {f}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def rename(src: PathLike, dst: PathLike) -> Path:
|
|
105
|
+
"""Rename a file or directory.
|
|
106
|
+
|
|
107
|
+
Works across drives on Windows (falls back to copy + delete).
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
src : str or Path
|
|
112
|
+
Source path.
|
|
113
|
+
dst : str or Path
|
|
114
|
+
New name or path.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
Path
|
|
119
|
+
The new path.
|
|
120
|
+
"""
|
|
121
|
+
src = Path(src)
|
|
122
|
+
dst = Path(dst)
|
|
123
|
+
try:
|
|
124
|
+
return src.rename(dst)
|
|
125
|
+
except OSError:
|
|
126
|
+
# Cross-drive rename on Windows
|
|
127
|
+
if src.is_dir():
|
|
128
|
+
shutil.copytree(src, dst)
|
|
129
|
+
shutil.rmtree(src)
|
|
130
|
+
else:
|
|
131
|
+
shutil.copy2(src, dst)
|
|
132
|
+
src.unlink()
|
|
133
|
+
return dst
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def mkdir(path: PathLike) -> Path:
|
|
137
|
+
"""Create a directory (including parents) if it does not exist.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
path : str or Path
|
|
142
|
+
Directory path to create.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
Path
|
|
147
|
+
The created directory path.
|
|
148
|
+
"""
|
|
149
|
+
p = Path(path)
|
|
150
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
return p
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def touch(path: PathLike) -> Path:
|
|
155
|
+
"""Create an empty file or update its modification timestamp.
|
|
156
|
+
|
|
157
|
+
Parent directories are created if needed.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
path : str or Path
|
|
162
|
+
File path.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
Path
|
|
167
|
+
The touched file path.
|
|
168
|
+
"""
|
|
169
|
+
p = Path(path)
|
|
170
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
p.touch()
|
|
172
|
+
return p
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def file_size(path: PathLike) -> int:
|
|
176
|
+
"""Return the size of a file in bytes.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
path : str or Path
|
|
181
|
+
File path.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
int
|
|
186
|
+
File size in bytes.
|
|
187
|
+
|
|
188
|
+
Raises
|
|
189
|
+
------
|
|
190
|
+
FileNotFoundError
|
|
191
|
+
If the file does not exist.
|
|
192
|
+
"""
|
|
193
|
+
return Path(path).stat().st_size
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def list_files(
|
|
197
|
+
src: PathLike,
|
|
198
|
+
pattern: str = "*",
|
|
199
|
+
recursive: bool = False,
|
|
200
|
+
full_path: bool = True,
|
|
201
|
+
) -> List[str]:
|
|
202
|
+
"""List files in a directory, optionally filtered by glob pattern.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
src : str or Path
|
|
207
|
+
Directory path, or glob pattern for backward compatibility
|
|
208
|
+
(e.g. ``"data/*.nc"``).
|
|
209
|
+
pattern : str
|
|
210
|
+
Glob pattern to filter files. Defaults to ``"*"`` (all files).
|
|
211
|
+
Ignored when *src* itself contains wildcards.
|
|
212
|
+
recursive : bool
|
|
213
|
+
If ``True``, search subdirectories recursively.
|
|
214
|
+
full_path : bool
|
|
215
|
+
If ``True``, return full paths. If ``False``, return basenames.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
List[str]
|
|
220
|
+
Sorted list of file paths or names.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
# Collapse consecutive wildcards — pathlib rejects "**" mixed with text
|
|
224
|
+
while "**" in pattern:
|
|
225
|
+
pattern = pattern.replace("**", "*")
|
|
226
|
+
p = Path(src)
|
|
227
|
+
if _has_glob(p):
|
|
228
|
+
# Legacy: src is a glob pattern like "data/*.nc"
|
|
229
|
+
name = p.name
|
|
230
|
+
while "**" in name:
|
|
231
|
+
name = name.replace("**", "*")
|
|
232
|
+
files = list(p.parent.glob(name))
|
|
233
|
+
elif recursive:
|
|
234
|
+
files = list(p.rglob(pattern))
|
|
235
|
+
else:
|
|
236
|
+
files = list(p.glob(pattern))
|
|
237
|
+
files = [f for f in files if f.is_file()]
|
|
238
|
+
if full_path:
|
|
239
|
+
return sorted(str(f) for f in files)
|
|
240
|
+
return sorted(f.name for f in files)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def list_folders(
|
|
244
|
+
src: PathLike, pattern: str = "*", basename: bool = False
|
|
245
|
+
) -> List[str]:
|
|
246
|
+
"""List subdirectories, optionally filtered by glob pattern.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
src : str or Path
|
|
251
|
+
Directory path, or glob pattern for backward compatibility.
|
|
252
|
+
pattern : str
|
|
253
|
+
Glob pattern to filter folders. Defaults to ``"*"``.
|
|
254
|
+
Ignored when *src* itself contains wildcards.
|
|
255
|
+
basename : bool
|
|
256
|
+
If ``True``, return only folder names instead of full paths.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
List[str]
|
|
261
|
+
Sorted list of folder paths or names.
|
|
262
|
+
"""
|
|
263
|
+
p = Path(src)
|
|
264
|
+
if _has_glob(p):
|
|
265
|
+
folders = list(p.parent.glob(p.name))
|
|
266
|
+
else:
|
|
267
|
+
folders = list(p.glob(pattern))
|
|
268
|
+
folders = [f for f in folders if f.is_dir()]
|
|
269
|
+
if basename:
|
|
270
|
+
return sorted(f.name for f in folders)
|
|
271
|
+
return sorted(str(f) for f in folders)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def exists(src: PathLike) -> bool:
|
|
275
|
+
"""Check if a file or directory exists.
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
src : str or Path
|
|
280
|
+
Path to check.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
bool
|
|
285
|
+
"""
|
|
286
|
+
return Path(src).exists()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def find_replace(file_name: PathLike, old: str, new: str) -> None:
|
|
290
|
+
"""Replace all occurrences of a string in a file.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
file_name : str or Path
|
|
295
|
+
File to modify in-place.
|
|
296
|
+
old : str
|
|
297
|
+
String to search for.
|
|
298
|
+
new : str
|
|
299
|
+
Replacement string.
|
|
300
|
+
"""
|
|
301
|
+
p = Path(file_name)
|
|
302
|
+
p.write_text(p.read_text().replace(old, new))
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ── Internal helpers ──────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _has_glob(p: Path) -> bool:
|
|
309
|
+
"""Check if a path contains glob wildcard characters."""
|
|
310
|
+
return any(c in str(p) for c in ("*", "?", "["))
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _copy_single(src: Path, dst: Path) -> None:
|
|
314
|
+
"""Copy a single file or directory to a destination path."""
|
|
315
|
+
if dst.exists():
|
|
316
|
+
if dst.is_dir():
|
|
317
|
+
shutil.rmtree(dst)
|
|
318
|
+
else:
|
|
319
|
+
dst.unlink()
|
|
320
|
+
if src.is_dir():
|
|
321
|
+
shutil.copytree(src, dst)
|
|
322
|
+
else:
|
|
323
|
+
shutil.copy2(src, dst)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ── Backward-compatible aliases ───────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
move_file = move
|
|
329
|
+
copy_file = copy
|
|
330
|
+
delete_file = delete
|
|
331
|
+
delete_folder = delete
|
|
332
|
+
rm = delete
|
|
333
|
+
rmdir = delete
|
|
334
|
+
findreplace = find_replace
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def list_all_files(src: PathLike) -> List[str]:
|
|
338
|
+
"""Backward-compatible alias for ``list_files(src, recursive=True)``."""
|
|
339
|
+
return list_files(src, recursive=True)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def list_files_recursive(src: PathLike, pattern: str = "*") -> List[str]:
|
|
343
|
+
"""Backward-compatible alias for ``list_files(src, pattern, recursive=True)``."""
|
|
344
|
+
return list_files(src, pattern=pattern, recursive=True)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Spatial interpolation utilities for regular and unstructured grids."""
|
|
2
|
+
|
|
3
|
+
from cht_utils.interpolation.interpolation import interp2 as interp2
|
|
4
|
+
from cht_utils.interpolation.interpolation import interp2_bilinear as interp2_bilinear
|
|
5
|
+
from cht_utils.interpolation.interpolation import interp3 as interp3
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Spatial interpolation utilities for regular and unstructured grids."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.interpolate import RegularGridInterpolator, griddata
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def interp2(
|
|
8
|
+
x0: np.ndarray,
|
|
9
|
+
y0: np.ndarray,
|
|
10
|
+
z0: np.ndarray,
|
|
11
|
+
x1: np.ndarray,
|
|
12
|
+
y1: np.ndarray,
|
|
13
|
+
method: str = "linear",
|
|
14
|
+
) -> np.ndarray:
|
|
15
|
+
"""Interpolate from a regular grid to arbitrary points.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
x0 : np.ndarray
|
|
20
|
+
1-D array of x-coordinates of the source grid.
|
|
21
|
+
y0 : np.ndarray
|
|
22
|
+
1-D array of y-coordinates of the source grid.
|
|
23
|
+
z0 : np.ndarray
|
|
24
|
+
2-D array of values on the source grid, shape ``(len(y0), len(x0))``.
|
|
25
|
+
x1 : np.ndarray
|
|
26
|
+
Target x-coordinates (1-D or 2-D).
|
|
27
|
+
y1 : np.ndarray
|
|
28
|
+
Target y-coordinates (1-D or 2-D).
|
|
29
|
+
method : str
|
|
30
|
+
Interpolation method (``"linear"``, ``"nearest"``).
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
np.ndarray
|
|
35
|
+
Interpolated values at the target locations.
|
|
36
|
+
"""
|
|
37
|
+
f = RegularGridInterpolator(
|
|
38
|
+
(y0, x0), z0, bounds_error=False, fill_value=np.nan, method=method
|
|
39
|
+
)
|
|
40
|
+
if x1.ndim > 1:
|
|
41
|
+
sz = x1.shape
|
|
42
|
+
z1 = f((y1.ravel(), x1.ravel())).reshape(sz)
|
|
43
|
+
else:
|
|
44
|
+
z1 = f((y1, x1))
|
|
45
|
+
return z1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def interp2_bilinear(
|
|
49
|
+
xp: np.ndarray,
|
|
50
|
+
yp: np.ndarray,
|
|
51
|
+
zp: np.ndarray,
|
|
52
|
+
x: np.ndarray,
|
|
53
|
+
y: np.ndarray,
|
|
54
|
+
) -> np.ndarray:
|
|
55
|
+
"""Bilinear interpolation on a regular grid.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
xp : np.ndarray
|
|
60
|
+
1-D array of source x-coordinates (monotonically increasing).
|
|
61
|
+
yp : np.ndarray
|
|
62
|
+
1-D array of source y-coordinates (monotonically increasing).
|
|
63
|
+
zp : np.ndarray
|
|
64
|
+
2-D source values, shape ``(len(yp), len(xp))``.
|
|
65
|
+
x : np.ndarray
|
|
66
|
+
Target x-coordinates.
|
|
67
|
+
y : np.ndarray
|
|
68
|
+
Target y-coordinates.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
np.ndarray
|
|
73
|
+
Interpolated values at the target locations.
|
|
74
|
+
"""
|
|
75
|
+
dx = xp[1] - xp[0]
|
|
76
|
+
dy = yp[1] - yp[0]
|
|
77
|
+
|
|
78
|
+
col = (x - xp[0]) / dx
|
|
79
|
+
row = (y - yp[0]) / dy
|
|
80
|
+
|
|
81
|
+
c0 = np.floor(col).astype(int)
|
|
82
|
+
r0 = np.floor(row).astype(int)
|
|
83
|
+
c1 = c0 + 1
|
|
84
|
+
r1 = r0 + 1
|
|
85
|
+
|
|
86
|
+
# Clip to valid range
|
|
87
|
+
c0 = np.clip(c0, 0, len(xp) - 1)
|
|
88
|
+
c1 = np.clip(c1, 0, len(xp) - 1)
|
|
89
|
+
r0 = np.clip(r0, 0, len(yp) - 1)
|
|
90
|
+
r1 = np.clip(r1, 0, len(yp) - 1)
|
|
91
|
+
|
|
92
|
+
# Fractional parts
|
|
93
|
+
dc = col - np.floor(col)
|
|
94
|
+
dr = row - np.floor(row)
|
|
95
|
+
|
|
96
|
+
z = (
|
|
97
|
+
zp[r0, c0] * (1 - dc) * (1 - dr)
|
|
98
|
+
+ zp[r0, c1] * dc * (1 - dr)
|
|
99
|
+
+ zp[r1, c0] * (1 - dc) * dr
|
|
100
|
+
+ zp[r1, c1] * dc * dr
|
|
101
|
+
)
|
|
102
|
+
return z
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def interp3(
|
|
106
|
+
x0: np.ndarray,
|
|
107
|
+
y0: np.ndarray,
|
|
108
|
+
z0: np.ndarray,
|
|
109
|
+
x1: np.ndarray,
|
|
110
|
+
y1: np.ndarray,
|
|
111
|
+
method: str = "linear",
|
|
112
|
+
) -> np.ndarray:
|
|
113
|
+
"""Interpolate from unstructured or curvilinear points.
|
|
114
|
+
|
|
115
|
+
Uses :func:`scipy.interpolate.griddata` for scattered-data interpolation.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
x0 : np.ndarray
|
|
120
|
+
Source x-coordinates (1-D or 2-D).
|
|
121
|
+
y0 : np.ndarray
|
|
122
|
+
Source y-coordinates (1-D or 2-D).
|
|
123
|
+
z0 : np.ndarray
|
|
124
|
+
Source values.
|
|
125
|
+
x1 : np.ndarray
|
|
126
|
+
Target x-coordinates (1-D or 2-D).
|
|
127
|
+
y1 : np.ndarray
|
|
128
|
+
Target y-coordinates (1-D or 2-D).
|
|
129
|
+
method : str
|
|
130
|
+
Interpolation method (``"linear"``, ``"nearest"``, ``"cubic"``).
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
np.ndarray
|
|
135
|
+
Interpolated values at the target locations.
|
|
136
|
+
"""
|
|
137
|
+
if x1.ndim > 1:
|
|
138
|
+
sz = x1.shape
|
|
139
|
+
z1 = griddata(
|
|
140
|
+
(x0.ravel(), y0.ravel()),
|
|
141
|
+
z0.ravel(),
|
|
142
|
+
(x1.ravel(), y1.ravel()),
|
|
143
|
+
method=method,
|
|
144
|
+
).reshape(sz)
|
|
145
|
+
else:
|
|
146
|
+
z1 = griddata(
|
|
147
|
+
(x0.ravel(), y0.ravel()),
|
|
148
|
+
z0.ravel(),
|
|
149
|
+
(x1, y1),
|
|
150
|
+
method=method,
|
|
151
|
+
)
|
|
152
|
+
return z1
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""File and directory operations for copying, moving, deleting, and listing paths."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import glob
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def move_file(src: str, dst: str) -> None:
|
|
14
|
+
"""Move files matching a glob pattern to a destination directory.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
src : str
|
|
19
|
+
Glob pattern for source files.
|
|
20
|
+
dst : str
|
|
21
|
+
Destination directory path.
|
|
22
|
+
"""
|
|
23
|
+
for full_file_name in glob.glob(src):
|
|
24
|
+
src_name = os.path.basename(full_file_name)
|
|
25
|
+
if os.path.exists(os.path.join(dst, src_name)):
|
|
26
|
+
try:
|
|
27
|
+
os.remove(os.path.join(dst, src_name))
|
|
28
|
+
except Exception:
|
|
29
|
+
logger.error(f"Could not remove file {os.path.join(dst, src_name)}")
|
|
30
|
+
try:
|
|
31
|
+
shutil.move(full_file_name, dst)
|
|
32
|
+
except Exception:
|
|
33
|
+
logger.error(f"Could not move file {full_file_name}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def copy_file(src: str, dst: str) -> None:
|
|
37
|
+
"""Copy files matching a glob pattern to a destination directory.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
src : str
|
|
42
|
+
Glob pattern for source files.
|
|
43
|
+
dst : str
|
|
44
|
+
Destination directory path.
|
|
45
|
+
"""
|
|
46
|
+
for full_file_name in glob.glob(src):
|
|
47
|
+
src_name = os.path.basename(full_file_name)
|
|
48
|
+
if os.path.exists(os.path.join(dst, src_name)):
|
|
49
|
+
os.remove(os.path.join(dst, src_name))
|
|
50
|
+
if os.path.isdir(full_file_name):
|
|
51
|
+
dstf = os.path.join(dst, os.path.basename(full_file_name))
|
|
52
|
+
shutil.copytree(full_file_name, dstf)
|
|
53
|
+
else:
|
|
54
|
+
shutil.copy(full_file_name, dst)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def delete_file(src: str) -> None:
|
|
58
|
+
"""Delete files matching a glob pattern.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
src : str
|
|
63
|
+
Glob pattern for files to delete.
|
|
64
|
+
"""
|
|
65
|
+
for file_name in glob.glob(src):
|
|
66
|
+
try:
|
|
67
|
+
os.remove(src)
|
|
68
|
+
except Exception:
|
|
69
|
+
logger.error(f"Could not delete {src}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def rm(src: str) -> None:
|
|
73
|
+
"""Remove a single file.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
src : str
|
|
78
|
+
Path to the file to remove.
|
|
79
|
+
"""
|
|
80
|
+
os.remove(src)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def mkdir(path: str) -> None:
|
|
84
|
+
"""Create a directory (and parents) if it does not already exist.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
path : str
|
|
89
|
+
Directory path to create.
|
|
90
|
+
"""
|
|
91
|
+
if not os.path.exists(path):
|
|
92
|
+
os.makedirs(path)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def list_files(src: str, full_path: bool = True) -> list[str]:
|
|
96
|
+
"""List files matching a glob pattern or directory listing.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
src : str
|
|
101
|
+
Glob pattern (when *full_path* is True) or directory path (when False).
|
|
102
|
+
full_path : bool
|
|
103
|
+
If True, use glob and return full paths. If False, use ``os.listdir``.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
list[str]
|
|
108
|
+
Sorted list of file paths or names.
|
|
109
|
+
"""
|
|
110
|
+
if full_path:
|
|
111
|
+
file_list = []
|
|
112
|
+
full_list = glob.glob(src)
|
|
113
|
+
for item in full_list:
|
|
114
|
+
if os.path.isfile(item):
|
|
115
|
+
file_list.append(item)
|
|
116
|
+
else:
|
|
117
|
+
file_list = os.listdir(src)
|
|
118
|
+
|
|
119
|
+
return sorted(file_list)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def list_folders(src: str, basename: bool = False) -> list[str]:
|
|
123
|
+
"""List directories matching a glob pattern.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
src : str
|
|
128
|
+
Glob pattern.
|
|
129
|
+
basename : bool
|
|
130
|
+
If True, return only base names instead of full paths.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
list[str]
|
|
135
|
+
Sorted list of directory paths (or base names).
|
|
136
|
+
"""
|
|
137
|
+
folder_list = []
|
|
138
|
+
full_list = glob.glob(src)
|
|
139
|
+
for item in full_list:
|
|
140
|
+
if os.path.isdir(item):
|
|
141
|
+
if basename:
|
|
142
|
+
folder_list.append(os.path.basename(item))
|
|
143
|
+
else:
|
|
144
|
+
folder_list.append(item)
|
|
145
|
+
|
|
146
|
+
return sorted(folder_list)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def delete_folder(src: str) -> None:
|
|
150
|
+
"""Recursively delete a directory tree.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
src : str
|
|
155
|
+
Path to the directory to delete.
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
shutil.rmtree(src, ignore_errors=False, onerror=None)
|
|
159
|
+
except Exception:
|
|
160
|
+
logger.error(f"Could not delete folder {src}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def rmdir(src: str) -> None:
|
|
164
|
+
"""Recursively delete a directory tree if it exists.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
src : str
|
|
169
|
+
Path to the directory to delete.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
if os.path.exists(src):
|
|
173
|
+
shutil.rmtree(src, ignore_errors=False, onerror=None)
|
|
174
|
+
except Exception:
|
|
175
|
+
logger.error(f"Could not delete folder {src}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def exists(src: str) -> bool:
|
|
179
|
+
"""Check whether a path exists.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
src : str
|
|
184
|
+
Path to check.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
bool
|
|
189
|
+
True if the path exists.
|
|
190
|
+
"""
|
|
191
|
+
return os.path.exists(src)
|