Skip to content

Commit 2786363

Browse files
EliEli
authored andcommitted
Moved separate_species to vtools.
1 parent 9cb76a4 commit 2786363

1 file changed

Lines changed: 219 additions & 0 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""Separation of tidal data into species
2+
The key function in this module is separate_species, which decomposes
3+
tides into subtidal, diurnal, semidiurnal and noise components.
4+
5+
The fileters are long, so the time resolution of the amplitude may be limited.
6+
A demo function is also provided that reads tide series (6min intervl)
7+
from input files, seperates the species, writes results and optionally plots
8+
an example
9+
"""
10+
11+
import argparse
12+
from vtools.datastore.read_ts import read_ts
13+
import datetime as dtm
14+
from vtools.functions.filter import cosine_lanczos
15+
from vtools.data.vtime import hours, minutes
16+
17+
18+
def separate_species(ts, noise_thresh_min=40):
19+
"""Separate species into subtidal, diurnal, semidiurnal and noise components
20+
21+
Input:
22+
ts: timeseries to be decomposed into species, assumed to be
23+
at six minute intervals. The filters used
24+
have long lenghts, so avoid missing data and allow for four extra
25+
days worth of data on each end.
26+
27+
Output:
28+
four regular time series, representing subtidal, diurnal, semi-diurnal and noise
29+
"""
30+
31+
# the first filter eliminates noise
32+
ts_denoise = cosine_lanczos(ts, cutoff_period=minutes(noise_thresh_min))
33+
ts_noise = ts - ts_denoise # this is the residual, the part that IS noise
34+
35+
# the filter length assumes 6 minute data. The resulting filter is 90 hours
36+
# long which is MUCH longer than the default because this filter has to be
37+
# really sharp
38+
assert ts.index.freq == minutes(6)
39+
# 14.5 hours = 870min
40+
ts_diurnal_and_low = cosine_lanczos(
41+
ts_denoise, cutoff_period=minutes(870), filter_len=900
42+
)
43+
ts_semidiurnal_and_high = ts_denoise - ts_diurnal_and_low
44+
45+
# The resulting filter is again 90 hours
46+
# long which is still a bit longer than the default. Again,
47+
# we want this filter to be pretty sharp.
48+
# ts_sub_tide=cosine_lanczos(ts_diurnal_and_low,cutoff_period=hours(40),
49+
# filter_len=900)
50+
ts_sub_tide = cosine_lanczos(ts_denoise, cutoff_period=hours(40), filter_len=900)
51+
ts_diurnal = ts_diurnal_and_low - ts_sub_tide
52+
return ts_sub_tide, ts_diurnal, ts_semidiurnal_and_high, ts_noise
53+
54+
55+
def write_th(filename, ts_output):
56+
"""This works fine for fairly big series"""
57+
fout = open(filename, "w")
58+
st = ts_output.ticks[0]
59+
for el in ts_output:
60+
tck = float(el.ticks - st)
61+
fout.write("%-12.1f %6.4f\n" % (tck, el.value))
62+
fout.close()
63+
64+
65+
def plot_result(ts, ts_semi, ts_diurnal, ts_sub_tide, station):
66+
import matplotlib.pyplot as plt
67+
68+
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex=True)
69+
70+
ax1.plot(ts.times, ts.data, color="black", linewidth=1, label="Original data")
71+
ax1.legend(loc="lower right") # ,prop=legend_font)
72+
ax1.set_ylabel("Elev (m)")
73+
ax1.grid(b=True, which="both", color="0.9", linestyle="-", linewidth=0.5)
74+
75+
ax2.plot(
76+
ts_semi.times, ts_semi.data, color="black", linewidth=1, label="Semidiurnal"
77+
)
78+
ax2.legend(loc="lower right")
79+
ax2.set_ylabel("Elev (m)")
80+
ax2.grid(b=True, which="both", color="0.9", linestyle="-", linewidth=0.5)
81+
82+
ax3.plot(
83+
ts_diurnal.times, ts_diurnal.data, color="black", linewidth=1, label="Diurnal"
84+
)
85+
ax3.legend(loc="lower right")
86+
ax3.set_ylabel("Elev (m)")
87+
ax3.grid(b=True, which="both", color="0.9", linestyle="-", linewidth=0.5)
88+
89+
ax4.plot(
90+
ts_sub_tide.times,
91+
ts_sub_tide.data,
92+
color="black",
93+
linewidth=1,
94+
label="Subtidal",
95+
)
96+
ax4.legend(loc="lower right")
97+
ax4.set_ylabel("Elev (m)")
98+
ax4.grid(b=True, which="both", color="0.9", linestyle="-", linewidth=0.5)
99+
100+
fig.tight_layout()
101+
fig.autofmt_xdate()
102+
plt.show()
103+
# plt.savefig('tidal_species_%s.png'%station,bbox_inches=0)
104+
plt.close(fig)
105+
106+
107+
################# command line application #####################
108+
def create_arg_parser():
109+
import textwrap
110+
111+
parser = argparse.ArgumentParser(
112+
formatter_class=argparse.RawDescriptionHelpFormatter,
113+
epilog=textwrap.dedent(
114+
"""
115+
============== Example ==================
116+
> separate_species.py --stime=2009-03-12 --etime=2010-01-01 --label="San Francisco" --outprefix=sf --plot 9414290_gageheight.csv
117+
"""
118+
),
119+
description="""Script to filter a tide into its subtidal, diurnal and semidiurnal components (and residual noise""",
120+
)
121+
parser.add_argument(
122+
"--stime",
123+
default=None,
124+
required=False,
125+
help="Start time in ISO-like format 2009-03-12T00:00:00. Time part and 'T' are optional.",
126+
)
127+
parser.add_argument("--etime", default=None, required=False, help="End time.")
128+
parser.add_argument(
129+
"--plot",
130+
default=False,
131+
required=False,
132+
action="store_true",
133+
help="Plot time series in matplotlib",
134+
)
135+
parser.add_argument(
136+
"--label", default="Tide", required=False, help="Label for station in plots"
137+
)
138+
parser.add_argument(
139+
"--outprefix",
140+
default=None,
141+
help="Output file prefix (species name will be added). If omitted, the label will be used",
142+
)
143+
parser.add_argument("infile", help="Input file.")
144+
145+
return parser
146+
147+
148+
def main():
149+
parser = create_arg_parser()
150+
args = parser.parse_args()
151+
if args.stime:
152+
sdate = dtm.datetime(
153+
*list(map(int, re.split(r"[^\d]", args.stime)))
154+
) # convert start time string input to datetime
155+
else:
156+
sdate = None
157+
if args.etime:
158+
edate = dtm.datetime(
159+
*list(map(int, re.split(r"[^\d]", args.etime)))
160+
) # convert start time string input to datetime
161+
else:
162+
edate = None
163+
data_file = args.infile
164+
station_name = args.label
165+
outprefix = args.outprefix
166+
if not outprefix:
167+
outprefix = station_name.replace(" ", "_")
168+
do_plot = args.plot
169+
170+
ts = read_ts(data_file, None, None)
171+
astart = max(ts.start, sdate - days(5)) if sdate else ts.start
172+
aend = min(ts.end, edate + days(5)) if edate else ts.end
173+
ts_sub, ts_diurnal, ts_semi, ts_noise = separate_species(ts.window(astart, aend))
174+
175+
comps = [
176+
(ts_noise, "noise"),
177+
(ts_semi, "semi_over"),
178+
(ts_diurnal, "diurnal"),
179+
(ts_sub, "subtidal"),
180+
]
181+
182+
for comp in comps:
183+
ts_output = comp[0].window(sdate, edate)
184+
output_file = outprefix + "_" + comp[1] + ".th"
185+
write_th(output_file, ts_output)
186+
187+
if do_plot:
188+
plot_result(
189+
ts.window(sdate, edate),
190+
ts_semi.window(sdate, edate),
191+
ts_diurnal.window(sdate, edate),
192+
ts_sub.window(sdate, edate),
193+
station_name,
194+
)
195+
196+
197+
def run_example():
198+
"""This is the data for the example.
199+
Note that you want the data to
200+
be at least 4 days longer than the desired output
201+
"""
202+
start = dtm.datetime(2009, 2, 18)
203+
end = dtm.datetime(2010, 11, 24)
204+
205+
out_st = dtm.datetime(2009, 3, 12)
206+
out_end = dtm.datetime(2010, 11, 2)
207+
208+
sf_path = "../9415020_gageheight.csv"
209+
ts = read_ts(sf_path, start, end)
210+
211+
print("separating reys...")
212+
separate_species(ts, "rey", out_st, out_end, do_plot=True)
213+
214+
print("all done")
215+
216+
217+
if __name__ == "__main__":
218+
main()
219+
# run_example()

0 commit comments

Comments
 (0)