1
1
# -*- coding: utf-8 -*-
2
2
"""
3
- Class to manage observations gathered in track
3
+ Class to manage observations gathered in trajectories
4
4
"""
5
5
import logging
6
6
from datetime import datetime , timedelta
@@ -123,7 +123,7 @@ def __repr__(self):
123
123
return content
124
124
125
125
def add_distance (self ):
126
- """Add a field of distance (m) between to consecutive observation , 0 for the last observation of each track"""
126
+ """Add a field of distance (m) between two consecutive observations , 0 for the last observation of each track"""
127
127
if "distance_next" in self .observations .dtype .descr :
128
128
return self
129
129
new = self .add_fields (("distance_next" ,))
@@ -181,7 +181,7 @@ def filled_by_interpolation(self, mask):
181
181
)
182
182
183
183
def extract_longer_eddies (self , nb_min , nb_obs , compress_id = True ):
184
- """Select eddies which are longer than nb_min"""
184
+ """Select the trajectories longer than nb_min"""
185
185
mask = nb_obs >= nb_min
186
186
nb_obs_select = mask .sum ()
187
187
logger .info ("Selection of %d observations" , nb_obs_select )
@@ -226,9 +226,9 @@ def set_global_attr_netcdf(self, h_nc):
226
226
227
227
def extract_with_period (self , period , ** kwargs ):
228
228
"""
229
- Extract with a period
229
+ Extract within a time period
230
230
231
- :param (int,int) period: two date to define period, must be specify from 1/1/1950
231
+ :param (int,int) period: two dates to define the period, must be specify from 1/1/1950
232
232
:param dict kwargs: look at :py:meth:`extract_with_mask`
233
233
:return: Return all eddy tracks which are in bounds
234
234
:rtype: TrackEddiesObservations
@@ -251,9 +251,9 @@ def extract_with_period(self, period, **kwargs):
251
251
252
252
def get_azimuth (self , equatorward = False ):
253
253
"""
254
- Return azimuth for each tracks .
254
+ Return azimuth for each track .
255
255
256
- Azimuth is compute with first and last observation
256
+ Azimuth is computed with first and last observation
257
257
258
258
:param bool equatorward: If True, Poleward are positive and equatorward negative
259
259
:rtype: array
@@ -285,7 +285,7 @@ def compute_index(self):
285
285
"""
286
286
if self .__first_index_of_track is None :
287
287
s = self .tracks .max () + 1
288
- # Doesn't work => core dump with numba, maybe he wait i8 instead of u4
288
+ # Doesn't work => core dump with numba, maybe he wants i8 instead of u4
289
289
# self.__first_index_of_track = -ones(s, self.tracks.dtype)
290
290
# self.__obs_by_track = zeros(s, self.observation_number.dtype)
291
291
self .__first_index_of_track = - ones (s , "i8" )
@@ -333,12 +333,12 @@ def nb_obs_by_track(self):
333
333
334
334
@property
335
335
def lifetime (self ):
336
- """Return for each observation lifetime """
336
+ """Return lifetime for each observation"""
337
337
return self .nb_obs_by_track .repeat (self .nb_obs_by_track )
338
338
339
339
@property
340
340
def age (self ):
341
- """Return for each observation age in %, will be [0:100]"""
341
+ """Return age in % for each observation , will be [0:100]"""
342
342
return self .n .astype ("f4" ) / (self .lifetime - 1 ) * 100.0
343
343
344
344
def extract_ids (self , tracks ):
@@ -347,10 +347,10 @@ def extract_ids(self, tracks):
347
347
348
348
def extract_toward_direction (self , west = True , delta_lon = None ):
349
349
"""
350
- Get eddy which go in same direction
350
+ Get trajectories going in the same direction
351
351
352
- :param bool west: Only eastward eddy if True return westward
353
- :param None,float delta_lon: Only eddy with more than delta_lon span in longitude
352
+ :param bool west: Only eastward eddies if True return westward
353
+ :param None,float delta_lon: Only eddies with more than delta_lon span in longitude
354
354
:return: Only eastern eddy
355
355
:rtype: __class__
356
356
@@ -397,10 +397,10 @@ def extract_in_direction(self, direction, value=0):
397
397
398
398
def extract_with_length (self , bounds ):
399
399
"""
400
- Return all observations in [b0:b1]
400
+ Return the observations within trajectories lasting between [b0:b1]
401
401
402
- :param (int,int) bounds: length min and max of selected eddies , if use of -1 this bound is not used
403
- :return: Return all eddy tracks which have length between bounds
402
+ :param (int,int) bounds: length min and max of the desired trajectories , if -1 this bound is not used
403
+ :return: Return all trajectories having length between bounds
404
404
:rtype: TrackEddiesObservations
405
405
406
406
.. minigallery:: py_eddy_tracker.TrackEddiesObservations.extract_with_length
@@ -460,11 +460,11 @@ def extract_with_mask(
460
460
Extract a subset of observations
461
461
462
462
:param array(bool) mask: mask to select observations
463
- :param bool full_path: extract full path if only one part is selected
464
- :param bool remove_incomplete: delete path which are not fully selected
465
- :param bool compress_id: resample track number to use a little range
466
- :param bool reject_virtual: if track are only virtual in selection we remove track
467
- :return: same object with selected observations
463
+ :param bool full_path: extract the full trajectory if only one part is selected
464
+ :param bool remove_incomplete: delete trajectory if not fully selected
465
+ :param bool compress_id: resample trajectory number to use a smaller range
466
+ :param bool reject_virtual: if only virtual are selected, the trajectory is removed
467
+ :return: same object with the selected observations
468
468
:rtype: self.__class__
469
469
"""
470
470
if full_path and remove_incomplete :
@@ -509,7 +509,9 @@ def re_reference_index(index, ref):
509
509
510
510
def shape_polygon (self , intern = False ):
511
511
"""
512
- Get polygons which enclosed each track
512
+ Get the polygon enclosing each trajectory.
513
+
514
+ The polygon merges the non-overlapping bounds of the specified contours
513
515
514
516
:param bool intern: If True use speed contour instead of effective contour
515
517
:rtype: list(array, array)
@@ -519,9 +521,9 @@ def shape_polygon(self, intern=False):
519
521
520
522
def display_shape (self , ax , ref = None , intern = False , ** kwargs ):
521
523
"""
522
- This function will draw shape of each track
524
+ This function will draw the shape of each trajectory
523
525
524
- :param matplotlib.axes.Axes ax: ax where drawed
526
+ :param matplotlib.axes.Axes ax: ax to draw
525
527
:param float,int ref: if defined all coordinates will be wrapped with ref like west boundary
526
528
:param bool intern: If True use speed contour instead of effective contour
527
529
:param dict kwargs: keyword arguments for Axes.plot
@@ -546,10 +548,10 @@ def display_shape(self, ax, ref=None, intern=False, **kwargs):
546
548
547
549
def close_tracks (self , other , nb_obs_min = 10 , ** kwargs ):
548
550
"""
549
- Get close from another atlas.
551
+ Get close trajectories from another atlas.
550
552
551
553
:param self other: Atlas to compare
552
- :param int nb_obs_min: Minimal number of overlap for one track
554
+ :param int nb_obs_min: Minimal number of overlap for one trajectory
553
555
:param dict kwargs: keyword arguments for match function
554
556
:return: return other atlas reduce to common track with self
555
557
@@ -576,10 +578,10 @@ def format_label(self, label):
576
578
577
579
def plot (self , ax , ref = None , ** kwargs ):
578
580
"""
579
- This function will draw path of each track
581
+ This function will draw path of each trajectory
580
582
581
- :param matplotlib.axes.Axes ax: ax where drawed
582
- :param float,int ref: if defined all coordinates will be wrapped with ref like west boundary
583
+ :param matplotlib.axes.Axes ax: ax to draw
584
+ :param float,int ref: if defined, all coordinates will be wrapped with ref like west boundary
583
585
:param dict kwargs: keyword arguments for Axes.plot
584
586
:return: matplotlib mappable
585
587
"""
@@ -594,7 +596,7 @@ def plot(self, ax, ref=None, **kwargs):
594
596
return ax .plot (x , y , ** kwargs )
595
597
596
598
def split_network (self , intern = True , ** kwargs ):
597
- """Divide each group in track """
599
+ """Return each group (network) divided in segments """
598
600
track_s , track_e , track_ref = build_index (self .tracks )
599
601
ids = empty (
600
602
len (self ),
@@ -609,17 +611,23 @@ def split_network(self, intern=True, **kwargs):
609
611
],
610
612
)
611
613
ids ["group" ], ids ["time" ] = self .tracks , self .time
612
- # To store id track
614
+ # Initialisation
615
+ # To store the id of the segments, the backward and forward cost associations
613
616
ids ["track" ], ids ["previous_cost" ], ids ["next_cost" ] = 0 , 0 , 0
617
+ # To store the indexes of the backward and forward observations associated
614
618
ids ["previous_obs" ], ids ["next_obs" ] = - 1 , - 1
619
+ # At the end, ids["previous_obs"] == -1 means the start of a non-split segment
620
+ # and ids["next_obs"] == -1 means the end of a non-merged segment
615
621
616
622
xname , yname = self .intern (intern )
617
623
for i_s , i_e in zip (track_s , track_e ):
618
624
if i_s == i_e or self .tracks [i_s ] == self .NOGROUP :
619
625
continue
620
626
sl = slice (i_s , i_e )
621
627
local_ids = ids [sl ]
628
+ # built segments with local indices
622
629
self .set_tracks (self [xname ][sl ], self [yname ][sl ], local_ids , ** kwargs )
630
+ # shift the local indices to the total indexation for the used observations
623
631
m = local_ids ["previous_obs" ] != - 1
624
632
local_ids ["previous_obs" ][m ] += i_s
625
633
m = local_ids ["next_obs" ] != - 1
@@ -628,7 +636,7 @@ def split_network(self, intern=True, **kwargs):
628
636
629
637
def set_tracks (self , x , y , ids , window , ** kwargs ):
630
638
"""
631
- Will split one group in tracks
639
+ Will split one group (network) in segments
632
640
633
641
:param array x: coordinates of group
634
642
:param array y: coordinates of group
@@ -640,18 +648,21 @@ def set_tracks(self, x, y, ids, window, **kwargs):
640
648
nb = x .shape [0 ]
641
649
used = zeros (nb , dtype = "bool" )
642
650
track_id = 1
643
- # build all polygon (need to check if wrap is needed)
651
+ # build all polygons (need to check if wrap is needed)
644
652
for i in range (nb ):
645
- # If observation already in one track, we go to the next one
653
+ # If the observation is already in one track, we go to the next one
646
654
if used [i ]:
647
655
continue
656
+ # Search a possible continuation (forward)
648
657
self .follow_obs (i , track_id , used , ids , x , y , * time_index , window , ** kwargs )
649
658
track_id += 1
650
- # Search a possible ancestor
659
+ # Search a possible ancestor (backward)
651
660
self .previous_obs (i , ids , x , y , * time_index , window , ** kwargs )
652
661
653
662
@classmethod
654
663
def follow_obs (cls , i_next , track_id , used , ids , * args , ** kwargs ):
664
+ """Associate the observations to the segments"""
665
+
655
666
while i_next != - 1 :
656
667
# Flag
657
668
used [i_next ] = True
@@ -675,14 +686,16 @@ def follow_obs(cls, i_next, track_id, used, ids, *args, **kwargs):
675
686
676
687
@staticmethod
677
688
def previous_obs (i_current , ids , x , y , time_s , time_e , time_ref , window , ** kwargs ):
689
+ """Backward association of observations to the segments"""
690
+
678
691
time_cur = ids ["time" ][i_current ]
679
692
t0 , t1 = time_cur - 1 - time_ref , max (time_cur - window - time_ref , 0 )
680
693
for t_step in range (t0 , t1 - 1 , - 1 ):
681
694
i0 , i1 = time_s [t_step ], time_e [t_step ]
682
695
# No observation at the time step
683
696
if i0 == i1 :
684
697
continue
685
- # Intersection / union, to be able to separte in case of multiple inside
698
+ # Search for overlaps
686
699
xi , yi , xj , yj = x [[i_current ]], y [[i_current ]], x [i0 :i1 ], y [i0 :i1 ]
687
700
ii , ij = bbox_intersection (xi , yi , xj , yj )
688
701
if len (ii ) == 0 :
@@ -703,6 +716,7 @@ def previous_obs(i_current, ids, x, y, time_s, time_e, time_ref, window, **kwarg
703
716
704
717
@staticmethod
705
718
def next_obs (i_current , ids , x , y , time_s , time_e , time_ref , window , ** kwargs ):
719
+ """Forward association of observations to the segments"""
706
720
time_max = time_e .shape [0 ] - 1
707
721
time_cur = ids ["time" ][i_current ]
708
722
t0 , t1 = time_cur + 1 - time_ref , min (time_cur + window - time_ref , time_max )
@@ -713,7 +727,7 @@ def next_obs(i_current, ids, x, y, time_s, time_e, time_ref, window, **kwargs):
713
727
# No observation at the time step
714
728
if i0 == i1 :
715
729
continue
716
- # Intersection / union, to be able to separte in case of multiple inside
730
+ # Search for overlaps
717
731
xi , yi , xj , yj = x [[i_current ]], y [[i_current ]], x [i0 :i1 ], y [i0 :i1 ]
718
732
ii , ij = bbox_intersection (xi , yi , xj , yj )
719
733
if len (ii ) == 0 :
0 commit comments