Skip to content

Commit 864760f

Browse files
Merge pull request #207 from OceanParcels/script_convert_indexed_output_to_array
Script to convert indexed output to array
2 parents ccf9b98 + 0c57af2 commit 864760f

File tree

6 files changed

+668
-381
lines changed

6 files changed

+668
-381
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ install:
2929
- pip install -r requirements.txt
3030
- git submodule update --init --recursive
3131

32+
before_script: # configure a headless display to test plot generation
33+
- "export DISPLAY=:99.0"
34+
- "sh -e /etc/init.d/xvfb start"
35+
- sleep 3 # give xvfb some time to start
36+
3237
script:
3338
- export PYTHONPATH=`pwd`:$PYTHONPATH
3439
- flake8 parcels

examples/tutorial_delaystart.ipynb

Lines changed: 529 additions & 374 deletions
Large diffs are not rendered by default.

parcels/scripts/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .plotParticles import plotTrajectoriesFile # NOQA get flake8 to ignore unused import.
2+
from .convert_IndexedOutputToArray import * # NOQA
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from netCDF4 import Dataset
2+
import numpy as np
3+
from progressbar import ProgressBar
4+
from argparse import ArgumentParser
5+
6+
7+
def convert_IndexedOutputToArray(file_in, file_out):
8+
"""Script to convert a trajectory file as outputted by Parcels
9+
in Indexed format to the easier-to-handle Array format
10+
11+
:param file_in: name of the input file in Indexed format
12+
:param file_out: name of the output file"""
13+
14+
pfile_in = Dataset(file_in, 'r')
15+
if 'trajectory' in pfile_in.dimensions:
16+
print file_in+' appears to be in array format already. Doing nothing..'
17+
return
18+
19+
class IndexedTrajectories(object):
20+
"""IndexedTrajectories class that holds info on the indices where the
21+
individual particle trajectories start and end"""
22+
def __init__(self, pfile):
23+
self.ids = pfile.variables['trajectory'][:]
24+
self.indices = np.argsort(self.ids)
25+
self.ids = self.ids[self.indices]
26+
self.starts = np.insert([x + 1 for x in np.where(np.diff(self.ids) > 0)], 0, 0)
27+
self.ends = np.append(self.starts[1:], [len(self.ids)-1])
28+
self.lengths = self.ends - self.starts
29+
self.nid = len(self.starts)
30+
self.nobs = np.max(self.lengths)
31+
for i in range(self.nid):
32+
# Test whether all ids in a trajectory are the same
33+
assert all(self.ids[self.starts[i]:self.ends[i]] == self.ids[self.starts[i]])
34+
35+
trajs = IndexedTrajectories(pfile_in)
36+
37+
pfile_out = Dataset("%s" % file_out, "w", format="NETCDF4")
38+
pfile_out.createDimension("obs", trajs.nobs)
39+
pfile_out.createDimension("trajectory", trajs.nid)
40+
coords = ("trajectory", "obs")
41+
42+
id = pfile_out.createVariable("trajectory", "i4", ("trajectory",))
43+
id.long_name = "Unique identifier for each particle"
44+
id.cf_role = "trajectory_id"
45+
id[:] = np.array([trajs.ids[p] for p in trajs.starts])
46+
47+
# create dict of all variables, except 'trajectory' as that is already created above
48+
var = {}
49+
for v in pfile_in.variables:
50+
if str(v) != 'trajectory':
51+
varin = pfile_in.variables[v]
52+
var[v] = pfile_out.createVariable(v, "f4", coords, fill_value=np.nan)
53+
# copy all attributes, except Fill_Value which is set automatically
54+
var[v].setncatts({k: varin.getncattr(k) for k in varin.ncattrs() if k != '_FillValue'})
55+
56+
pbar = ProgressBar()
57+
for i in pbar(range(trajs.nid)):
58+
ii = np.sort(trajs.indices[trajs.starts[i]:trajs.ends[i]])
59+
for v in var:
60+
var[v][i, 0:trajs.lengths[i]] = pfile_in.variables[v][ii]
61+
62+
pfile_out.sync()
63+
64+
65+
if __name__ == "__main__":
66+
p = ArgumentParser(description="""Converting Indexed Parcels output to Array format""")
67+
p.add_argument('-i', '--file_in', type=str,
68+
help='Name of input file in indexed form')
69+
p.add_argument('-o', '--file_out', type=str,
70+
help='Name of output file in array form')
71+
args = p.parse_args()
72+
convert_IndexedOutputToArray(file_in=args.file_in, file_out=args.file_out)

parcels/scripts/plotParticles.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
14-
tracerlon='x', tracerlat='y', recordedvar=None):
14+
tracerlon='x', tracerlat='y', recordedvar=None, show_plt=True):
1515
"""Quick and simple plotting of Parcels trajectories
1616
1717
:param filename: Name of Parcels-generated NetCDF file with particle positions
@@ -24,6 +24,7 @@ def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
2424
:param tracerlat: Name of latitude dimension of variable to show as background
2525
:param recordedvar: Name of variable used to color particles in scatter-plot.
2626
Only works in 'movie2d' or 'movie2d_notebook' mode.
27+
:param show_plt: Boolean whether plot should directly be show (for py.test)
2728
"""
2829

