55from glob import glob
66import logging
77import time
8-
8+ from datetime import timedelta , datetime
9+ import os
910import netCDF4
1011from numba import njit , types as nb_types
1112from numba .typed import List
13+ import numpy as np
1214from numpy import (
1315 arange ,
1416 array ,
1517 bincount ,
1618 bool_ ,
1719 concatenate ,
20+
1821 empty ,
1922 nan ,
2023 ones ,
@@ -119,13 +122,15 @@ def __repr__(self):
119122 period = (self .period [1 ] - self .period [0 ]) / 365.25
120123 nb_by_network = self .network_size ()
121124 nb_trash = 0 if self .ref_index != 0 else nb_by_network [0 ]
125+ lifetime = self .lifetime
122126 big = 50_000
123127 infos = [
124128 f"Atlas with { self .nb_network } networks ({ self .nb_network / period :0.0f} networks/year),"
125129 f" { self .nb_segment } segments ({ self .nb_segment / period :0.0f} segments/year), { len (self )} observations ({ len (self ) / period :0.0f} observations/year)" ,
126130 f" { m_event .size } merging ({ m_event .size / period :0.0f} merging/year), { s_event .size } splitting ({ s_event .size / period :0.0f} splitting/year)" ,
127131 f" with { (nb_by_network > big ).sum ()} network with more than { big } obs and the biggest have { nb_by_network .max ()} observations ({ nb_by_network [nb_by_network > big ].sum ()} observations cumulate)" ,
128132 f" { nb_trash } observations in trash" ,
133+ f" { lifetime .max ()} days max of lifetime" ,
129134 ]
130135 return "\n " .join (infos )
131136
@@ -200,6 +205,13 @@ def ref_segment_track_index(self):
200205 @property
201206 def ref_index (self ):
202207 return self .index_network [2 ]
208+
209+ @property
210+ def lifetime (self ):
211+ """Return lifetime for each observation"""
212+ lt = self .networks_period .astype ("int" )
213+ nb_by_network = self .network_size ()
214+ return lt .repeat (nb_by_network )
203215
204216 def network_segment_size (self , id_networks = None ):
205217 """Get number of segment by network
@@ -225,6 +237,15 @@ def network_size(self, id_networks=None):
225237 i = id_networks - self .index_network [2 ]
226238 return self .index_network [1 ][i ] - self .index_network [0 ][i ]
227239
240+ @property
241+ def networks_period (self ):
242+ """
243+ Return period for each network
244+ """
245+ return get_period_with_index (self .time , * self .index_network [:2 ])
246+
247+
248+
228249 def unique_segment_to_id (self , id_unique ):
229250 """Return id network and id segment for a unique id
230251
@@ -274,7 +295,7 @@ def astype(self, cls):
274295 new [k ][:] = self [k ][:]
275296 new .sign_type = self .sign_type
276297 return new
277-
298+
278299 def longer_than (self , nb_day_min = - 1 , nb_day_max = - 1 ):
279300 """
280301 Select network on time duration
@@ -1125,23 +1146,29 @@ def segment_track_array(self):
11251146 self ._segment_track_array = build_unique_array (self .segment , self .track )
11261147 return self ._segment_track_array
11271148
1128- def birth_event (self ):
1149+ def birth_event (self , only_index = False ):
11291150 """Extract birth events."""
11301151 i_start , _ , _ = self .index_segment_track
11311152 indices = i_start [self .previous_obs [i_start ] == - 1 ]
11321153 if self .first_is_trash ():
11331154 indices = indices [1 :]
1134- return self .extract_event (indices )
1135-
1155+ if only_index :
1156+ return indices
1157+ else :
1158+ return self .extract_event (indices )
1159+
11361160 generation_event = birth_event
11371161
1138- def death_event (self ):
1162+ def death_event (self , only_index = False ):
11391163 """Extract death events."""
11401164 _ , i_stop , _ = self .index_segment_track
11411165 indices = i_stop [self .next_obs [i_stop - 1 ] == - 1 ] - 1
11421166 if self .first_is_trash ():
11431167 indices = indices [1 :]
1144- return self .extract_event (indices )
1168+ if only_index :
1169+ return indices
1170+ else :
1171+ return self .extract_event (indices )
11451172
11461173 dissipation_event = death_event
11471174
@@ -1452,7 +1479,7 @@ def plot(self, ax, ref=None, color_cycle=None, **kwargs):
14521479 j += 1
14531480 return mappables
14541481
1455- def remove_dead_end (self , nobs = 3 , ndays = 0 , recursive = 0 , mask = None ):
1482+ def remove_dead_end (self , nobs = 3 , ndays = 0 , recursive = 0 , mask = None , return_mask = False ):
14561483 """
14571484 Remove short segments that don't connect several segments
14581485
@@ -1478,6 +1505,8 @@ def remove_dead_end(self, nobs=3, ndays=0, recursive=0, mask=None):
14781505 )
14791506 # get mask for selected obs
14801507 m = ~ self .segment_mask (segments_keep )
1508+ if return_mask :
1509+ return ~ m
14811510 self .track [m ] = 0
14821511 self .segment [m ] = 0
14831512 self .previous_obs [m ] = - 1
@@ -1495,6 +1524,8 @@ def remove_dead_end(self, nobs=3, ndays=0, recursive=0, mask=None):
14951524 self .sort ()
14961525 if recursive > 0 :
14971526 self .remove_dead_end (nobs , ndays , recursive - 1 )
1527+
1528+
14981529
14991530 def extract_segment (self , segments , absolute = False ):
15001531 """Extract given segments
@@ -2035,6 +2066,29 @@ def group_observations(self, min_overlap=0.2, minimal_area=False, **kwargs):
20352066 results , nb_obs = list (), list ()
20362067 # To display print only in INFO
20372068 display_iteration = logger .getEffectiveLevel () == logging .INFO
2069+
2070+
2071+ # Trier les fichiers par date
2072+ def extract_date (file ):
2073+ filename = os .path .basename (file )
2074+ date_str = filename .split ('_' )[- 1 ].split ('.' )[0 ] # Extraire la partie date (ex : "20180101")
2075+ return datetime .strptime (date_str , "%Y%m%d" ) # Convertir en objet datetime
2076+ self .filenames = sorted (self .filenames , key = extract_date )
2077+
2078+ # Detect missing date and print them to inform the user which files are missing
2079+ missing_dates = []
2080+ dates_list = [extract_date (self .filenames [i ]) for i in range (len (self .filenames ))]
2081+ for i in range (len (dates_list ) - 1 ):
2082+ expected_date = dates_list [i ] + timedelta (days = 1 )
2083+ while expected_date < dates_list [i + 1 ]:
2084+ missing_dates .append (expected_date )
2085+ expected_date += timedelta (days = 1 )
2086+ if missing_dates :
2087+ missing_str = ', ' .join (date .strftime ("%Y-%m-%d" ) for date in missing_dates )
2088+ raise Exception (f"Following files missing : { missing_str } " )
2089+ else :
2090+ print ("No missing files" )
2091+
20382092 for i , filename in enumerate (self .filenames ):
20392093 if display_iteration :
20402094 print (f"{ filename } compared to { self .window } next" , end = "\r " )
@@ -2316,3 +2370,19 @@ def mask_follow_obs(m, next_obs, time, indexs, dt=3):
23162370 m [i_next ] = True
23172371 i_next = next_obs [i_next ]
23182372 dt_ = abs (time [i_next ] - t0 )
2373+
2374+ @njit (cache = True )
2375+ def get_period_with_index (t , i0 , i1 ):
2376+ """Return peek to peek cover by each slice define by i0 and i1
2377+ :param array t: array which contain values to estimate spread
2378+ :param array i0: index which determine start of slice
2379+ :param array i1: index which determine end of slice
2380+ :return array: Peek to peek of t
2381+ """
2382+ periods = np .empty (i0 .size , t .dtype )
2383+ for i in range (i0 .size ):
2384+ if i1 [i ] == i0 [i ]:
2385+ periods [i ] = 0
2386+ continue
2387+ periods [i ] = t [i0 [i ] : i1 [i ]].ptp ()
2388+ return periods
0 commit comments