Skip to content

Commit e3ddc29

Browse files
author
adelepoulle
committed
Add virtual observation
1 parent 775751e commit e3ddc29

File tree

4 files changed

+184
-52
lines changed

4 files changed

+184
-52
lines changed

src/py_eddy_tracker/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ def parse_args(self, *args, **kwargs):
9393
description='cyclonic -1; anti-cyclonic +1',
9494
)
9595
),
96+
virtual=dict(
97+
attr_name=None,
98+
nc_name='virtual',
99+
nc_type='byte',
100+
nc_dims=('Nobs',),
101+
nc_attr=dict(
102+
long_name='virtual_position',
103+
units='boolean',
104+
description='Virtual observation: 0 for real',
105+
)
106+
),
96107
lon=dict(
97108
attr_name='lon',
98109
compute_type='float64',

src/py_eddy_tracker/py_eddy_tracker_property_classes.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ def elements(self):
114114

115115
def coherence(self, other):
116116
return self.track_extra_variables == other.track_extra_variables
117+
118+
def merge(self, other):
119+
nb_obs_self = len(self)
120+
nb_obs = nb_obs_self + len(other)
121+
eddies = self.__class__(size=nb_obs)
122+
eddies.obs[:nb_obs_self] = self.obs[:]
123+
eddies.obs[nb_obs_self:] = other.obs[:]
124+
eddies.sign_type = self.sign_type
125+
return eddies
117126

118127
def reset(self):
119128
self.observations = np.zeros(0, dtype=self.dtype)
@@ -185,12 +194,30 @@ def load_from_netcdf(filename):
185194
return eddies
186195

187196

197+
class VirtualEddiesObservations(EddiesObservations):
198+
199+
@property
200+
def elements(self):
201+
elements = super(VirtualEddiesObservations, self).elements
202+
elements.extend(['track'])
203+
return elements
204+
188205
class TrackEddiesObservations(EddiesObservations):
189206

207+
def extract_longer_eddies(self, nb_min, nb_obs):
208+
m = nb_obs >= nb_min
209+
nb_obs_select = m.sum()
210+
logging.info('Selection of %d observations', nb_obs_select)
211+
eddies = TrackEddiesObservations(size=nb_obs_select)
212+
eddies.sign_type = self.sign_type
213+
for var, _ in eddies.obs.dtype.descr:
214+
eddies.obs[var] = self.obs[var][m]
215+
return eddies
216+
190217
@property
191218
def elements(self):
192219
elements = super(TrackEddiesObservations, self).elements
193-
elements.extend(['track', 'n'])
220+
elements.extend(['track', 'n', 'virtual'])
194221
return elements
195222

196223
def create_variable(self, handler_nc, kwargs_variable,
@@ -201,10 +228,12 @@ def create_variable(self, handler_nc, kwargs_variable,
201228
**kwargs_variable)
202229
for attr, attr_value in attr_variable.iteritems():
203230
var.setncattr(attr, attr_value)
204-
var[:] = data
205-
var.set_auto_maskandscale(False)
206-
if scale_factor is not None:
207-
var.scale_factor = scale_factor
231+
232+
var[:] = data
233+
234+
#~ var.set_auto_maskandscale(False)
235+
if scale_factor is not None:
236+
var.scale_factor = scale_factor
208237

209238
try:
210239
var.setncattr('min', var[:].min())

src/py_eddy_tracker/tools.pyx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ctypedef double DTYPE_coord
1313

1414
cdef DTYPE_coord D2R = 0.017453292519943295
1515
cdef DTYPE_coord PI = 3.141592653589793
16+
cdef DTYPE_coord EARTH_DIAMETER = 6371.3150 * 2
1617

1718
@wraparound(False)
1819
@boundscheck(False)
@@ -229,18 +230,24 @@ def distance_matrix(
229230
ndarray[DTYPE_coord, ndim=2] dist,
230231
):
231232

232-
cdef DTYPE_coord sin_dlat, sin_dlon, cos_lat1, cos_lat2, a_val
233+
cdef DTYPE_coord sin_dlat, sin_dlon, cos_lat1, cos_lat2, a_val, dlon, dlat
233234
cdef DTYPE_ui i_elt0, i_elt1, nb_elt0, nb_elt1
234235
nb_elt0 = lon0.shape[0]
235236
nb_elt1 = lon1.shape[0]
236237
for i_elt0 from 0 <= i_elt0 < nb_elt0:
237238
for i_elt1 from 0 <= i_elt1 < nb_elt1:
238-
sin_dlat = sin((lat1[i_elt1] - lat0[i_elt0]) * 0.5 * D2R)
239-
sin_dlon = sin((lon1[i_elt1] - lon0[i_elt0]) * 0.5 * D2R)
239+
dlon = (lon1[i_elt1] - lon0[i_elt0] + 180) % 360 - 180
240+
if dlon > 20 or dlon < -20:
241+
continue
242+
dlat = lat1[i_elt1] - lat0[i_elt0]
243+
if dlat > 15 or dlat < -15:
244+
continue
245+
sin_dlat = sin(dlat * 0.5 * D2R)
246+
sin_dlon = sin(dlon * 0.5 * D2R)
240247
cos_lat1 = cos(lat0[i_elt0] * D2R)
241248
cos_lat2 = cos(lat1[i_elt1] * D2R)
242249
a_val = sin_dlon ** 2 * cos_lat1 * cos_lat2 + sin_dlat ** 2
243-
dist[i_elt0, i_elt1] = 6371315.0 * 2 * atan2(a_val ** 0.5, (1 - a_val) ** 0.5)
250+
dist[i_elt0, i_elt1] = EARTH_DIAMETER * atan2(a_val ** 0.5, (1 - a_val) ** 0.5)
244251

245252

246253
@wraparound(False)

src/scripts/EddyTracking

Lines changed: 128 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22
# -*- coding: utf-8 -*-
33
"""
44
"""
5+
56
from py_eddy_tracker import EddyParser
67
from glob import glob
78
from yaml import load as yaml_load
8-
from py_eddy_tracker.py_eddy_tracker_property_classes import EddiesObservations, TrackEddiesObservations
9+
from py_eddy_tracker.py_eddy_tracker_property_classes import EddiesObservations, TrackEddiesObservations, VirtualEddiesObservations
910
#~ from py_eddy_tracker.make_eddy_tracker_list_obj import RossbyWaveSpeed
11+
from py_eddy_tracker.tools import distance_matrix
12+
1013
import logging
1114
import numpy as np
1215
import datetime as dt
1316

1417
D2R = 0.017453292519943295
15-
def distance_matrix(lon0, lat0, lon1, lat1):
18+
#~ def distance_matrix(lon0, lat0, lon1, lat1):
19+
def distance_matrix2(lon0, lat0, lon1, lat1):
1620
"""Why it's quicker than cython version?
1721
"""
1822
nb_elt0 = lon0.shape[0]
@@ -26,6 +30,7 @@ def distance_matrix(lon0, lat0, lon1, lat1):
2630
a_val = sin_dlon ** 2 * cos_lat1 * cos_lat2 + sin_dlat ** 2
2731
return 6371.315 * 2 * np.arctan2(a_val ** 0.5, (1 - a_val) ** 0.5)
2832
UINT32_MAX = 4294967295
33+
UINT16_MAX = 65535
2934

3035
if __name__ == '__main__':
3136
# Run using:
@@ -40,6 +45,7 @@ if __name__ == '__main__':
4045
CONFIG = yaml_load(stream)
4146

4247
NB_OBS_MIN = int(CONFIG['TRACK_DURATION_MIN'])
48+
NB_VIRTUAL_OBS_MAX_BY_SEGMENT = int(CONFIG['VIRTUAL_LEGNTH_MAX'])
4349

4450
PATTERN = CONFIG['PATHS']['FILES_PATTERN']
4551
FILENAMES = glob(PATTERN)
@@ -53,105 +59,184 @@ if __name__ == '__main__':
5359
CURRENT_ID = 0
5460
CORRESPONDANCES = []
5561
START = True
62+
FLG_VIRTUAL = False
5663

5764
for file_name in FILENAMES[1:]:
5865
logging.debug('%s match with previous state', file_name)
5966
e_current = EddiesObservations.load_from_netcdf(file_name)
6067
logging.debug('%d obs to match', len(e_current))
61-
dist_result = distance_matrix(
62-
e_previous.obs['lon'], e_previous.obs['lat'],
63-
e_current.obs['lon'], e_current.obs['lat'])
64-
65-
#~ previous_amp, current_amp = np.meshgrid(
66-
#~ e_current.obs['amplitude'], e_previous.obs['amplitude'])
67-
#~ delta_amp = abs(current_amp - previous_amp) / previous_amp
68-
69-
#~ previous_radius, current_radius = np.meshgrid(
70-
#~ e_current.obs['radius_e'], e_previous.obs['radius_e'])
71-
#~ delta_radius = abs(current_radius ** 2 - previous_radius ** 2) ** .5 / previous_radius
7268

73-
i_previous, i_current = np.where(dist_result < 25)
69+
if FLG_VIRTUAL:
70+
nb_real_obs = len(e_previous)
71+
# If you comment this the virtual fonctionnality will be disable
72+
e_previous = e_previous.merge(virtual_obs)
73+
dist_result = np.empty((len(e_previous), len(e_current)), dtype='f8') + np.nan
74+
distance_matrix(
75+
e_previous.obs['lon'], e_previous.obs['lat'],
76+
e_current.obs['lon'], e_current.obs['lat'],
77+
dist_result)
78+
i_previous, i_current = np.where(dist_result < 5.)
7479
nb_match = i_previous.shape[0]
7580

7681
logging.debug('%d match with previous', nb_match)
77-
correspondance = np.array(i_previous, dtype=[('in', 'u2'), ('out', 'u2'), ('id', 'u4')])
82+
correspondance = np.array(i_previous, dtype=[('in', 'u2'), ('out', 'u2'), ('id', 'u4'), ('virtual', np.bool_)])
7883
correspondance['out'] = i_current
84+
85+
if FLG_VIRTUAL:
86+
correspondance['virtual'] = i_previous >= nb_real_obs
87+
88+
else:
89+
correspondance['virtual'] = False
90+
7991
if START:
8092
START=False
8193
# Set an id for each match
8294
correspondance['id'] = np.arange(nb_match)
8395
# Set counter
8496
CURRENT_ID += nb_match
8597
else:
98+
# We set all id to UINT32_MAX
8699
id_previous = np.ones(len(e_previous), dtype='u4') * UINT32_MAX
87-
id_previous[CORRESPONDANCES[-1]['out']] = CORRESPONDANCES[-1]['id']
100+
# We get old id for previously eddies tracked
101+
previous_id = CORRESPONDANCES[-1]['id']
102+
id_previous[CORRESPONDANCES[-1]['out']] = previous_id
88103
correspondance['id'] = id_previous[correspondance['in']]
89-
# new_id is -1 we must add a new ones
104+
if FLG_VIRTUAL:
105+
# Set id for virtual
106+
correspondance['id'][correspondance['virtual']] = \
107+
virtual_obs.obs['track'][correspondance['in'][correspondance['virtual']] - nb_real_obs]
108+
#~ correspondance['in'][correspondance['virtual']] = UINT16_MAX
109+
110+
# SECTION for virtual observation
111+
# List previous id which are not use in the next step
112+
dead_id = np.setdiff1d(previous_id, correspondance['id'])
113+
# Creation of an virtual step for dead one
114+
virtual_obs = VirtualEddiesObservations(size=len(dead_id))
115+
# Find mask/index on previous correspondance to extrapolate
116+
# position
117+
list_previous_id = previous_id.tolist()
118+
i_dead_id = [list_previous_id.index(i) for i in dead_id]
119+
120+
# Selection of observations on N-2 and N-1
121+
obs_a = e_previous2.obs[CORRESPONDANCES[-1][i_dead_id]['in']]
122+
obs_b = e_previous.obs[CORRESPONDANCES[-1][i_dead_id]['out']]
123+
# Position N-2 : A
124+
# Position N-1 : B
125+
# Virtual Position : C
126+
# New position C = B + AB
127+
virtual_obs.obs['lon'] = 2 * obs_b['lon'] - obs_a['lon']
128+
virtual_obs.obs['lat'] = 2 * obs_b['lat'] - obs_a['lat']
129+
# Id which are prolongated
130+
virtual_obs.obs['track'] = dead_id
131+
FLG_VIRTUAL = True
132+
# END
133+
134+
135+
# new_id is equal to UINT32_MAX we must add a new ones
90136
# we count the number of new
91137
mask_new_id = correspondance['id'] == UINT32_MAX
92138
nb_new_tracks = mask_new_id.sum()
93139
# Set new id
94140
correspondance['id'][mask_new_id] = np.arange(CURRENT_ID, CURRENT_ID + nb_new_tracks)
95141
# Set counter
96142
CURRENT_ID += nb_new_tracks
143+
97144
CORRESPONDANCES.append(correspondance)
98145

146+
e_previous2 = e_previous
99147
e_previous = e_current
148+
100149
logging.info('Track finish')
101150
logging.info('Start merging')
102151
# count obs by tracks
103152
nb_obs_by_tracks = np.zeros(CURRENT_ID, dtype='u2') + 1
104153
for correspondance in CORRESPONDANCES:
105154
nb_obs_by_tracks[correspondance['id']] += 1
155+
nb_obs_by_tracks[correspondance['id'][correspondance['virtual']]] += 1
106156

107157
i_current_by_tracks = nb_obs_by_tracks.cumsum() - nb_obs_by_tracks
108158
nb_obs = nb_obs_by_tracks.sum()
109159
logging.info('%d tracks will be create', CURRENT_ID)
110160
logging.info('%d observations will be join', nb_obs)
111-
# Start create netcdf to agglomerate all eddy
112-
161+
# Start create netcdf to agglomerate all eddy
113162
FINAL_EDDIES = TrackEddiesObservations(size=nb_obs)
114-
115-
lon = np.ones(nb_obs, dtype='f4') * np.nan
116-
lat = np.ones(nb_obs, dtype='f4') * np.nan
163+
117164
n = np.arange(nb_obs, dtype='u4') - i_current_by_tracks.repeat(nb_obs_by_tracks)
118165
FINAL_EDDIES.obs['n'] = np.uint16(n)
166+
FINAL_EDDIES.obs['track'] = np.arange(CURRENT_ID).repeat(nb_obs_by_tracks)
119167

120-
# Start
121-
eddies_init = EddiesObservations.load_from_netcdf(FILENAMES[0])
122-
FINAL_EDDIES.sign_type = eddies_init.sign_type
123-
i_id = CORRESPONDANCES[0]['id']
124-
index_final = i_current_by_tracks[i_id]
125-
index_source = CORRESPONDANCES[0]['in']
126-
# Copy all variable
127-
for var, _ in eddies_init.obs.dtype.descr:
128-
FINAL_EDDIES.obs[var][index_final] = eddies_init.obs[var][index_source]
168+
# Start loading identification again to save in the finals tracks
169+
# Load first file
170+
eddies_previous = EddiesObservations.load_from_netcdf(FILENAMES[0])
171+
# Set type of eddy with first file
172+
FINAL_EDDIES.sign_type = eddies_previous.sign_type
129173

130-
del eddies_init
131-
# Add increment for each index used
132-
i_current_by_tracks[i_id] += 1
174+
# To know if the track start
175+
first_obs_save_in_tracks = np.zeros(i_current_by_tracks.shape, dtype=np.bool_)
133176

134177
for i, file_name in enumerate(FILENAMES[1:]):
178+
# Load current file (we begin with second one)
135179
eddies_current = EddiesObservations.load_from_netcdf(file_name)
136-
180+
# We select the list of id which are involve in the correspondance
137181
i_id = CORRESPONDANCES[i]['id']
182+
# Index where we will write in the final object
138183
index_final = i_current_by_tracks[i_id]
139-
index_source = CORRESPONDANCES[i]['out']
184+
185+
# First obs of eddies
186+
m_first_obs = -first_obs_save_in_tracks[i_id]
187+
if m_first_obs.any():
188+
# Index in the current file
189+
index_in = CORRESPONDANCES[i]['in'][m_first_obs]
190+
# Copy all variable
191+
for var, _ in eddies_current.obs.dtype.descr:
192+
FINAL_EDDIES.obs[var][index_final[m_first_obs]] = eddies_previous.obs[var][index_in]
193+
# Increment
194+
i_current_by_tracks[i_id[m_first_obs]] += 1
195+
first_obs_save_in_tracks[i_id] = True
196+
index_final = i_current_by_tracks[i_id]
197+
198+
# Index in the current file
199+
index_current = CORRESPONDANCES[i]['out']
200+
# If the flag virtual in correspondance is active, the previous is virtual
201+
m_virtual = CORRESPONDANCES[i]['virtual']
202+
if m_virtual.any():
203+
index_virtual = index_final[m_virtual]
204+
FINAL_EDDIES.obs['virtual'][index_virtual] = 1
205+
i_current_by_tracks[i_id[m_virtual]] += 1
206+
index_final = i_current_by_tracks[i_id]
207+
140208
# Copy all variable
141209
for var, _ in eddies_current.obs.dtype.descr:
142-
FINAL_EDDIES.obs[var][index_final] = eddies_current.obs[var][index_source]
210+
FINAL_EDDIES.obs[var][index_final] = eddies_current.obs[var][index_current]
211+
212+
143213
# Add increment for each index used
144214
i_current_by_tracks[i_id] += 1
145-
146-
215+
eddies_previous = eddies_current
216+
217+
i = np.where(FINAL_EDDIES.obs['virtual'])[0]
218+
nb_virtual = FINAL_EDDIES.obs['virtual'].sum()
219+
logging.info('%d obs are virtual (unobserved)', nb_virtual)
220+
FINAL_EDDIES.obs['lon'][i] = (FINAL_EDDIES.obs['lon'][i - 1] + FINAL_EDDIES.obs['lon'][i + 1]) / 2
221+
FINAL_EDDIES.obs['lat'][i] = (FINAL_EDDIES.obs['lat'][i - 1] + FINAL_EDDIES.obs['lat'][i + 1]) / 2
222+
FINAL_EDDIES.obs['time'][i] = (FINAL_EDDIES.obs['time'][i - 1] + FINAL_EDDIES.obs['time'][i + 1]) / 2
223+
224+
147225
# Total running time
148226
logging.info('Mean duration by loop : %s',
149227
(dt.datetime.now() - START_TIME) / (len(FILENAMES) - 1))
150228
logging.info('Duration : %s', dt.datetime.now() - START_TIME)
151229

152230
logging.info('The longest tracks have %d observations', nb_obs_by_tracks.max())
153231

154-
FINAL_EDDIES.write_netcdf()
155-
156-
#~ nb_obs_by_tracks = nb_obs_by_tracks.repeat(nb_obs_by_tracks)
157-
#~ m = nb_obs_by_tracks > NB_OBS_MIN
232+
FINAL_EDDIES.extract_longer_eddies(NB_OBS_MIN, nb_obs_by_tracks.repeat(nb_obs_by_tracks)).write_netcdf()
233+
234+
235+
236+
#~ previous_amp, current_amp = np.meshgrid(
237+
#~ e_current.obs['amplitude'], e_previous.obs['amplitude'])
238+
#~ delta_amp = abs(current_amp - previous_amp) / previous_amp
239+
240+
#~ previous_radius, current_radius = np.meshgrid(
241+
#~ e_current.obs['radius_e'], e_previous.obs['radius_e'])
242+
#~ delta_radius = abs(current_radius ** 2 - previous_radius ** 2) ** .5 / previous_radius

0 commit comments

Comments
 (0)