2930
if plt is None:
@@ -34,10 +35,10 @@ def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
3435
lon = pfile.variables['lon']
3536
lat = pfile.variables['lat']
3637
z = pfile.variables['z']
38+
time = pfile.variables['time'][:]
3739
if len(lon.shape) == 1:
3840
type = 'indexed'
3941
id = pfile.variables['trajectory'][:]
40-
time = pfile.variables['time'][:]
4142
else:
4243
type = 'array'
4344

@@ -59,7 +60,7 @@ def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
5960
for p in range(len(lon)):
6061
ax.plot(lon[p, :], lat[p, :], z[p, :], '.-')
6162
elif type == 'indexed':
62-
for t in range(max(id)+1):
63+
for t in np.unique(id):
6364
ax.plot(lon[id == t], lat[id == t],
6465
z[id == t], '.-')
6566
ax.set_xlabel('Longitude')
@@ -69,11 +70,18 @@ def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
6970
if type == 'array':
7071
plt.plot(np.transpose(lon), np.transpose(lat), '.-')
7172
elif type == 'indexed':
72-
for t in range(max(id)+1):
73+
for t in np.unique(id):
7374
plt.plot(lon[id == t], lat[id == t], '.-')
7475
plt.xlabel('Longitude')
7576
plt.ylabel('Latitude')
7677
elif mode == 'movie2d' or 'movie2d_notebook':
78+
if type == 'array' and any(time[:, 0] != time[0, 0]):
79+
# since particles don't start at the same time, treat as indexed
80+
type = 'indexed'
81+
id = pfile.variables['trajectory'][:].flatten()
82+
lon = lon[:].flatten()
83+
lat = lat[:].flatten()
84+
time = time.flatten()
7785

7886
fig = plt.figure()
7987
ax = plt.axes(xlim=(np.amin(lon), np.amax(lon)), ylim=(np.amin(lat), np.amax(lat)))
@@ -84,7 +92,7 @@ def plotTrajectoriesFile(filename, mode='2d', tracerfile=None, tracerfield='P',
8492
mintime = min(time)
8593
scat = ax.scatter(lon[time == mintime], lat[time == mintime],
8694
s=60, cmap=plt.get_cmap('autumn'))
87-
frames = np.unique(time)
95+
frames = np.unique(time[~np.isnan(time)])
8896

8997
def animate(t):
9098
if type == 'array':
@@ -102,7 +110,9 @@ def animate(t):
102110
plt.close()
103111
return anim
104112
else:
105-
plt.show()
113+
if show_plt:
114+
plt.show()
115+
return plt
106116

107117

108118
if __name__ == "__main__":
@@ -125,4 +135,5 @@ def animate(t):
125135

126136
plotTrajectoriesFile(args.particlefile, mode=args.mode, tracerfile=args.tracerfile,
127137
tracerfield=args.tracerfilefield, tracerlon=args.tracerfilelon,
128-
tracerlat=args.tracerfilelat, recordedvar=args.recordedvar)
138+
tracerlat=args.tracerfilelat, recordedvar=args.recordedvar,
139+
show_plt=True)

tests/test_scripts.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from parcels import (FieldSet, ParticleSet, JITParticle, AdvectionRK4,
2+
plotTrajectoriesFile, convert_IndexedOutputToArray)
3+
from datetime import timedelta as delta
4+
import numpy as np
5+
import pytest
6+
from os import path, pardir
7+
8+
9+
def create_outputfiles(dir):
10+
datafile = path.join(path.dirname(__file__), pardir, 'examples',
11+
'Peninsula_data', 'peninsula')
12+
13+
fieldset = FieldSet.from_nemo(datafile, allow_time_extrapolation=True)
14+
pset = ParticleSet(fieldset=fieldset, lon=[], lat=[], pclass=JITParticle)
15+
npart = 10
16+
delaytime = delta(hours=1)
17+
endtime = delta(hours=24)
18+
x = 3. * (1. / 1.852 / 60)
19+
y = (fieldset.U.lat[0] + x, fieldset.U.lat[-1] - x)
20+
lat = np.linspace(y[0], y[1], npart, dtype=np.float32)
21+
22+
fp_index = dir.join("DelayParticle")
23+
output_file = pset.ParticleFile(name=fp_index, type="indexed")
24+
25+
for t in range(npart):
26+
pset.add(JITParticle(lon=x, lat=lat[t], fieldset=fieldset))
27+
pset.execute(AdvectionRK4, runtime=delaytime, dt=delta(minutes=5),
28+
interval=delaytime, starttime=delaytime*t, output_file=output_file)
29+
30+
pset.execute(AdvectionRK4, runtime=endtime-npart*delaytime, starttime=delaytime*npart,
31+
dt=delta(minutes=5), interval=delta(hours=1), output_file=output_file)
32+
33+
fp_array = dir.join("DelayParticle_array")
34+
convert_IndexedOutputToArray(fp_index+'.nc', fp_array+'.nc')
35+
return fp_index, fp_array
36+
37+
38+
@pytest.mark.parametrize('mode', ['2d', '3d', 'movie2d'])
39+
@pytest.mark.parametrize('fp_type', ['index', 'array'])
40+
def test_plotting(mode, tmpdir, fp_type):
41+
fp_index, fp_array = create_outputfiles(tmpdir)
42+
fp = fp_array if fp_type == 'array' else fp_index
43+
plotTrajectoriesFile(fp+'.nc', mode=mode, show_plt=False)

0 commit comments

Comments
 (0)