Skip to content

Commit d6164ed

Browse files
committed
add a fonction to get distance to next obs
1 parent 9e0a969 commit d6164ed

File tree

10 files changed

+133
-78
lines changed

10 files changed

+133
-78
lines changed

doc/autodoc/tracking.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Correspondances
2-
============
2+
===============
33

44
.. automodule:: py_eddy_tracker.tracking
55
:members:

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Welcome to py-eddy-tracker's documentation!
4242
autodoc/observations
4343
autodoc/eddy_feature
4444
autodoc/featured_tracking
45+
autodoc/tracking
4546
autodoc/poly
4647
autodoc/generic
4748

examples/10_tracking_diagnostics/pet_propagation.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,9 @@
55
"""
66
from matplotlib import pyplot as plt
77
from py_eddy_tracker.observations.tracking import TrackEddiesObservations
8-
from py_eddy_tracker.generic import distance
8+
from py_eddy_tracker.generic import cumsum_by_track
99
import py_eddy_tracker_sample
10-
from numpy import arange, empty
11-
from numba import njit
12-
13-
14-
# %%
15-
# We will create a function compile with numba, to compute a field which contains curvilign distance
16-
@njit(cache=True)
17-
def cum_distance_by_track(distance, track):
18-
tr_previous = 0
19-
d_cum = 0
20-
new_distance = empty(track.shape, dtype=distance.dtype)
21-
for i in range(distance.shape[0]):
22-
tr = track[i]
23-
if i != 0 and tr != tr_previous:
24-
d_cum = 0
25-
new_distance[i] = d_cum
26-
d_cum += distance[i]
27-
tr_previous = tr
28-
new_distance[i + 1] = d_cum
29-
return new_distance
30-
10+
from numpy import arange
3111

3212
# %%
3313
# Load an experimental med atlas over a period of 26 years (1993-2019)
@@ -45,10 +25,8 @@ def cum_distance_by_track(distance, track):
4525

4626
# %%
4727
# Compute curvilign distance
48-
d_a = distance(a.longitude[:-1], a.latitude[:-1], a.longitude[1:], a.latitude[1:])
49-
d_c = distance(c.longitude[:-1], c.latitude[:-1], c.longitude[1:], c.latitude[1:])
50-
d_a = cum_distance_by_track(d_a, a["track"]) / 1000.0
51-
d_c = cum_distance_by_track(d_c, c["track"]) / 1000.0
28+
d_a = cumsum_by_track(a.distance_to_next(), a.tracks) / 1000.0
29+
d_c = cumsum_by_track(c.distance_to_next(), c.tracks) / 1000.0
5230

5331
# %%
5432
# Plot

notebooks/python_module/10_tracking_diagnostics/pet_propagation.ipynb

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"cell_type": "markdown",
1616
"metadata": {},
1717
"source": [
18-
"\nPropagation Histogram\n===================\n"
18+
"\n# Propagation Histogram\n"
1919
]
2020
},
2121
{
@@ -26,25 +26,7 @@
2626
},
2727
"outputs": [],
2828
"source": [
29-
"from matplotlib import pyplot as plt\nfrom py_eddy_tracker.observations.tracking import TrackEddiesObservations\nfrom py_eddy_tracker.generic import distance\nimport py_eddy_tracker_sample\nfrom numpy import arange, empty\nfrom numba import njit"
30-
]
31-
},
32-
{
33-
"cell_type": "markdown",
34-
"metadata": {},
35-
"source": [
36-
"We will create a function compile with numba, to compute a field which contains curvilign distance\n\n"
37-
]
38-
},
39-
{
40-
"cell_type": "code",
41-
"execution_count": null,
42-
"metadata": {
43-
"collapsed": false
44-
},
45-
"outputs": [],
46-
"source": [
47-
"@njit(cache=True)\ndef cum_distance_by_track(distance, track):\n tr_previous = 0\n d_cum = 0\n new_distance = empty(track.shape, dtype=distance.dtype)\n for i in range(distance.shape[0]):\n tr = track[i]\n if i != 0 and tr != tr_previous:\n d_cum = 0\n new_distance[i] = d_cum\n d_cum += distance[i]\n tr_previous = tr\n new_distance[i + 1] = d_cum\n return new_distance"
29+
"from matplotlib import pyplot as plt\nfrom py_eddy_tracker.observations.tracking import TrackEddiesObservations\nfrom py_eddy_tracker.generic import cumsum_by_track\nimport py_eddy_tracker_sample\nfrom numpy import arange"
4830
]
4931
},
5032
{
@@ -98,7 +80,7 @@
9880
},
9981
"outputs": [],
10082
"source": [
101-
"d_a = distance(a.longitude[:-1], a.latitude[:-1], a.longitude[1:], a.latitude[1:])\nd_c = distance(c.longitude[:-1], c.latitude[:-1], c.longitude[1:], c.latitude[1:])\nd_a = cum_distance_by_track(d_a, a[\"track\"]) / 1000.0\nd_c = cum_distance_by_track(d_c, c[\"track\"]) / 1000.0"
83+
"d_a = cumsum_by_track(a.distance_to_next(), a.tracks) / 1000.0\nd_c = cumsum_by_track(c.distance_to_next(), c.tracks) / 1000.0"
10284
]
10385
},
10486
{

src/py_eddy_tracker/__init__.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
import logging
2626
import numpy
2727
import zarr
28+
from ._version import get_versions
29+
30+
__version__ = get_versions()["version"]
31+
del get_versions
2832

2933

3034
def start_logger():
@@ -140,6 +144,18 @@ def parse_args(self, *args, **kwargs):
140144
nc_dims=("obs",),
141145
nc_attr=dict(),
142146
),
147+
distance_next=dict(
148+
attr_name=None,
149+
nc_name="distance_next",
150+
nc_type="float32",
151+
output_type="uint16",
152+
scale_factor=50.,
153+
nc_dims=("obs",),
154+
nc_attr=dict(
155+
long_name="Distance to next position",
156+
units='m',
157+
),
158+
),
143159
virtual=dict(
144160
attr_name=None,
145161
nc_name="observation_flag",
@@ -622,8 +638,3 @@ def parse_args(self, *args, **kwargs):
622638
VAR_DESCR_inv[VAR_DESCR[key]["nc_name"]] = key
623639
for key_old in VAR_DESCR[key].get("old_nc_name", list()):
624640
VAR_DESCR_inv[key_old] = key
625-
626-
from ._version import get_versions
627-
628-
__version__ = get_versions()["version"]
629-
del get_versions

src/py_eddy_tracker/appli/eddies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def display_infos():
109109
print(f"-- {filename} -- ")
110110
if track:
111111
vars_ = vars.copy()
112-
vars_.extend(('track', 'observation_number', 'observation_flag'))
112+
vars_.extend(('track', 'observation_number', 'observation_flag', 'longitude'))
113113
e = TrackEddiesObservations.load_file(filename, include_vars=vars_)
114114
else:
115115
e = EddiesObservations.load_file(filename, include_vars=vars)

src/py_eddy_tracker/appli/gui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def setup(self, cmap="jet", nb_step=25, figsize=(8, 6)):
5858
# plot
5959
self.fig = pyplot.figure(figsize=figsize)
6060
t0, t1 = self.period
61-
self.fig.suptitle(f'{t0} -> {t1}')
61+
self.fig.suptitle(f"{t0} -> {t1}")
6262
self.ax = self.fig.add_axes((0.05, 0.05, 0.9, 0.9))
6363
self.ax.set_xlim(x_min, x_max), self.ax.set_ylim(y_min, y_max)
6464
self.ax.set_aspect("equal")

src/py_eddy_tracker/generic.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,30 @@ def distance(lon0, lat0, lon1, lat1):
145145
return 6370997.0 * 2 * arctan2(a_val ** 0.5, (1 - a_val) ** 0.5)
146146

147147

148+
@njit(cache=True)
149+
def cumsum_by_track(field, track):
150+
"""
151+
Cumsum by track
152+
153+
:param array field: data to sum
154+
:pram array(int) track: id of track to separate data
155+
:return: cumsum with a reset at each start of track
156+
:rtype: array
157+
"""
158+
tr_previous = 0
159+
d_cum = 0
160+
cumsum_array = empty(track.shape, dtype=field.dtype)
161+
for i in range(field.shape[0]):
162+
tr = track[i]
163+
if tr != tr_previous:
164+
d_cum = 0
165+
cumsum_array[i] = d_cum
166+
d_cum += field[i]
167+
tr_previous = tr
168+
cumsum_array[i + 1] = d_cum
169+
return cumsum_array
170+
171+
148172
@njit(cache=True, fastmath=True)
149173
def interp2d_geo(x_g, y_g, z_g, m_g, x, y):
150174
"""

