Skip to content

Commit 9e04d81

Browse files
committed
- Add method to colorize contour with a field
- Add option to force align on to return all step for reference dataset - Add method and property to network to easily select segment and network - Add method to found same track/segment/network in dataset - Rewrite particle candidate to be easily parallelize
1 parent 6dcbbc4 commit 9e04d81

File tree

15 files changed

+738
-108
lines changed

15 files changed

+738
-108
lines changed

setup.cfg

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
[flake8]
2+
max-line-length = 140
23
ignore =
34
E203, # whitespace before ':'
45
W503, # line break before binary operator
56

7+
[isort]
8+
combine_as_imports=True
9+
force_grid_wrap=0
10+
force_sort_within_sections=True
11+
force_to_top=typing
12+
include_trailing_comma=True
13+
line_length=140
14+
multi_line_output=3
15+
skip=
16+
build
17+
docs/source/conf.py
18+
19+
620
[versioneer]
721
VCS = git
822
style = pep440

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"EddyNetworkGroup = py_eddy_tracker.appli.network:build_network",
4949
"EddyNetworkBuildPath = py_eddy_tracker.appli.network:divide_network",
5050
"EddyNetworkSubSetter = py_eddy_tracker.appli.network:subset_network",
51+
"EddyNetworkQuickCompare = py_eddy_tracker.appli.network:quick_compare",
5152
# anim/gui
5253
"EddyAnim = py_eddy_tracker.appli.gui:anim",
5354
"GUIEddy = py_eddy_tracker.appli.gui:guieddy",

