11# -*- coding: utf-8 -*-
22"""
3- Class to manage observations gathered in track
3+ Class to manage observations gathered in trajectories
44"""
55import logging
66from 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 ()
0 commit comments