src/py_eddy_tracker/observations/observation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
from tokenize import TokenError
6161
from tarfile import ExFileObject
6262
from matplotlib.path import Path as BasePath
63-
from .. import VAR_DESCR, VAR_DESCR_inv
63+
from .. import VAR_DESCR, VAR_DESCR_inv, __version__
6464
from ..generic import (
6565
distance_grid,
6666
distance,
@@ -1318,6 +1318,7 @@ def global_attr(self):
13181318
Metadata_Conventions="Unidata Dataset Discovery v1.0",
13191319
comment="Surface product; mesoscale eddies",
13201320
framework_used="https://github.com/AntSimi/py-eddy-tracker",
1321+
framework_version=__version__,
13211322
standard_name_vocabulary="NetCDF Climate and Forecast (CF) Metadata Convention Standard Name Table",
13221323
rotation_type=self.sign_type,
13231324
)

src/py_eddy_tracker/observations/tracking.py

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@
3838
zeros,
3939
array,
4040
median,
41-
histogram
41+
histogram,
4242
)
4343
from datetime import datetime, timedelta
4444
from numba import njit
4545
from Polygon import Polygon
4646
from .observation import EddiesObservations
4747
from .. import VAR_DESCR_inv
48-
from ..generic import split_line, wrap_longitude, build_index
48+
from ..generic import split_line, wrap_longitude, build_index, distance, cumsum_by_track
4949
from ..poly import polygon_overlap, create_vertice_from_2darray
5050

