Skip to content

Commit 401f1ab

Browse files
add merging and splitting triplets (AntSimi#51)
add triplet in merging and splitting
1 parent e7f25c0 commit 401f1ab

File tree

4 files changed

+111
-31
lines changed

4 files changed

+111
-31
lines changed

examples/16_network/pet_atlas.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
step = 1 / 10.0
1818
bins = ((-10, 37, step), (30, 46, step))
1919
kw_time = dict(cmap="terrain_r", factor=100.0 / n.nb_days, name="count")
20-
kw_ratio = dict(cmap=plt.get_cmap("coolwarm_r", 10))
20+
kw_ratio = dict(cmap=plt.get_cmap("magma_r", 10))
2121

2222

2323
# %%
@@ -58,8 +58,10 @@ def update_axes(ax, mappable=None):
5858
m = g_10.display(ax, **kw_time, vmin=0, vmax=75)
5959
update_axes(ax, m).set_label("Pixel used in % of time")
6060

61+
# %%
6162
# Display the ratio between the short and total presence.
62-
# Red = mostly short networks
63+
#
64+
# Light = mostly short networks
6365
ax = start_axes("")
6466
m = g_10.display(
6567
ax,
@@ -73,7 +75,7 @@ def update_axes(ax, mappable=None):
7375
# Network longer than 20 days
7476
# ---------------------------
7577
# Display the % of time each pixel (1/10°) is within an anticyclonic network
76-
# which total lifetime in longer than 20 days
78+
# which total lifetime is longer than 20 days
7779
ax = start_axes("")
7880
n20 = n.longer_than(20)
7981
g_20 = n20.grid_count(bins)
@@ -82,7 +84,8 @@ def update_axes(ax, mappable=None):
8284

8385
# %%
8486
# Display the ratio between the short and total presence.
85-
# Red = mostly short networks
87+
#
88+
# Light = mostly short networks
8689
ax = start_axes("")
8790
m = g_20.display(
8891
ax,
@@ -95,7 +98,9 @@ def update_axes(ax, mappable=None):
9598

9699
# %%
97100
# Display the ratio between the short and total presence.
98-
# Red = mostly short networks
101+
#
102+
# Light = mostly short networks
103+
#
99104
# Networks shorter than 365 days are masked
100105
ax = start_axes("")
101106
m = g_20.display(
@@ -110,9 +115,10 @@ def update_axes(ax, mappable=None):
110115
update_axes(ax, m).set_label("Pixel used in % all atlas")
111116
# %%
112117
# Display the ratio between the short and total presence.
113-
# Red = mostly short networks
118+
#
114119
# Networks longer than 365 days are masked
115-
# -> Coastal areas are mostly populated by short networks
120+
#
121+
# # -> Coastal areas are mostly populated by short networks
116122
ax = start_axes("")
117123
m = g_20.display(
118124
ax,
@@ -133,8 +139,9 @@ def update_axes(ax, mappable=None):
133139
# Display the occurence of merging events
134140
ax = start_axes("")
135141
g_all_merging = n.merging_event().grid_count(bins)
136-
m = g_all_merging.display(ax, **kw_time, vmin=0, vmax=0.2)
142+
m = g_all_merging.display(ax, **kw_time, vmin=0, vmax=1)
137143
update_axes(ax, m).set_label("Pixel used in % of time")
144+
138145
# %%
139146
# Ratio merging events / eddy presence
140147
ax = start_axes("")
@@ -152,15 +159,15 @@ def update_axes(ax, mappable=None):
152159
# ---------------------------------------
153160
ax = start_axes("")
154161
g_10_merging = n10.merging_event().grid_count(bins)
155-
m = g_10_merging.display(ax, **kw_time, vmin=0, vmax=0.2)
162+
m = g_10_merging.display(ax, **kw_time, vmin=0, vmax=1)
156163
update_axes(ax, m).set_label("Pixel used in % of time")
157164
# %%
158165
ax = start_axes("")
159166
m = g_10_merging.display(
160167
ax,
161168
**kw_ratio,
162169
vmin=0,
163-
vmax=0.5,
170+
vmax=2,
164171
name=ma.array(
165172
g_10_merging.vars["count"] * 100.0 / g_10.vars["count"],
166173
mask=g_10.vars["count"] < 365,

examples/16_network/pet_ioannou_2017_case.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def update_axes(ax, mappable=None):
6969

7070
# %%
7171
# We know the position and the time of a specific eddy
72+
#
7273
# `n.extract_with_mask` give us the corresponding network
7374
n = NetworkObservations.load_file(
7475
"med/Anticyclonic_seg.nc"

examples/16_network/pet_relative.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# %%
3333
# Display timeline with events
3434
# A segment generated by a splitting is marked with a star
35+
#
3536
# A segment merging in another is marked with an exagon
3637
fig = plt.figure(figsize=(15, 5))
3738
ax = fig.add_axes([0.04, 0.04, 0.92, 0.92])
@@ -82,6 +83,7 @@
8283
# Parameters timeline
8384
# -------------------
8485
# Scatter is usefull to display the parameters' temporal evolution
86+
#
8587
# Effective Radius and Amplitude
8688
kw = dict(s=25, cmap="Spectral_r", zorder=10)
8789
fig = plt.figure(figsize=(15, 8))
@@ -125,7 +127,7 @@
125127
# Remove all tiny segments with less than N obs which didn't join two segments
126128
#
127129
# .. warning::
128-
# Must be explore, no solution to solve all the case
130+
# Must be explore, no solution to solve all cases
129131

130132
n_clean = n.remove_dead_end(nobs=10)
131133
fig = plt.figure(figsize=(15, 8))
@@ -140,6 +142,7 @@
140142
# Keep close relative
141143
# -------------------
142144
# When you want to investigate one particular observation and select only the closest segments
145+
#
143146
# First choose an observation in the network
144147
fig = plt.figure(figsize=(15, 5))
145148
ax = fig.add_axes([0.04, 0.06, 0.90, 0.88])
@@ -201,21 +204,36 @@
201204
# Display the position of the eddies after a merging
202205
fig = plt.figure(figsize=(15, 8))
203206
ax = fig.add_axes([0.04, 0.06, 0.90, 0.88], projection="full_axes")
204-
merging = close_to_i2.merging_event()
205-
merging.display(ax)
207+
close_to_i2.plot(ax, color_cycle=close_to_i2.COLORS)
208+
m1, m0, m0_stop = close_to_i2.merging_event(triplet=True)
209+
m1.display(ax, color="violet", lw=2, label="Eddies after merging")
210+
m0.display(ax, color="blueviolet", lw=2, label="Eddies before merging")
211+
m0_stop.display(ax, color="black", lw=2, label="Eddies stopped by merging")
212+
ax.plot(m1.lon, m1.lat, marker=".", color="purple", ls="")
213+
ax.plot(m0.lon, m0.lat, marker=".", color="blueviolet", ls="")
214+
ax.plot(m0_stop.lon, m0_stop.lat, marker=".", color="black", ls="")
215+
ax.legend()
206216
ax.set_xlim(-10, 20), ax.set_ylim(-37, -21), ax.grid()
207-
merging
217+
m1
218+
208219

209220
# %%
210221
# Get spliting event
211222
# ------------------
212223
# Display the position of the eddies before a splitting
213224
fig = plt.figure(figsize=(15, 8))
214225
ax = fig.add_axes([0.04, 0.06, 0.90, 0.88], projection="full_axes")
215-
spliting = close_to_i2.spliting_event()
216-
spliting.display(ax)
226+
close_to_i2.plot(ax, color_cycle=close_to_i2.COLORS)
227+
s0, s1, s1_start = close_to_i2.spliting_event(triplet=True)
228+
s0.display(ax, color="violet", lw=2, label="Eddies before splitting")
229+
s1.display(ax, color="blueviolet", lw=2, label="Eddies after splitting")
230+
s1_start.display(ax, color="black", lw=2, label="Eddies starting by splitting")
231+
ax.plot(s0.lon, s0.lat, marker=".", color="purple", ls="")
232+
ax.plot(s1.lon, s1.lat, marker=".", color="blueviolet", ls="")
233+
ax.plot(s1_start.lon, s1_start.lat, marker=".", color="black", ls="")
234+
ax.legend()
217235
ax.set_xlim(-10, 20), ax.set_ylim(-37, -21), ax.grid()
218-
spliting
236+
s1
219237

220238
# %%
221239
# Get birth event

src/py_eddy_tracker/observations/network.py

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ def longer_than(self, nb_day_min=-1, nb_day_max=-1):
113113
"""
114114
Select network on time duration
115115
116-
:param int nb_day_min: Minimal number of day which must be covered by one network, if negative -> not used
117-
:param int nb_day_max: Maximal number of day which must be covered by one network, if negative -> not used
116+
:param int nb_day_min: Minimal number of day covered by one network, if negative -> not used
117+
:param int nb_day_max: Maximal number of day covered by one network, if negative -> not used
118118
"""
119119
if nb_day_max < 0:
120120
nb_day_max = 1000000000000
@@ -132,7 +132,7 @@ def longer_than(self, nb_day_min=-1, nb_day_max=-1):
132132
@classmethod
133133
def from_split_network(cls, group_dataset, indexs, **kwargs):
134134
"""
135-
Build a NetworkObservations object with Group dataset and indexs
135+
Build a NetworkObservations object with Group dataset and indexes
136136
137137
:param TrackEddiesObservations group_dataset: Group dataset
138138
:param indexs: result from split_network
@@ -204,6 +204,9 @@ def __close_segment(cls, father, shift, connexions, distance):
204204
cls.__close_segment(son, shift, connexions, distance)
205205

206206
def segment_relative_order(self, seg_origine):
207+
"""
208+
Compute the relative order of each segment to the chosen segment
209+
"""
207210
i_s, i_e, i_ref = build_index(self.segment)
208211
segment_connexions = self.connexions()
209212
relative_tr = -ones(i_s.shape, dtype="i4")
@@ -216,6 +219,9 @@ def segment_relative_order(self, seg_origine):
216219
return d
217220

218221
def relative(self, i_obs, order=2, direct=True, only_past=False, only_future=False):
222+
"""
223+
Extract the segments at a certain order.
224+
"""
219225
d = self.segment_relative_order(self.segment[i_obs])
220226
m = (d <= order) * (d != -1)
221227
return self.extract_with_mask(m)
@@ -234,7 +240,7 @@ def only_one_network(self):
234240
"""
235241
_, i_start, _ = self.index_network
236242
if len(i_start) > 1:
237-
raise Exception("Several network")
243+
raise Exception("Several networks")
238244

239245
def position_filter(self, median_half_window, loess_half_window):
240246
self.median_filter(median_half_window, "time", "lon").loess_filter(
@@ -266,7 +272,15 @@ def display_timeline(
266272
self, ax, event=True, field=None, method=None, factor=1, **kwargs
267273
):
268274
"""
269-
Must be call on only one network
275+
Plot a timeline of a network.
276+
Must be called on only one network.
277+
278+
:param matplotlib.axes.Axes ax: matplotlib axe used to draw
279+
:param bool event: if True, draw the splitting and merging events
280+
:param str,array field: yaxis values, if None, segments are used
281+
:param str method: if None, mean values are used
282+
:param float factor: to multiply field
283+
:return: plot mappable
270284
"""
271285
self.only_one_network()
272286
j = 0
@@ -516,6 +530,7 @@ def extract_event(self, indices):
516530

517531
@property
518532
def segment_track_array(self):
533+
"""Return a unique segment id when multiple networks are considered"""
519534
return build_unique_array(self.segment, self.track)
520535

521536
def birth_event(self):
@@ -542,27 +557,65 @@ def death_event(self):
542557
indices.append(i.stop - 1)
543558
return self.extract_event(list(set(indices)))
544559

545-
def merging_event(self):
546-
indices = list()
560+
def merging_event(self, triplet=False):
561+
"""Return observation after a merging event.
562+
563+
If `triplet=True` return the eddy after a merging event, the eddy before the merging event,
564+
and the eddy stopped due to merging.
565+
"""
566+
idx_m1 = list()
567+
if triplet:
568+
idx_m0_stop = list()
569+
idx_m0 = list()
570+
547571
for i, _, _ in self.iter_on(self.segment_track_array):
548572
nb = i.stop - i.start
549573
if nb == 0:
550574
continue
551575
i_n = self.next_obs[i.stop - 1]
552576
if i_n != -1:
553-
indices.append(i.stop - 1)
554-
return self.extract_event(list(set(indices)))
577+
if triplet:
578+
idx_m0_stop.append(i.stop - 1)
579+
idx_m0.append(self.previous_obs[i_n])
580+
idx_m1.append(i_n)
581+
582+
if triplet:
583+
return (
584+
self.extract_event(list(idx_m1)),
585+
self.extract_event(list(idx_m0)),
586+
self.extract_event(list(idx_m0_stop)),
587+
)
588+
else:
589+
return self.extract_event(list(set(idx_m1)))
555590

556-
def spliting_event(self):
557-
indices = list()
591+
def spliting_event(self, triplet=False):
592+
"""Return observation before a splitting event.
593+
594+
If `triplet=True` return the eddy before a splitting event, the eddy after the splitting event,
595+
and the eddy starting due to splitting.
596+
"""
597+
idx_s0 = list()
598+
if triplet:
599+
idx_s1_start = list()
600+
idx_s1 = list()
558601
for i, _, _ in self.iter_on(self.segment_track_array):
559602
nb = i.stop - i.start
560603
if nb == 0:
561604
continue
562605
i_p = self.previous_obs[i.start]
563606
if i_p != -1:
564-
indices.append(i.start)
565-
return self.extract_event(list(set(indices)))
607+
if triplet:
608+
idx_s1_start.append(i.start)
609+
idx_s1.append(self.next_obs[i_p])
610+
idx_s0.append(i_p)
611+
if triplet:
612+
return (
613+
self.extract_event(list(idx_s0)),
614+
self.extract_event(list(idx_s1)),
615+
self.extract_event(list(idx_s1_start)),
616+
)
617+
else:
618+
return self.extract_event(list(set(idx_s0)))
566619

567620
def dissociate_network(self):
568621
"""
@@ -655,7 +708,7 @@ def remove_dead_end(self, nobs=3, recursive=0, mask=None):
655708
self.only_one_network()
656709
segments_keep = list()
657710
connexions = self.connexions()
658-
for i, b0, b1 in self.iter_on("segment"):
711+
for i, b0, _ in self.iter_on("segment"):
659712
nb = i.stop - i.start
660713
if mask and mask[i].any():
661714
segments_keep.append(b0)
@@ -852,6 +905,7 @@ def apply_replace(x, x0, x1):
852905

853906
@njit(cache=True)
854907
def build_unique_array(id1, id2):
908+
"""Give a unique id for each (id1, id2) with id1 and id2 increasing monotonically"""
855909
k = 0
856910
new_id = empty(id1.shape, dtype=id1.dtype)
857911
id1_previous = id1[0]

0 commit comments

Comments
 (0)