colindex2 2.10.5__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.
- colindex2/__init__.py +8 -0
- colindex2/circle_overlap.py +115 -0
- colindex2/colindex2.py +319 -0
- colindex2/commands.py +269 -0
- colindex2/data_settings.py +56 -0
- colindex2/draw_map.py +268 -0
- colindex2/get_IDfile.py +340 -0
- colindex2/modules.py +649 -0
- colindex2/tracking_overlap2.py +1103 -0
- colindex2-2.10.5.dist-info/METADATA +28 -0
- colindex2-2.10.5.dist-info/RECORD +15 -0
- colindex2-2.10.5.dist-info/WHEEL +5 -0
- colindex2-2.10.5.dist-info/entry_points.txt +6 -0
- colindex2-2.10.5.dist-info/licenses/LICENSE +21 -0
- colindex2-2.10.5.dist-info/top_level.txt +1 -0
colindex2/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .colindex2 import Detect
|
|
2
|
+
from .commands import detect, track, gen_data_settings, find_track
|
|
3
|
+
from .tracking_overlap2 import Track
|
|
4
|
+
from .get_IDfile import Finder
|
|
5
|
+
from .draw_map import _draw_map, draw_ro
|
|
6
|
+
|
|
7
|
+
from .modules import gcd
|
|
8
|
+
from .modules import invert_gcd2
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def area_heron_x2(a, b, c):
|
|
7
|
+
s = (a+b+c)/2
|
|
8
|
+
if min(s-a, s-b, s-c) <= 0:
|
|
9
|
+
return np.nan
|
|
10
|
+
else:
|
|
11
|
+
return np.sqrt(s*(s-a)*(s-b)*(s-c))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class circle:
|
|
15
|
+
def __init__(self, r, x):
|
|
16
|
+
self.r = r
|
|
17
|
+
self.x = x
|
|
18
|
+
self.S = np.pi * r * r
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class circle_overlap:
|
|
22
|
+
def __init__(self, c1, c2, d):
|
|
23
|
+
|
|
24
|
+
fct = 1
|
|
25
|
+
S_square = area_heron_x2(c1.r, c2.r, d)
|
|
26
|
+
|
|
27
|
+
if np.isnan(S_square): # cannot make inner triangle
|
|
28
|
+
|
|
29
|
+
alpha1 = np.nan
|
|
30
|
+
d1 = np.nan
|
|
31
|
+
h = np.nan
|
|
32
|
+
|
|
33
|
+
if d < c1.r:
|
|
34
|
+
overlap = 1.
|
|
35
|
+
else:
|
|
36
|
+
overlap = 0.
|
|
37
|
+
|
|
38
|
+
else: # can make inner triangle
|
|
39
|
+
# https://tjkendev.github.io/procon-library/python/geometry/circles_intersection_area.html
|
|
40
|
+
|
|
41
|
+
theta1 = np.arccos((c1.r**2 + d**2 - c2.r**2) / (2 * c1.r * d))
|
|
42
|
+
theta2 = np.arccos((c2.r**2 + d**2 - c1.r**2) / (2 * c2.r * d))
|
|
43
|
+
|
|
44
|
+
S_fan = c1.r**2 * theta1 + c2.r**2 * theta2
|
|
45
|
+
S_and = S_fan - S_square
|
|
46
|
+
|
|
47
|
+
# complete overlap ratio
|
|
48
|
+
S_or = c1.S + c2.S - S_and
|
|
49
|
+
overlap = min(S_and / S_or, 1.0)
|
|
50
|
+
|
|
51
|
+
# smaller-side overlap ratio
|
|
52
|
+
#S_small = min(c1.S, c2.S)
|
|
53
|
+
#overlap = min(S_and / S_small, 1.0)
|
|
54
|
+
|
|
55
|
+
self.overlap = overlap
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test():
|
|
59
|
+
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
import matplotlib.patches as patches
|
|
62
|
+
|
|
63
|
+
r1, r2 = 500, 600
|
|
64
|
+
|
|
65
|
+
ds = np.arange(100, 1151, 50)
|
|
66
|
+
overs = []
|
|
67
|
+
alpha1s = []
|
|
68
|
+
d1s = []
|
|
69
|
+
hs = []
|
|
70
|
+
|
|
71
|
+
for d in ds:
|
|
72
|
+
|
|
73
|
+
c1 = circle(r=r1, x=0)
|
|
74
|
+
c2 = circle(r=r2, x=d)
|
|
75
|
+
o = cirlce_overlap(c1, c2, d)
|
|
76
|
+
overs.append(o.overlap)
|
|
77
|
+
alpha1s.append(o.alpha1)
|
|
78
|
+
d1s.append(o.d1)
|
|
79
|
+
hs.append(o.h)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
for i, d in enumerate(ds):
|
|
83
|
+
|
|
84
|
+
print(f'{d=}')
|
|
85
|
+
|
|
86
|
+
ax = plt.axes()
|
|
87
|
+
C1 = patches.Circle(xy=(0, 500), radius=r1, fc='none', ec='tab:blue')
|
|
88
|
+
C2 = patches.Circle(xy=(d, 500), radius=r2, fc='none', ec='tab:blue')
|
|
89
|
+
ax.add_patch(C1)
|
|
90
|
+
ax.add_patch(C2)
|
|
91
|
+
ax.plot(0, 500, marker='.', c='tab:blue')
|
|
92
|
+
ax.plot(d, 500, marker='.', c='tab:blue')
|
|
93
|
+
ax.plot([d1s[i], d1s[i]], [500-hs[i], 500+hs[i]], c='r')
|
|
94
|
+
ax.set_aspect('equal')
|
|
95
|
+
ax.set_xlim([-700,1250])
|
|
96
|
+
ax.set_ylim([-200, 1200])
|
|
97
|
+
ax.set_title(f'O={overs[i]:.3f}')
|
|
98
|
+
plt.savefig(f'fig/cc_d{d}.png')
|
|
99
|
+
plt.close()
|
|
100
|
+
|
|
101
|
+
ax = plt.axes()
|
|
102
|
+
ax.plot(ds, overs)
|
|
103
|
+
ax.plot(ds[i], overs[i], '.r')
|
|
104
|
+
plt.savefig(f'fig/overs_{d}.png')
|
|
105
|
+
plt.close()
|
|
106
|
+
|
|
107
|
+
'''ax = plt.axes()
|
|
108
|
+
ax.plot(ds, np.array(alpha1s)/np.pi)
|
|
109
|
+
ax.plot(ds[i], alpha1s[i]/np.pi, '.r')
|
|
110
|
+
plt.savefig(f'fig/alpha1s_d{d}.png')
|
|
111
|
+
plt.close()'''
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == '__main__':
|
|
115
|
+
test()
|
colindex2/colindex2.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import xarray as xr
|
|
5
|
+
#import multiprocessing as mp
|
|
6
|
+
import warnings
|
|
7
|
+
warnings.filterwarnings('ignore')
|
|
8
|
+
|
|
9
|
+
from .modules import check_gpu
|
|
10
|
+
from .modules import _calc_as_torch
|
|
11
|
+
|
|
12
|
+
class Detect():
|
|
13
|
+
"""
|
|
14
|
+
Depression detector by searching each optimal location, intensity, and size from 2D fields.
|
|
15
|
+
See puplication for details (Kasuga et al. 2021; K21) and (Kasuga and Honda 2025; KH25).
|
|
16
|
+
|
|
17
|
+
----
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
-----------
|
|
21
|
+
|
|
22
|
+
da : xarray.DataArray or numpy.ndarray
|
|
23
|
+
The target 2D field with (or without) time axis.
|
|
24
|
+
The dimmension order must be time->latitude->longitude.
|
|
25
|
+
|
|
26
|
+
odir : str, default './d01'
|
|
27
|
+
Parent output directory path. By defaults, output data will be stored as follows
|
|
28
|
+
Outputs will be produced in {odir}/AS (AS field) and {odir}/V (detected point values).
|
|
29
|
+
|
|
30
|
+
ty : str, {'both', 'L', 'H'}, default 'both'
|
|
31
|
+
Type of vortices to be detected.
|
|
32
|
+
|
|
33
|
+
r : np.array, default np.arange(200, 2101, 100) (K21)
|
|
34
|
+
Radial searching variable for average slop function (AS) [km].
|
|
35
|
+
Set range to include scales of which phenomena you are interested in.
|
|
36
|
+
|
|
37
|
+
SR_thres : float, default 3. (K21)
|
|
38
|
+
Threshold to remove noises with respect to the slope ratio.
|
|
39
|
+
|
|
40
|
+
So_thres : float, default 3. (KH25)
|
|
41
|
+
Threshold to remove noises with respect to the intensity [m/100km].
|
|
42
|
+
In K21, ``10`` and ``5`` was used for climatology for cutoff lows and preexisting troughs, respectively.
|
|
43
|
+
|
|
44
|
+
Do_thres : float, default 0.
|
|
45
|
+
Threshold to remove noises with respect to the depth [m].
|
|
46
|
+
A large value (e.g., 10.) reduces small-size noise, which might be helpful for surface low/high detection.
|
|
47
|
+
|
|
48
|
+
xx_thres : float, default 0.5
|
|
49
|
+
Threshold to remove noises with respect to the zonal laplacan [m/(100 km)^2].
|
|
50
|
+
This works for high detection
|
|
51
|
+
by removeing large and weak ridges located south of the subtropical jet.
|
|
52
|
+
|
|
53
|
+
rm_rmin : bool, default True
|
|
54
|
+
Detected point will be removed if ro is minimum of r because this may be too small.
|
|
55
|
+
True in K21 and KH25.
|
|
56
|
+
|
|
57
|
+
rm_rmax : bool, default False
|
|
58
|
+
Detected point will be removed if ro is maximum of r because this may be too large.
|
|
59
|
+
True in K21 and False in KH25.
|
|
60
|
+
|
|
61
|
+
factor : float, default 100.
|
|
62
|
+
Factor to convert units of So and AS. Default convert m/km -> m/100 km for geopotential height input.
|
|
63
|
+
|
|
64
|
+
local_ex_req_num : int, default 7.
|
|
65
|
+
Number required in local extremum condition (N).
|
|
66
|
+
The conditions of minimum/maximum are defined in a 3x3 grid box as at least N points (out of 8 points)
|
|
67
|
+
must greater/smaller than and equal to the center value.
|
|
68
|
+
|
|
69
|
+
gpu : bool, default False
|
|
70
|
+
Note, this feature is experimental.
|
|
71
|
+
If True, the processes wil be computed with gpu processing via CUDA or METAL.
|
|
72
|
+
|
|
73
|
+
nc : bool, default True
|
|
74
|
+
If True, netcdf file will be created for AS. If you want grads binary outputs, set False.
|
|
75
|
+
|
|
76
|
+
fmt : str, default '<f4'
|
|
77
|
+
Output binary format when nc=False. The format is correspond to dtype in numpy.
|
|
78
|
+
|
|
79
|
+
subs : bool, default False
|
|
80
|
+
If True, 2D fields of ro, m, and n are produced.
|
|
81
|
+
|
|
82
|
+
t : pandas.DatetimeIndex, default [pd.Timestamp('1000-01-01 00:00')]
|
|
83
|
+
Time of data. Option for numpy.ndarray input.
|
|
84
|
+
|
|
85
|
+
lev : int, default 0
|
|
86
|
+
Level of data. Option for numpy.ndarray input. This will be used in the name of output files.
|
|
87
|
+
Note: Float level will be converted to integer.
|
|
88
|
+
|
|
89
|
+
lons : list or numpy.ndarray, default [-999.]
|
|
90
|
+
Longitude list. Option for numpy.ndarray input. Set like lon=np.arange(0, 360, 1.25).
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
lats : list or numpy.ndarray, default [-999.]
|
|
94
|
+
Option for numpy.ndarray input. Set like lon=np.arange(-90, 90, 1.25).
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self,
|
|
99
|
+
da, # 2D DataArray or ndarray
|
|
100
|
+
odir='./d01', # output parent dir
|
|
101
|
+
ty="both",
|
|
102
|
+
r=np.arange(200, 2101, 100), # searching radius variable
|
|
103
|
+
SR_thres=3., # shape threshold
|
|
104
|
+
So_thres=3., # intensity threshold
|
|
105
|
+
Do_thres=0., # depth threshold
|
|
106
|
+
xx_thres=.5, # zonal concavity thershold
|
|
107
|
+
rm_rmin=True, # remove minimum size
|
|
108
|
+
rm_rmax=False, # remove maximum size
|
|
109
|
+
factor=100., # factor of So m/km -> m/100km
|
|
110
|
+
local_ex_req_num=7, # out of 8 points
|
|
111
|
+
gpu=False, # gpu calculation
|
|
112
|
+
nc=True, # netcdf outputs otherwise grads bin
|
|
113
|
+
fmt='<f4', # binary format when nc=False
|
|
114
|
+
subs=False, # output intermediate fields
|
|
115
|
+
lev=0, # for ndarray input below
|
|
116
|
+
t=[pd.Timestamp('1700-01-01 00:00')],
|
|
117
|
+
lons=[-999.],
|
|
118
|
+
lats=[-999.],
|
|
119
|
+
):
|
|
120
|
+
|
|
121
|
+
# constants
|
|
122
|
+
self.rr = 6371. # earth radius [km]
|
|
123
|
+
self.g = 9.8 # gravity [m/ss]
|
|
124
|
+
self.factor = factor
|
|
125
|
+
|
|
126
|
+
self.ty = ty
|
|
127
|
+
self.r = np.array(r)
|
|
128
|
+
|
|
129
|
+
self.da = da
|
|
130
|
+
self.odir = odir
|
|
131
|
+
self.SR_thres = SR_thres
|
|
132
|
+
self.So_thres = So_thres
|
|
133
|
+
self.Do_thres = Do_thres
|
|
134
|
+
self.xx_thres = xx_thres
|
|
135
|
+
self.thres4 = [So_thres, Do_thres, SR_thres, xx_thres]
|
|
136
|
+
self.rm_rmin = rm_rmin
|
|
137
|
+
self.rm_rmax = rm_rmax
|
|
138
|
+
self.local_ex_req_num = local_ex_req_num
|
|
139
|
+
|
|
140
|
+
self.lons = lons
|
|
141
|
+
self.lats = lats
|
|
142
|
+
self.t = t
|
|
143
|
+
self.lev = lev
|
|
144
|
+
|
|
145
|
+
self.gpu = gpu
|
|
146
|
+
self.nc = nc
|
|
147
|
+
self.fmt = fmt
|
|
148
|
+
self.subs = subs
|
|
149
|
+
|
|
150
|
+
# main process
|
|
151
|
+
self._main()
|
|
152
|
+
|
|
153
|
+
# maxe a text about detection parameters
|
|
154
|
+
self._save_param_text()
|
|
155
|
+
|
|
156
|
+
print("done!")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _set_coords(self):
|
|
160
|
+
|
|
161
|
+
da = self.da
|
|
162
|
+
lons = self.lons
|
|
163
|
+
lats = self.lats
|
|
164
|
+
lev = self.lev
|
|
165
|
+
t = self.t
|
|
166
|
+
if isinstance(da, xr.DataArray):
|
|
167
|
+
|
|
168
|
+
_dims = [k for k, v in da.coords.items()] # this may include squeezed dims
|
|
169
|
+
|
|
170
|
+
if 'valid_time' in _dims:
|
|
171
|
+
tt = pd.to_datetime(da.valid_time.values)
|
|
172
|
+
elif 'time' in _dims:
|
|
173
|
+
tt = pd.to_datetime(da.time.values)
|
|
174
|
+
|
|
175
|
+
if isinstance(tt, pd.Timestamp):
|
|
176
|
+
self.t = [tt] # must be iterable
|
|
177
|
+
self.da = da.values[np.newaxis, ...]
|
|
178
|
+
else:
|
|
179
|
+
self.t = tt # pd.DatetimeIndex or default list
|
|
180
|
+
self.da = da.values
|
|
181
|
+
|
|
182
|
+
if 'level' in _dims:
|
|
183
|
+
self.lev = da.level.values
|
|
184
|
+
elif 'lev' in _dims:
|
|
185
|
+
self.lev = da.lev.values
|
|
186
|
+
elif 'levs' in _dims:
|
|
187
|
+
self.lev = da.levs.values
|
|
188
|
+
elif 'isobaricInhPa' in _dims:
|
|
189
|
+
self.lev = da.isobaricInhPa.values
|
|
190
|
+
|
|
191
|
+
if 'longitude' in _dims:
|
|
192
|
+
self.lon = da.longitude.values
|
|
193
|
+
if 'lon' in _dims:
|
|
194
|
+
self.lon = da.lon.values
|
|
195
|
+
if 'lons' in _dims:
|
|
196
|
+
self.lon = da.lons.values
|
|
197
|
+
|
|
198
|
+
if 'latitude' in _dims:
|
|
199
|
+
self.lat = da.latitude.values
|
|
200
|
+
if 'lat' in _dims:
|
|
201
|
+
self.lat = da.lat.values
|
|
202
|
+
if 'lats' in _dims:
|
|
203
|
+
self.lat = da.lats.values
|
|
204
|
+
|
|
205
|
+
elif isinstance(da, np.ndarray):
|
|
206
|
+
|
|
207
|
+
if len(lons) != da.shape[-1] or len(lats) != da.shape[-2]:
|
|
208
|
+
raise ValueError('shape of lons or lats not fit input')
|
|
209
|
+
else:
|
|
210
|
+
self.t = t
|
|
211
|
+
self.lev = lev
|
|
212
|
+
self.lon = lons
|
|
213
|
+
self.lat = lats
|
|
214
|
+
self.da = da
|
|
215
|
+
|
|
216
|
+
if len(self.t) == 1:
|
|
217
|
+
self.da = [self.da]
|
|
218
|
+
self.t = [self.t]
|
|
219
|
+
|
|
220
|
+
else:
|
|
221
|
+
raise ValueError('wrong input data type, must be xr.DataArray or np.ndarray')
|
|
222
|
+
|
|
223
|
+
if isinstance(self.lev, np.ndarray):
|
|
224
|
+
self.lev = self.lev.item()
|
|
225
|
+
|
|
226
|
+
self.lev = int(self.lev)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def check_zonal_loop(self):
|
|
231
|
+
|
|
232
|
+
lon1 = self.lon[1]
|
|
233
|
+
lon1n = self.lon[-1]
|
|
234
|
+
lon0 = self.lon[0]
|
|
235
|
+
dlon = lon1 - lon0
|
|
236
|
+
|
|
237
|
+
if lon1n == lon0:
|
|
238
|
+
raise ValueError("longitude is already looped. use non-looped data")
|
|
239
|
+
|
|
240
|
+
l = lon1n + dlon
|
|
241
|
+
|
|
242
|
+
if l == lon0 or l+360 == lon0 or l-360 == lon0:
|
|
243
|
+
self.zloop = True
|
|
244
|
+
else:
|
|
245
|
+
self.zloop = False
|
|
246
|
+
|
|
247
|
+
def check_increasing_lat(self):
|
|
248
|
+
|
|
249
|
+
dlat = self.lat[1] - self.lat[0]
|
|
250
|
+
|
|
251
|
+
if dlat < 0.:
|
|
252
|
+
self.da = self.da[...,::-1,:].copy()
|
|
253
|
+
self.lat = self.lat[::-1].copy()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _main(self):
|
|
257
|
+
|
|
258
|
+
# set coordinates of data
|
|
259
|
+
self._set_coords()
|
|
260
|
+
|
|
261
|
+
# check gpu is available
|
|
262
|
+
self.device = check_gpu(self.gpu)
|
|
263
|
+
|
|
264
|
+
# check whether data longitude is looped
|
|
265
|
+
# and increasing latitude
|
|
266
|
+
self.check_zonal_loop()
|
|
267
|
+
self.check_increasing_lat()
|
|
268
|
+
|
|
269
|
+
print("level:", self.lev)
|
|
270
|
+
print("calculation:", self.device)
|
|
271
|
+
print("-----------------------")
|
|
272
|
+
|
|
273
|
+
# start main process
|
|
274
|
+
args = [(
|
|
275
|
+
self.odir,
|
|
276
|
+
self.ty,
|
|
277
|
+
self.da[i],
|
|
278
|
+
self.lon,
|
|
279
|
+
self.lat,
|
|
280
|
+
self.lev,
|
|
281
|
+
self.t[i],
|
|
282
|
+
self.r,
|
|
283
|
+
self.thres4,
|
|
284
|
+
self.rm_rmin,
|
|
285
|
+
self.rm_rmax,
|
|
286
|
+
self.factor,
|
|
287
|
+
self.zloop,
|
|
288
|
+
self.device,
|
|
289
|
+
self.nc,
|
|
290
|
+
self.fmt,
|
|
291
|
+
self.subs,
|
|
292
|
+
self.local_ex_req_num,
|
|
293
|
+
) for i in range(len(self.t))]
|
|
294
|
+
|
|
295
|
+
#with mp.Pool(self.nproc) as p:
|
|
296
|
+
# p.map(_calc_as_torch, args)
|
|
297
|
+
for x in args:
|
|
298
|
+
_calc_as_torch(x)
|
|
299
|
+
|
|
300
|
+
def _save_param_text(self):
|
|
301
|
+
from datetime import datetime
|
|
302
|
+
import getpass
|
|
303
|
+
import socket
|
|
304
|
+
now = datetime.now()
|
|
305
|
+
user = getpass.getuser()
|
|
306
|
+
host = socket.gethostname()
|
|
307
|
+
with open(f"{self.odir}/_detection_parms.txt", "w", encoding="utf-8") as f:
|
|
308
|
+
print("r:", self.r, file=f)
|
|
309
|
+
print("SR_thres:", self.SR_thres, file=f)
|
|
310
|
+
print("So_thres:", self.So_thres, file=f)
|
|
311
|
+
print("Do_thres:", self.Do_thres, file=f)
|
|
312
|
+
print("xx_thres:", self.xx_thres, file=f)
|
|
313
|
+
print("rm_rmin:", self.rm_rmin, file=f)
|
|
314
|
+
print("rm_rmax:", self.rm_rmax, file=f)
|
|
315
|
+
print("factor:", self.factor, file=f)
|
|
316
|
+
print("local_ex_req_num:", self.local_ex_req_num, file=f)
|
|
317
|
+
print("calculation device:", self.device, file=f)
|
|
318
|
+
print("", file=f)
|
|
319
|
+
print("executed in", now, "by", user, "@", host, file=f)
|
colindex2/commands.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import sys, os
|
|
2
|
+
import argparse
|
|
3
|
+
import importlib.util
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import xarray as xr
|
|
7
|
+
from .colindex2 import Detect
|
|
8
|
+
from .tracking_overlap2 import Track
|
|
9
|
+
from .get_IDfile import Finder
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import importlib.resources
|
|
13
|
+
|
|
14
|
+
def gen_data_settings():
|
|
15
|
+
with importlib.resources.path("colindex2", "data_settings.py") as file_path:
|
|
16
|
+
dest = Path.cwd() / "data_settings.py"
|
|
17
|
+
shutil.copy(file_path, dest)
|
|
18
|
+
|
|
19
|
+
def _load_local_module(path):
|
|
20
|
+
if not os.path.exists(path):
|
|
21
|
+
raise FileNotFoundError(f"{path} has not generated yet. Use gen_data_settings command")
|
|
22
|
+
spec = importlib.util.spec_from_file_location("data_settings", path)
|
|
23
|
+
module = importlib.util.module_from_spec(spec)
|
|
24
|
+
sys.modules["data_settings"] = module
|
|
25
|
+
spec.loader.exec_module(module)
|
|
26
|
+
return module
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def detect():
|
|
30
|
+
description = """
|
|
31
|
+
Run detection of colindex.
|
|
32
|
+
Before run, generate data_settings.py with 'gen_data_settings' command and edit it.
|
|
33
|
+
|
|
34
|
+
$ gen_data_settings
|
|
35
|
+
|
|
36
|
+
Then execute below for netcdf input.
|
|
37
|
+
|
|
38
|
+
$ detect path/to/file.nc
|
|
39
|
+
|
|
40
|
+
For binary input, specify start/end times (st,et) and frequency of timesteps (freq).
|
|
41
|
+
|
|
42
|
+
$ detect path/to/file.grd -st "yyyy-mm-dd hh" -et "yyyy-mm-dd hh" -freq 6h
|
|
43
|
+
|
|
44
|
+
Option -track execute also tracking after detection.
|
|
45
|
+
In this case, 'tracking_times' in data_settings.py is ignored,
|
|
46
|
+
and the detection timesteps will be used.
|
|
47
|
+
|
|
48
|
+
$ detect path/to/file.nc -track
|
|
49
|
+
$ detect path/to/file.grd -st "yyyy-mm-dd hh" -et "yyyy-mm-dd hh" -freq 6h -track
|
|
50
|
+
|
|
51
|
+
Result data will be saved in output_dir/V output_dir/AS.
|
|
52
|
+
"""
|
|
53
|
+
#Option -n can set number of multiprocess (default 4)
|
|
54
|
+
#
|
|
55
|
+
# $ detect path/to/file.nc -n 20
|
|
56
|
+
|
|
57
|
+
parser = argparse.ArgumentParser(description=description,
|
|
58
|
+
formatter_class=argparse.RawTextHelpFormatter)
|
|
59
|
+
parser.add_argument("input_data_path",
|
|
60
|
+
type=str,
|
|
61
|
+
help="input data path")
|
|
62
|
+
#parser.add_argument("-n",
|
|
63
|
+
# type=int,
|
|
64
|
+
# help="number of multiprocessing, default is 4.",
|
|
65
|
+
# default=4)
|
|
66
|
+
parser.add_argument("-st",
|
|
67
|
+
type=str,
|
|
68
|
+
help="start timestep yyyy-mm-dd hh",
|
|
69
|
+
default="1700-01-01 00")
|
|
70
|
+
parser.add_argument("-et",
|
|
71
|
+
type=str,
|
|
72
|
+
help="end timestep yyyy-mm-dd hh",
|
|
73
|
+
default="1700-01-01 00")
|
|
74
|
+
parser.add_argument("-freq",
|
|
75
|
+
type=str,
|
|
76
|
+
help="frequency of timestep for pd.date_range (default: '6h')",
|
|
77
|
+
default="6h")
|
|
78
|
+
parser.add_argument("-track",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="enable tracking",)
|
|
81
|
+
args = parser.parse_args()
|
|
82
|
+
input_data_path = args.input_data_path
|
|
83
|
+
st = args.st
|
|
84
|
+
et = args.et
|
|
85
|
+
freq = args.freq
|
|
86
|
+
_track = args.track
|
|
87
|
+
|
|
88
|
+
data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
|
|
89
|
+
if not os.path.exists(data_settings_path):
|
|
90
|
+
raise FileNotFoundError("No data_settings.py in the current dir. Run 'gen_data_settings' command.")
|
|
91
|
+
D = _load_local_module(data_settings_path)
|
|
92
|
+
|
|
93
|
+
if D.input_data_type == "nc":
|
|
94
|
+
if not hasattr(D, "var_name"):
|
|
95
|
+
da = xr.open_dataarray(input_data_path)
|
|
96
|
+
else:
|
|
97
|
+
ds = xr.open_dataset(input_data_path)
|
|
98
|
+
da = ds[D.var_name]
|
|
99
|
+
|
|
100
|
+
if hasattr(D, "lon_name"):
|
|
101
|
+
da = da.rename({D.lon_name:"longitude"})
|
|
102
|
+
if hasattr(D, "lat_name"):
|
|
103
|
+
da = da.rename({D.lat_name:"latitude"})
|
|
104
|
+
if hasattr(D, "lev_name"):
|
|
105
|
+
da = da.rename({D.lev_name:"level"})
|
|
106
|
+
if hasattr(D, "time_name"):
|
|
107
|
+
da = da.rename({D.time_name:"time"})
|
|
108
|
+
|
|
109
|
+
lons = da.longitude.values
|
|
110
|
+
lats = da.latitude.values
|
|
111
|
+
levs = da.level.values
|
|
112
|
+
times = pd.to_datetime(da.time.values)
|
|
113
|
+
da = da.values
|
|
114
|
+
fmt = None
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
lons = D.lons
|
|
118
|
+
lats = D.lats
|
|
119
|
+
levs = D.levs
|
|
120
|
+
#times = D.times
|
|
121
|
+
times = pd.date_range(st,et,freq=freq)
|
|
122
|
+
shape = (len(times),len(levs),len(lats),len(lons))
|
|
123
|
+
fmt = D.input_data_type
|
|
124
|
+
with open(input_data_path, "r") as a:
|
|
125
|
+
da = np.fromfile(a, dtype=fmt).reshape(shape)
|
|
126
|
+
|
|
127
|
+
if D.output_data_type == "nc":
|
|
128
|
+
nc = True
|
|
129
|
+
else:
|
|
130
|
+
nc = False
|
|
131
|
+
fmt = D.output_data_type
|
|
132
|
+
|
|
133
|
+
idx_levs = np.where(np.isin(levs, D.selected_levs))[0]
|
|
134
|
+
|
|
135
|
+
for l in idx_levs:
|
|
136
|
+
Detect(da[:,l,:,:],
|
|
137
|
+
D.output_dir,
|
|
138
|
+
D.detection_type,
|
|
139
|
+
D.r,
|
|
140
|
+
D.SR_thres,
|
|
141
|
+
D.So_thres,
|
|
142
|
+
D.Do_thres,
|
|
143
|
+
D.xx_thres,
|
|
144
|
+
D.rm_rmin,
|
|
145
|
+
D.rm_rmax,
|
|
146
|
+
D.factor,
|
|
147
|
+
D.local_ex_req_num,
|
|
148
|
+
D.gpu,
|
|
149
|
+
nc,
|
|
150
|
+
fmt,
|
|
151
|
+
D.subs,
|
|
152
|
+
levs[l],
|
|
153
|
+
times,
|
|
154
|
+
lons,
|
|
155
|
+
lats,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if _track:
|
|
159
|
+
track_cli(times)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def track_cli(times=0):
|
|
163
|
+
|
|
164
|
+
data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
|
|
165
|
+
if not os.path.exists(data_settings_path):
|
|
166
|
+
raise FileNotFoundError("No data_settings.pn in the current dir. Run 'gen_data_settings' command.")
|
|
167
|
+
D = _load_local_module(data_settings_path)
|
|
168
|
+
|
|
169
|
+
if isinstance(times, pd.DatetimeIndex):
|
|
170
|
+
tracking_times = times
|
|
171
|
+
else:
|
|
172
|
+
tracking_times = D.tracking_times
|
|
173
|
+
|
|
174
|
+
argss=[(
|
|
175
|
+
D.output_dir,
|
|
176
|
+
D.tracking_types,
|
|
177
|
+
lev,
|
|
178
|
+
tracking_times,
|
|
179
|
+
D.tlimit,
|
|
180
|
+
D.penalty,
|
|
181
|
+
D.long_term,
|
|
182
|
+
D.operational,
|
|
183
|
+
D.DU_thres,
|
|
184
|
+
D.QS_min_intensity,
|
|
185
|
+
D.QS_min_radius,
|
|
186
|
+
D.QS_min_overlap_ratio,
|
|
187
|
+
D.only_count
|
|
188
|
+
) for lev in D.selected_levs]
|
|
189
|
+
|
|
190
|
+
for _args in argss:
|
|
191
|
+
Track(*_args)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def track():
|
|
195
|
+
description = """
|
|
196
|
+
Run tracking of colindex. Before run, edit TRACKING SETTINGS section in data_settings.py
|
|
197
|
+
|
|
198
|
+
$ track
|
|
199
|
+
|
|
200
|
+
Result data will be saved in output_dir/Vtc.
|
|
201
|
+
"""
|
|
202
|
+
parser = argparse.ArgumentParser(description=description,
|
|
203
|
+
formatter_class=argparse.RawTextHelpFormatter)
|
|
204
|
+
parser.parse_args()
|
|
205
|
+
track_cli()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def find_track():
|
|
209
|
+
description = """
|
|
210
|
+
Search tracking for specific IDs and save. Four arguments are required.
|
|
211
|
+
$ find_track output_dir type lev freqH type2
|
|
212
|
+
|
|
213
|
+
The fourth argument 'type2' can be set 3 types:
|
|
214
|
+
|
|
215
|
+
c: only count number of all tracks
|
|
216
|
+
$ find_track ./d01 L 300 6 c
|
|
217
|
+
|
|
218
|
+
A: find all tracks and save them as an all-included csv output_dir/all_{type}_{lev}.csv.
|
|
219
|
+
$ find_track ./d01 L 300 6 A
|
|
220
|
+
|
|
221
|
+
a: find all tracks and save them separately in output_dir/ID/.
|
|
222
|
+
$ find_track ./d01 L 300 6 a
|
|
223
|
+
|
|
224
|
+
(digit of ID): find one track labeled by ID. Add fifth argument of 'yyyymm' (year month) when the track observed, as following
|
|
225
|
+
$ find_track ./d01 L 300 6 2222 202302
|
|
226
|
+
|
|
227
|
+
This will produce a track obtained from 6-hour intarval data for 300-hPa level whose ID is 2222, appears in Feb 2023.
|
|
228
|
+
If add -a (-b) option at its tail, the tracks after merging (before splitting) are connected.
|
|
229
|
+
$ find_track ./d01 L 300 6 2222 202302 -a
|
|
230
|
+
$ find_track ./d01 L 300 6 2222 202302 -b
|
|
231
|
+
$ find_track ./d01 L 300 6 2222 202302 -a -b
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
|
|
235
|
+
parser.add_argument("odir", type=str, help="output data directory")
|
|
236
|
+
parser.add_argument("ty", type=str, help="L or H")
|
|
237
|
+
parser.add_argument("lev", type=str, help="level")
|
|
238
|
+
parser.add_argument("freqH", type=int, help="frequency of timestep in hour")
|
|
239
|
+
parser.add_argument("type2", nargs="+", type=str, help="A or a or c or 'ID yyyymm'")
|
|
240
|
+
parser.add_argument('-a', '--option_a', action='store_true', default=False, help='follow track after merge')
|
|
241
|
+
parser.add_argument('-b', '--option_b', action='store_true', default=False, help='follow track before split')
|
|
242
|
+
args = parser.parse_args()
|
|
243
|
+
odir = args.odir
|
|
244
|
+
ty = args.ty
|
|
245
|
+
#lev = int(args.lev)
|
|
246
|
+
lev = args.lev
|
|
247
|
+
type2 = args.type2
|
|
248
|
+
|
|
249
|
+
#data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
|
|
250
|
+
#D = _load_local_module(data_settings_path)
|
|
251
|
+
#freqH = int(D.tracking_times.inferred_freq[:-1])
|
|
252
|
+
freqH = args.freqH
|
|
253
|
+
|
|
254
|
+
f = Finder(odir, timestep=freqH)
|
|
255
|
+
|
|
256
|
+
if type2[0] == "a":
|
|
257
|
+
f.find_all(ty,lev)
|
|
258
|
+
elif type2[0] == "c":
|
|
259
|
+
f.count_all(ty,lev)
|
|
260
|
+
elif type2[0] == "A":
|
|
261
|
+
f.find_all(ty,lev,all_in_one=True)
|
|
262
|
+
elif args.option_a and args.option_b:
|
|
263
|
+
f.find_one(ty,lev,int(type2[1]), int(type2[0]),after_merge=True,before_split=True)
|
|
264
|
+
elif args.option_a:
|
|
265
|
+
f.find_one(ty,lev,int(type2[1]), int(type2[0]),after_merge=True)
|
|
266
|
+
elif args.option_b:
|
|
267
|
+
f.find_one(ty,lev,int(type2[1]), int(type2[0]),before_split=True)
|
|
268
|
+
else:
|
|
269
|
+
f.find_one(ty,lev,int(type2[1]), int(type2[0]))
|