5151

@@ -56,7 +56,7 @@ class TrackEddiesObservations(EddiesObservations):
5656
"""Class to practice Tracking on observations
5757
"""
5858

59-
__slots__ = ("__obs_by_track", "__first_index_of_track")
59+
__slots__ = ("__obs_by_track", "__first_index_of_track", "__nb_track")
6060

6161
ELEMENTS = [
6262
"lon",
@@ -85,6 +85,16 @@ def __init__(self, *args, **kwargs):
8585
super().__init__(*args, **kwargs)
8686
self.__first_index_of_track = None
8787
self.__obs_by_track = None
88+
self.__nb_track = None
89+
90+
@property
91+
def nb_tracks(self):
92+
"""
93+
Will count and send number of track
94+
"""
95+
if self.__nb_track is None:
96+
self.__nb_track = (self.nb_obs_by_track != 0).sum()
97+
return self.__nb_track
8898

8999
def __repr__(self):
90100
content = super().__repr__()
@@ -94,23 +104,56 @@ def __repr__(self):
94104
nb_obs = self.observations.shape[0]
95105
m = self["virtual"].astype("bool")
96106
nb_m = m.sum()
97-
bins_t = (0, 20, 50, 100, 200, 1000, 10000)
107+
bins_t = (1, 20, 50, 100, 200, 1000, 10000)
98108
nb_tracks_by_t = histogram(nb, bins=bins_t)[0]
99109
nb_obs_by_t = histogram(nb, bins=bins_t, weights=nb)[0]
100-
pct_tracks_by_t = nb_tracks_by_t / nb_tracks_by_t.sum() * 100.
101-
pct_obs_by_t = nb_obs_by_t / nb_obs_by_t.sum() * 100.
110+
pct_tracks_by_t = nb_tracks_by_t / nb_tracks_by_t.sum() * 100.0
111+
pct_obs_by_t = nb_obs_by_t / nb_obs_by_t.sum() * 100.0
112+
d = self.distance_to_next() / 1000.0
113+
cum_d = cumsum_by_track(d, self.tracks)
114+
m_last = ones(d.shape, dtype="bool")
115+
m_last[-1] = False
116+
m_last[self.index_from_track[1:] - 1] = False
102117
content += f"""
103-
| {nb.shape[0]} tracks ({nb.mean():.2f} obs/tracks, shorter {nb.min()} obs, longer {nb.max()} obs)
104-
| {nb_m} filled observations ({nb_m / nb.shape[0]:.2f} obs/tracks, {nb_m / nb_obs * 100:.2f} % of total)
105-
| Intepolated speed area : {self["speed_area"][m].sum() / period / 1e12:.2f} Mkm²/day
106-
| Intepolated effective area : {self["effective_area"][m].sum() / period / 1e12:.2f} Mkm²/day
118+
| {self.nb_tracks} tracks ({
119+
nb_obs / self.nb_tracks:.2f} obs/tracks, shorter {nb[nb!=0].min()} obs, longer {nb.max()} obs)
120+
| {nb_m} filled observations ({nb_m / self.nb_tracks:.2f} obs/tracks, {nb_m / nb_obs * 100:.2f} % of total)
121+
| Intepolated speed area : {self["speed_area"][m].sum() / period / 1e12:.2f} Mkm²/day
122+
| Intepolated effective area : {self["effective_area"][m].sum() / period / 1e12:.2f} Mkm²/day
123+
| Distance by day : Mean {d[m_last].mean():.2f} , Median {median(d[m_last]):.2f} km/day
124+
| Distance by track : Mean {cum_d[~m_last].mean():.2f} , Median {median(cum_d[~m_last]):.2f} km/track
107125
----Distribution in lifetime:
108126
| Lifetime (days ) {self.box_display(bins_t)}
109127
| Percent of tracks : {self.box_display(pct_tracks_by_t)}
110-
| Percent of eddies : {self.box_display(pct_obs_by_t)}
111-
"""
128+
| Percent of eddies : {self.box_display(pct_obs_by_t)}"""
112129
return content
113130

131+
def add_distance(self):
132+
"""Add a field of distance (m) between to consecutive observation, 0 for the last observation of each track
133+
"""
134+
if "distance_next" in self.observations.dtype.descr:
135+
return self
136+
new = self.add_fields(("distance_next",))
137+
new["distance_next"][:1] = self.distance_to_next()
138+
return new
139+
140+
def distance_to_next(self):
141+
"""
142+
:return: array of distance in m, 0 when next obs if from another track
143+
:rtype: array
144+
"""
145+
d = distance(
146+
self.longitude[:-1],
147+
self.latitude[:-1],
148+
self.longitude[1:],
149+
self.latitude[1:],
150+
)
151+
d[self.index_from_track[1:] - 1] = 0
152+
d_ = empty(d.shape[0] + 1, dtype=d.dtype)
153+
d_[:-1] = d
154+
d_[-1] = 0
155+
return d_
156+
114157
def filled_by_interpolation(self, mask):
115158
"""Filled selected values by interpolation
116159
"""
@@ -329,15 +372,14 @@ def __extract_with_mask(
329372
):
330373
"""
331374
Extract a subset of observations
332-
Args:
333-
mask: mask to select observations
334-
full_path: extract full path if only one part is selected
335-
remove_incomplete: delete path which are not fully selected
336-
compress_id: resample track number to use a little range
337-
reject_virtual: if track are only virtual in selection we remove track
338-
339-
Returns:
340-
same object with selected observations
375+
376+
:param array(bool) mask: mask to select observations
377+
:param full_path: extract full path if only one part is selected
378+
:param remove_incomplete: delete path which are not fully selected
379+
:param compress_id: resample track number to use a little range
380+
:param reject_virtual: if track are only virtual in selection we remove track
381+
:return: same object with selected observations
382+
:rtype: self
341383
"""
342384
if full_path and remove_incomplete:
343385
logger.warning(
@@ -373,6 +415,14 @@ def __extract_with_mask(
373415
return new
374416

375417
def plot(self, ax, ref=None, **kwargs):
418+
"""
419+
This function will draw path of each track
420+
421+
:param matplotlib.axes.Axes ax: ax where drawed
422+
:param float,int ref: if defined all coordinates will be wrapped with ref like west boundary
423+
:param dict kwargs: keyword arguments for Axes.plot
424+
:return: matplotlib mappable
425+
"""
376426
if "label" in kwargs:
377427
kwargs["label"] += " (%s eddies)" % (self.nb_obs_by_track != 0).sum()
378428
x, y = split_line(self.longitude, self.latitude, self.tracks)
@@ -436,7 +486,15 @@ def split_network(self, intern=True, **kwargs):
436486
# )
437487

438488
def set_tracks(self, x, y, ids, window):
439-
# Will split one group in tracks
489+
"""
490+
Will split one group in tracks
491+
492+
:param array x: coordinates of group
493+
:param array y: coordinates of group
494+
:param ndarray ids: several fields like time, group, ...
495+
:param int windows: number of days where observations could missed
496+
"""
497+
440498
time_index = build_index(ids["time"])
441499
nb = x.shape[0]
442500
used = zeros(nb, dtype="bool")

0 commit comments

Comments
 (0)