Skip to content

Commit f0070e9

Browse files
committed
Correction of global indexation, Add comments
Merge branch 'master' of https://github.com/AntSimi/py-eddy-tracker # Conflicts: # src/py_eddy_tracker/observations/observation.py
1 parent 2c78c22 commit f0070e9

File tree

3 files changed

+58
-44
lines changed

3 files changed

+58
-44
lines changed

src/py_eddy_tracker/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def parse_args(self, *args, **kwargs):
197197
nc_attr=dict(
198198
units="degrees_east",
199199
axis="X",
200-
comment="Longitude center of the fitted circle",
200+
comment="Longitude center of the fit circle",
201201
long_name="Eddy Center Longitude",
202202
standard_name="longitude",
203203
),
@@ -214,7 +214,7 @@ def parse_args(self, *args, **kwargs):
214214
axis="Y",
215215
long_name="Eddy Center Latitude",
216216
standard_name="latitude",
217-
comment="Latitude center of the fitted circle",
217+
comment="Latitude center of the fit circle",
218218
),
219219
),
220220
lon_max=dict(

src/py_eddy_tracker/observations/tracking.py

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
Class to manage observations gathered in track
3+
Class to manage observations gathered in trajectories
44
"""
55
import logging
66
from datetime import datetime, timedelta
@@ -124,7 +124,7 @@ def __repr__(self):
124124
return content
125125

126126
def add_distance(self):
127-
"""Add a field of distance (m) between to consecutive observation, 0 for the last observation of each track"""
127+
"""Add a field of distance (m) between two consecutive observations, 0 for the last observation of each track"""
128128
if "distance_next" in self.observations.dtype.descr:
129129
return self
130130
new = self.add_fields(("distance_next",))
@@ -182,7 +182,7 @@ def filled_by_interpolation(self, mask):
182182
)
183183

184184
def extract_longer_eddies(self, nb_min, nb_obs, compress_id=True):
185-
"""Select eddies which are longer than nb_min"""
185+
"""Select the trajectories longer than nb_min"""
186186
mask = nb_obs >= nb_min
187187
nb_obs_select = mask.sum()
188188
logger.info("Selection of %d observations", nb_obs_select)
@@ -227,9 +227,9 @@ def set_global_attr_netcdf(self, h_nc):
227227

228228
def extract_with_period(self, period, **kwargs):
229229
"""
230-
Extract with a period
230+
Extract within a time period
231231
232-
:param (int,int) period: two date to define period, must be specify from 1/1/1950
232+
:param (int,int) period: two dates to define the period, must be specify from 1/1/1950
233233
:param dict kwargs: look at :py:meth:`extract_with_mask`
234234
:return: Return all eddy tracks which are in bounds
235235
:rtype: TrackEddiesObservations
@@ -252,9 +252,9 @@ def extract_with_period(self, period, **kwargs):
252252

253253
def get_azimuth(self, equatorward=False):
254254
"""
255-
Return azimuth for each tracks.
255+
Return azimuth for each track.
256256
257-
Azimuth is compute with first and last observation
257+
Azimuth is computed with first and last observation
258258
259259
:param bool equatorward: If True, Poleward are positive and equatorward negative
260260
:rtype: array
@@ -286,7 +286,7 @@ def compute_index(self):
286286
"""
287287
if self.__first_index_of_track is None:
288288
s = self.tracks.max() + 1
289-
# Doesn't work => core dump with numba, maybe he wait i8 instead of u4
289+
# Doesn't work => core dump with numba, maybe he wants i8 instead of u4
290290
# self.__first_index_of_track = -ones(s, self.tracks.dtype)
291291
# self.__obs_by_track = zeros(s, self.observation_number.dtype)
292292
self.__first_index_of_track = -ones(s, "i8")
@@ -334,12 +334,12 @@ def nb_obs_by_track(self):
334334

335335
@property
336336
def lifetime(self):
337-
"""Return for each observation lifetime"""
337+
"""Return lifetime for each observation"""
338338
return self.nb_obs_by_track.repeat(self.nb_obs_by_track)
339339

340340
@property
341341
def age(self):
342-
"""Return for each observation age in %, will be [0:100]"""
342+
"""Return age in % for each observation, will be [0:100]"""
343343
return self.n.astype("f4") / (self.lifetime - 1) * 100.0
344344

345345
def extract_ids(self, tracks):
@@ -348,10 +348,10 @@ def extract_ids(self, tracks):
348348

349349
def extract_toward_direction(self, west=True, delta_lon=None):
350350
"""
351-
Get eddy which go in same direction
351+
Get trajectories going in the same direction
352352
353-
:param bool west: Only eastward eddy if True return westward
354-
:param None,float delta_lon: Only eddy with more than delta_lon span in longitude
353+
:param bool west: Only eastward eddies if True return westward
354+
:param None,float delta_lon: Only eddies with more than delta_lon span in longitude
355355
:return: Only eastern eddy
356356
:rtype: __class__
357357
@@ -398,10 +398,10 @@ def extract_in_direction(self, direction, value=0):
398398

399399
def extract_with_length(self, bounds):
400400
"""
401-
Return all observations in [b0:b1]
401+
Return the observations within trajectories lasting between [b0:b1]
402402
403-
:param (int,int) bounds: length min and max of selected eddies, if use of -1 this bound is not used
404-
:return: Return all eddy tracks which have length between bounds
403+
:param (int,int) bounds: length min and max of the desired trajectories, if -1 this bound is not used
404+
:return: Return all trajectories having length between bounds
405405
:rtype: TrackEddiesObservations
406406
407407
.. minigallery:: py_eddy_tracker.TrackEddiesObservations.extract_with_length
@@ -461,11 +461,11 @@ def extract_with_mask(
461461
Extract a subset of observations
462462
463463
:param array(bool) mask: mask to select observations
464-
:param bool full_path: extract full path if only one part is selected
465-
:param bool remove_incomplete: delete path which are not fully selected
466-
:param bool compress_id: resample track number to use a little range
467-
:param bool reject_virtual: if track are only virtual in selection we remove track
468-
:return: same object with selected observations
464+
:param bool full_path: extract the full trajectory if only one part is selected
465+
:param bool remove_incomplete: delete trajectory if not fully selected
466+
:param bool compress_id: resample trajectory number to use a smaller range
467+
:param bool reject_virtual: if only virtual are selected, the trajectory is removed
468+
:return: same object with the selected observations
469469
:rtype: self.__class__
470470
"""
471471
if full_path and remove_incomplete:
@@ -510,7 +510,9 @@ def re_reference_index(index, ref):
510510

511511
def shape_polygon(self, intern=False):
512512
"""
513-
Get polygons which enclosed each track
513+
Get the polygon enclosing each trajectory.
514+
515+
The polygon merges the non-overlapping bounds of the specified contours
514516
515517
:param bool intern: If True use speed contour instead of effective contour
516518
:rtype: list(array, array)
@@ -520,9 +522,9 @@ def shape_polygon(self, intern=False):
520522

521523
def display_shape(self, ax, ref=None, intern=False, **kwargs):
522524
"""
523-
This function will draw shape of each track
525+
This function will draw the shape of each trajectory
524526
525-
:param matplotlib.axes.Axes ax: ax where drawed
527+
:param matplotlib.axes.Axes ax: ax to draw
526528
:param float,int ref: if defined all coordinates will be wrapped with ref like west boundary
527529
:param bool intern: If True use speed contour instead of effective contour
528530
:param dict kwargs: keyword arguments for Axes.plot
@@ -547,10 +549,10 @@ def display_shape(self, ax, ref=None, intern=False, **kwargs):
547549

548550
def close_tracks(self, other, nb_obs_min=10, **kwargs):
549551
"""
550-
Get close from another atlas.
552+
Get close trajectories from another atlas.
551553
552554
:param self other: Atlas to compare
553-
:param int nb_obs_min: Minimal number of overlap for one track
555+
:param int nb_obs_min: Minimal number of overlap for one trajectory
554556
:param dict kwargs: keyword arguments for match function
555557
:return: return other atlas reduce to common track with self
556558
@@ -577,10 +579,10 @@ def format_label(self, label):
577579

578580
def plot(self, ax, ref=None, **kwargs):
579581
"""
580-
This function will draw path of each track
582+
This function will draw path of each trajectory
581583
582-
:param matplotlib.axes.Axes ax: ax where drawed
583-
:param float,int ref: if defined all coordinates will be wrapped with ref like west boundary
584+
:param matplotlib.axes.Axes ax: ax to draw
585+
:param float,int ref: if defined, all coordinates will be wrapped with ref like west boundary
584586
:param dict kwargs: keyword arguments for Axes.plot
585587
:return: matplotlib mappable
586588
"""
@@ -595,7 +597,7 @@ def plot(self, ax, ref=None, **kwargs):
595597
return ax.plot(x, y, **kwargs)
596598

597599
def split_network(self, intern=True, **kwargs):
598-
"""Divide each group in track"""
600+
"""Return each group (network) divided in segments"""
599601
track_s, track_e, track_ref = build_index(self.tracks)
600602
ids = empty(
601603
len(self),
@@ -610,26 +612,32 @@ def split_network(self, intern=True, **kwargs):
610612
],
611613
)
612614
ids["group"], ids["time"] = self.tracks, self.time
613-
# To store id track
615+
# Initialisation
616+
# To store the id of the segments, the backward and forward cost associations
614617
ids["track"], ids["previous_cost"], ids["next_cost"] = 0, 0, 0
618+
# To store the indexes of the backward and forward observations associated
615619
ids["previous_obs"], ids["next_obs"] = -1, -1
620+
# At the end, ids["previous_obs"] == -1 means the start of a non-split segment
621+
# and ids["next_obs"] == -1 means the end of a non-merged segment
616622

617623
xname, yname = self.intern(intern)
618624
for i_s, i_e in zip(track_s, track_e):
619625
if i_s == i_e or self.tracks[i_s] == self.NOGROUP:
620626
continue
621627
sl = slice(i_s, i_e)
622628
local_ids = ids[sl]
629+
# built segments with local indices
623630
self.set_tracks(self[xname][sl], self[yname][sl], local_ids, **kwargs)
624-
m = local_ids["previous_obs"] == -1
631+
# shift the local indices to the total indexation for the used observations
632+
m = local_ids["previous_obs"] != -1
625633
local_ids["previous_obs"][m] += i_s
626-
m = local_ids["next_obs"] == -1
634+
m = local_ids["next_obs"] != -1
627635
local_ids["next_obs"][m] += i_s
628636
return ids
629637

630638
def set_tracks(self, x, y, ids, window):
631639
"""
632-
Will split one group in tracks
640+
Will split one group (network) in segments
633641
634642
:param array x: coordinates of group
635643
:param array y: coordinates of group
@@ -641,19 +649,22 @@ def set_tracks(self, x, y, ids, window):
641649
nb = x.shape[0]
642650
used = zeros(nb, dtype="bool")
643651
track_id = 1
644-
# build all polygon (need to check if wrap is needed)
652+
# build all polygons (need to check if wrap is needed)
645653
polygons = [Polygon(create_vertice_from_2darray(x, y, i)) for i in range(nb)]
646654
for i in range(nb):
647-
# If observation already in one track, we go to the next one
655+
# If the observation is already in one track, we go to the next one
648656
if used[i]:
649657
continue
658+
# Search a possible continuation (forward)
650659
self.follow_obs(i, track_id, used, ids, polygons, *time_index, window)
651660
track_id += 1
652-
# Search a possible ancestor
661+
# Search a possible ancestor (backward)
653662
self.previous_obs(i, ids, polygons, *time_index, window)
654663

655664
@classmethod
656665
def follow_obs(cls, i_next, track_id, used, ids, *args):
666+
"""Associate the observations to the segments"""
667+
657668
while i_next != -1:
658669
# Flag
659670
used[i_next] = True
@@ -677,6 +688,8 @@ def follow_obs(cls, i_next, track_id, used, ids, *args):
677688

678689
@staticmethod
679690
def previous_obs(i_current, ids, polygons, time_s, time_e, time_ref, window):
691+
"""Backward association of observations to the segments"""
692+
680693
time_cur = ids["time"][i_current]
681694
t0, t1 = time_cur - 1 - time_ref, max(time_cur - window - time_ref, 0)
682695
for t_step in range(t0, t1 - 1, -1):
@@ -700,6 +713,7 @@ def previous_obs(i_current, ids, polygons, time_s, time_e, time_ref, window):
700713

701714
@staticmethod
702715
def next_obs(i_current, ids, polygons, time_s, time_e, time_ref, window):
716+
"""Forward association of observations to the segments"""
703717
time_max = time_e.shape[0] - 1
704718
time_cur = ids["time"][i_current]
705719
t0, t1 = time_cur + 1 - time_ref, min(time_cur + window - time_ref, time_max)
@@ -710,9 +724,9 @@ def next_obs(i_current, ids, polygons, time_s, time_e, time_ref, window):
710724
# No observation at the time step
711725
if i0 == i1:
712726
continue
713-
# Intersection / union, to be able to separte in case of multiple inside
727+
# Intersection / union, to be able to separate in case of multiple obs inside the current polygon in next_step
714728
c = polygon_overlap(polygons[i_current], polygons[i0:i1], minimal_area=True)
715-
# We remove low overlap
729+
# We remove low overlap TO DO : add the value as a parameter (default 0.1)?
716730
c[c < 0.1] = 0
717731
# We get index of maximal overlap
718732
i = c.argmax()

src/py_eddy_tracker/poly.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def polygon_overlap(p0, p1, minimal_area=False):
452452
453453
:param list(Polygon) p0: List of polygon to compare with p1 list
454454
:param list(Polygon) p1: List of polygon to compare with p0 list
455-
:param bool minimal_area: If True, function will compute intersection/little polygon, else intersection/union
455+
:param bool minimal_area: If True, function will compute intersection/smaller polygon, else intersection/union
456456
:return: Result of cost function
457457
:rtype: array
458458
"""
@@ -462,10 +462,10 @@ def polygon_overlap(p0, p1, minimal_area=False):
462462
p_ = p1[i]
463463
# Area of intersection
464464
intersection = (p0 & p_).area()
465-
# we divide intersection with the little one result from 0 to 1
465+
# we divide the intersection by the smaller area, result from 0 to 1
466466
if minimal_area:
467467
cost[i] = intersection / min(p0.area(), p_.area())
468-
# we divide intersection with polygon merging result from 0 to 1
468+
# we divide the intersection by the merged polygons area, result from 0 to 1
469469
else:
470470
cost[i] = intersection / (p0 + p_).area()
471471
return cost

0 commit comments

Comments
 (0)