src/py_eddy_tracker/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,14 +422,20 @@ def identify_time(str_date):
422422
nc_name="previous_cost",
423423
nc_type="float32",
424424
nc_dims=("obs",),
425-
nc_attr=dict(long_name="Previous cost for previous observation", comment="",),
425+
nc_attr=dict(
426+
long_name="Previous cost for previous observation",
427+
comment="",
428+
),
426429
),
427430
next_cost=dict(
428431
attr_name=None,
429432
nc_name="next_cost",
430433
nc_type="float32",
431434
nc_dims=("obs",),
432-
nc_attr=dict(long_name="Next cost for next observation", comment="",),
435+
nc_attr=dict(
436+
long_name="Next cost for next observation",
437+
comment="",
438+
),
433439
),
434440
n=dict(
435441
attr_name=None,
@@ -640,7 +646,8 @@ def identify_time(str_date):
640646
nc_type="f4",
641647
nc_dims=("obs",),
642648
nc_attr=dict(
643-
long_name="Log base 10 background chlorophyll", units="Log(Chl/[mg/m^3])",
649+
long_name="Log base 10 background chlorophyll",
650+
units="Log(Chl/[mg/m^3])",
644651
),
645652
),
646653
year=dict(

src/py_eddy_tracker/appli/eddies.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ def browse_dataset_in(
243243
filenames = bytes_(glob(full_path))
244244

245245
dataset_list = empty(
246-
len(filenames), dtype=[("filename", "S500"), ("date", "datetime64[s]")],
246+
len(filenames),
247+
dtype=[("filename", "S500"), ("date", "datetime64[s]")],
247248
)
248249
dataset_list["filename"] = filenames
249250

@@ -371,7 +372,8 @@ def track(
371372

372373
logger.info("Longer track saved have %d obs", c.nb_obs_by_tracks.max())
373374
logger.info(
374-
"The mean length is %d observations for long track", c.nb_obs_by_tracks.mean(),
375+
"The mean length is %d observations for long track",
376+
c.nb_obs_by_tracks.mean(),
375377
)
376378

377379
long_track.write_file(**kw_write)
@@ -381,7 +383,14 @@ def track(
381383

382384

383385
def get_group(
384-
dataset1, dataset2, index1, index2, score, invalid=2, low=10, high=60,
386+
dataset1,
387+
dataset2,
388+
index1,
389+
index2,
390+
score,
391+
invalid=2,
392+
low=10,
393+
high=60,
385394
):
386395
group1, group2 = dict(), dict()
387396
m_valid = (score * 100) >= invalid
@@ -490,7 +499,8 @@ def get_values(v, dataset):
490499
]
491500

492501
labels = dict(
493-
high=f"{high:0.0f} <= high", low=f"{invalid:0.0f} <= low < {low:0.0f}",
502+
high=f"{high:0.0f} <= high",
503+
low=f"{invalid:0.0f} <= low < {low:0.0f}",
494504
)
495505

496506
keys = [labels.get(key, key) for key in list(gr_ref.values())[0].keys()]

src/py_eddy_tracker/appli/network.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .. import EddyParser
99
from ..observations.network import Network, NetworkObservations
1010
from ..observations.tracking import TrackEddiesObservations
11+
from numpy import in1d, zeros
1112

1213
logger = logging.getLogger("pet")
1314

@@ -128,3 +129,135 @@ def subset_network():
128129
if args.period is not None:
129130
n = n.extract_with_period(args.period)
130131
n.write_file(filename=args.out)
132+
133+
134+
def quick_compare():
135+
parser = EddyParser(
136+
"""Tool to have a quick comparison between several network:
137+
- N : network
138+
- S : segment
139+
- Obs : observations
140+
"""
141+
142+
)
143+
parser.add_argument("ref", help="Identification file of reference")
144+
parser.add_argument("others", nargs="+", help="Identifications files to compare")
145+
parser.add_argument(
146+
"--path_out", default=None, help="Save each group in separate file"
147+
)
148+
args = parser.parse_args()
149+
150+
kw = dict(
151+
include_vars=['longitude', 'latitude', 'time', 'track', 'segment', 'next_obs', 'previous_obs']
152+
)
153+
154+
if args.path_out is not None:
155+
kw = dict()
156+
157+
ref = NetworkObservations.load_file(args.ref, **kw)
158+
print(
159+
f"[ref] {args.ref} -> {ref.nb_network} network / {ref.nb_segment} segment / {len(ref)} obs "
160+
f"-> {ref.network_size(0)} trash obs, "
161+
f"{len(ref.merging_event())} merging, {len(ref.splitting_event())} spliting"
162+
)
163+
others = {other: NetworkObservations.load_file(other, **kw) for other in args.others}
164+
165+
if args.path_out is not None:
166+
groups_ref, groups_other = run_compare(ref, others, **kwargs)
167+
if not exists(args.path_out):
168+
mkdir(args.path_out)
169+
for i, other_ in enumerate(args.others):
170+
dirname_ = f"{args.path_out}/{other_.replace('/', '_')}/"
171+
if not exists(dirname_):
172+
mkdir(dirname_)
173+
for k, v in groups_other[other_].items():
174+
basename_ = f"other_{k}.nc"
175+
others[other_].index(v).write_file(filename=f"{dirname_}/{basename_}")
176+
for k, v in groups_ref[other_].items():
177+
basename_ = f"ref_{k}.nc"
178+
ref.index(v).write_file(filename=f"{dirname_}/{basename_}")
179+
return
180+
display_compare(ref, others)
181+
182+
183+
def run_compare(ref, others):
184+
outs = dict()
185+
for i, (k, other) in enumerate(others.items()):
186+
out = dict()
187+
print(
188+
f"[{i}] {k} -> {other.nb_network} network / {other.nb_segment} segment / {len(other)} obs "
189+
f"-> {other.network_size(0)} trash obs, "
190+
f"{len(other.merging_event())} merging, {len(other.splitting_event())} spliting"
191+
)
192+
ref_id, other_id = ref.identify_in(other, size_min=2)
193+
m = other_id != -1
194+
ref_id, other_id = ref_id[m], other_id[m]
195+
out['same N(N)'] = m.sum()
196+
out['same N(Obs)'] = ref.network_size(ref_id).sum()
197+
198+
# For network which have same obs
199+
ref_, other_ = ref.networks(ref_id), other.networks(other_id)
200+
ref_segu, other_segu = ref_.identify_in(other_, segment=True)
201+
m = other_segu==-1
202+
ref_track_no_match, _ = ref_.unique_segment_to_id(ref_segu[m])
203+
ref_segu, other_segu = ref_segu[~m], other_segu[~m]
204+
m = ~in1d(ref_id, ref_track_no_match)
205+
out['same NS(N)'] = m.sum()
206+
out['same NS(Obs)'] = ref.network_size(ref_id[m]).sum()
207+
208+
# Check merge/split
209+
def follow_obs(d, i_follow):
210+
m = i_follow != -1
211+
i_follow = i_follow[m]
212+
t, x, y = zeros(m.size, d.time.dtype), zeros(m.size, d.longitude.dtype), zeros(m.size, d.latitude.dtype)
213+
t[m], x[m], y[m] = d.time[i_follow], d.longitude[i_follow], d.latitude[i_follow]
214+
return t, x, y
215+
def next_obs(d, i_seg):
216+
last_i = d.index_segment_track[1][i_seg] - 1
217+
return follow_obs(d, d.next_obs[last_i])
218+
def previous_obs(d, i_seg):
219+
first_i = d.index_segment_track[0][i_seg]
220+
return follow_obs(d, d.previous_obs[first_i])
221+
222+
tref, xref, yref = next_obs(ref_, ref_segu)
223+
tother, xother, yother = next_obs(other_, other_segu)
224+
225+
m = (tref == tother) & (xref == xother) & (yref == yother)
226+
print(m.sum(), m.size, ref_segu.size, ref_track_no_match.size)
227+
228+
tref, xref, yref = previous_obs(ref_, ref_segu)
229+
tother, xother, yother = previous_obs(other_, other_segu)
230+
231+
m = (tref == tother) & (xref == xother) & (yref == yother)
232+
print(m.sum(), m.size, ref_segu.size, ref_track_no_match.size)
233+
234+
235+
236+
ref_segu, other_segu = ref.identify_in(other, segment=True)
237+
m = other_segu != -1
238+
out['same S(S)'] = m.sum()
239+
out['same S(Obs)'] = ref.segment_size()[ref_segu[m]].sum()
240+
241+
outs[k] = out
242+
return outs
243+
244+
def display_compare(ref, others):
245+
def display(value, ref=None):
246+
if ref:
247+
outs = [f"{v/ref[k] * 100:.1f}% ({v})" for k, v in value.items()]
248+
else:
249+
outs = value
250+
return "".join([f"{v:^18}" for v in outs])
251+
252+
datas = run_compare(ref, others)
253+
ref_ = {
254+
'same N(N)' : ref.nb_network,
255+
"same N(Obs)": len(ref),
256+
'same NS(N)' : ref.nb_network,
257+
'same NS(Obs)' : len(ref),
258+
'same S(S)' : ref.nb_segment,
259+
'same S(Obs)' : len(ref),
260+
}
261+
print(" ", display(ref_.keys()))
262+
for i, (_, v) in enumerate(datas.items()):
263+
print(f"[{i:2}] ", display(v, ref=ref_))

src/py_eddy_tracker/dataset/grid.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ def __init__(
310310
self.load_general_features()
311311
self.load()
312312

313+
def populate(self):
314+
if self.dimensions is None:
315+
self.load_general_features()
316+
self.load()
317+
313318
@property
314319
def is_centered(self):
315320
"""Give True if pixel is described with its center's position or
@@ -539,7 +544,8 @@ def grid(self, varname, indexs=None):
539544
self.vars[varname] = self.vars[varname].T
540545
if self.nan_mask:
541546
self.vars[varname] = ma.array(
542-
self.vars[varname], mask=isnan(self.vars[varname]),
547+
self.vars[varname],
548+
mask=isnan(self.vars[varname]),
543549
)
544550
if not hasattr(self.vars[varname], "mask"):
545551
self.vars[varname] = ma.array(
@@ -869,7 +875,9 @@ def eddy_identification(
869875
num_fac=presampling_multiplier,
870876
)
871877
xy_e = uniform_resample(
872-
contour.lon, contour.lat, num_fac=presampling_multiplier,
878+
contour.lon,
879+
contour.lat,
880+
num_fac=presampling_multiplier,
873881
)
874882
xy_s = uniform_resample(
875883
speed_contour.lon,
@@ -1204,7 +1212,9 @@ def setup_coordinates(self):
12041212
dx = self.x_bounds[1:] - self.x_bounds[:-1]
12051213
dy = self.y_bounds[1:] - self.y_bounds[:-1]
12061214
if (dx < 0).any() or (dy < 0).any():
1207-
raise Exception("Coordinates in RegularGridDataset must be strictly increasing")
1215+
raise Exception(
1216+
"Coordinates in RegularGridDataset must be strictly increasing"
1217+
)
12081218
self._x_step = (self.x_c[1:] - self.x_c[:-1]).mean()
12091219
self._y_step = (self.y_c[1:] - self.y_c[:-1]).mean()
12101220

@@ -1736,7 +1746,7 @@ def compute_stencil(
17361746
self.x_c,
17371747
self.y_c,
17381748
data.data,
1739-
data.mask,
1749+
self.get_mask(data),
17401750
self.EARTH_RADIUS,
17411751
vertical=vertical,
17421752
stencil_halfwidth=stencil_halfwidth,
@@ -2285,23 +2295,23 @@ def __init__(self):
22852295
self.datasets = list()
22862296

22872297
@classmethod
2288-
def from_netcdf_cube(cls, filename, x_name, y_name, t_name, heigth=None):
2298+
def from_netcdf_cube(cls, filename, x_name, y_name, t_name, heigth=None, **kwargs):
22892299
new = cls()
22902300
with Dataset(filename) as h:
22912301
for i, t in enumerate(h.variables[t_name][:]):
2292-
d = RegularGridDataset(filename, x_name, y_name, indexs={t_name: i})
2302+
d = RegularGridDataset(filename, x_name, y_name, indexs={t_name: i}, **kwargs)
22932303
if heigth is not None:
22942304
d.add_uv(heigth)
22952305
new.datasets.append((t, d))
22962306
return new
22972307

22982308
@classmethod
2299-
def from_netcdf_list(cls, filenames, t, x_name, y_name, indexs=None, heigth=None):
2309+
def from_netcdf_list(cls, filenames, t, x_name, y_name, indexs=None, heigth=None, **kwargs):
23002310
new = cls()
23012311
for i, _t in enumerate(t):
23022312
filename = filenames[i]
23032313
logger.debug(f"load file {i:02d}/{len(t)} t={_t} : {filename}")
2304-
d = RegularGridDataset(filename, x_name, y_name, indexs=indexs)
2314+
d = RegularGridDataset(filename, x_name, y_name, indexs=indexs, **kwargs)
23052315
if heigth is not None:
23062316
d.add_uv(heigth)
23072317
new.datasets.append((_t, d))
@@ -2349,6 +2359,7 @@ def __iter__(self):
23492359
def __getitem__(self, item):
23502360
for t, d in self.datasets:
23512361
if t == item:
2362+
d.populate()
23522363
return d
23532364
raise KeyError(item)
23542365

@@ -2448,10 +2459,13 @@ def advect(
24482459
:param array y: Latitude of obs to move
24492460
:param str,array u_name: U field to advect obs
24502461
:param str,array v_name: V field to advect obs
2462+
:param float t_init: time to start advection
2463+
:param array,None mask_particule: advect only i mask is True
24512464
:param int nb_step: Number of iteration before to release data
24522465
:param int time_step: Number of second for each advection
2466+
:param bool rk4: Use rk4 algorithm instead of finite difference
24532467
2454-
:return: x,y position
2468+
:return: t,x,y position
24552469
24562470
.. minigallery:: py_eddy_tracker.GridCollection.advect
24572471
"""
@@ -2477,7 +2491,7 @@ def advect(
24772491
else:
24782492
mask_particule += isnan(x) + isnan(y)
24792493
while True:
2480-
logger.debug(f"advect : t={t}")
2494+
logger.debug(f"advect : t={t/86400}")
24812495
if (backward and t <= t1) or (not backward and t >= t1):
24822496
t0, u0, v0, m0 = t1, u1, v1, m1
24832497
t1, d1 = generator.__next__()
@@ -2507,7 +2521,7 @@ def get_next_time_step(self, t_init):
25072521
for i, (t, dataset) in enumerate(self.datasets):
25082522
if t < t_init:
25092523
continue
2510-
2524+
dataset.populate()
25112525
logger.debug(f"i={i}, t={t}, dataset={dataset}")
25122526
yield t, dataset
25132527

@@ -2517,7 +2531,7 @@ def get_previous_time_step(self, t_init):
25172531
i -= 1
25182532
if t > t_init:
25192533
continue
2520-
2534+
dataset.populate()
25212535
logger.debug(f"i={i}, t={t}, dataset={dataset}")
25222536
yield t, dataset
25232537

0 commit comments

Comments
 (0)