18
|
1 |
#!/usr/bin/perl
|
|
2 |
#======================================================================
|
|
3 |
# S B E _ W
|
|
4 |
# doc: Mon Nov 3 17:34:19 2014
|
|
5 |
# dlm: Fri Nov 7 15:52:27 2014
|
|
6 |
# (c) 2014 A.M. Thurnherr
|
|
7 |
# uE-Info: 22 0 NIL 0 0 72 2 2 4 NIL ofnI
|
|
8 |
#======================================================================
|
|
9 |
|
|
10 |
$antsSummary = 'pre-process SBE 9plus CTD data for LADCP_w';
|
|
11 |
$antsMinLibVersion = 6.0;
|
|
12 |
|
|
13 |
# HISTORY:
|
|
14 |
# Nov 3, 2014: - created
|
|
15 |
# Nov 4, 2014: - improved
|
|
16 |
# Nov 6, 2014: - BUG: sound speed was not calculated correctly
|
|
17 |
# - added -a
|
|
18 |
# - added conductivity & temperature editing
|
|
19 |
# Nov 7, 2014: - loosened outlier editing
|
|
20 |
# - added no-valid-data error message
|
|
21 |
# - modified binning criterion to allow any sampling
|
|
22 |
# frequency (not just divisors of 24)
|
|
23 |
|
|
24 |
($ANTS) = (`which ANTSlib` =~ m{^(.*)/[^/]*$});
|
|
25 |
|
|
26 |
require "$ANTS/ants.pl";
|
|
27 |
require "$ANTS/fft.pl";
|
|
28 |
require "$ANTS/libSBE.pl";
|
|
29 |
require "$ANTS/libEOS83.pl";
|
|
30 |
|
|
31 |
$antsParseHeader = 0; # usage
|
|
32 |
&antsUsage('al:rs:v:',1,
|
|
33 |
'[-v)erbosity <level[0]>]',
|
|
34 |
'[use -a)lternate sensor pair]',
|
|
35 |
'[-r)etain all data (no editing)]',
|
|
36 |
'[-s)ampling <rate[6Hz]>]',
|
|
37 |
'[-l)owpass w <cutoff[2s]>]',
|
|
38 |
'<SBE CNV file>');
|
|
39 |
|
|
40 |
&antsFloatOpt(\$opt_l,2); # defaults
|
|
41 |
&antsCardOpt(\$opt_s,6);
|
|
42 |
&antsCardOpt(\$opt_v,0);
|
|
43 |
|
|
44 |
$CNVfile = $ARGV[0]; # open CNV file
|
|
45 |
open(F,&antsFileArg());
|
|
46 |
&antsActivateOut(); # activate ANTS file
|
|
47 |
|
|
48 |
#----------------------------------------------------------------------
|
|
49 |
# Read Data
|
|
50 |
#----------------------------------------------------------------------
|
|
51 |
|
|
52 |
print(STDERR "Reading $CNVfile...") if ($opt_v);
|
|
53 |
|
|
54 |
($nfields,$nscans,$sampint,$badval,$ftype,$lat,$lon) = # decode SBE header
|
|
55 |
SBE_parseHeader(F,0,0); # SBE field names, no time check
|
|
56 |
|
|
57 |
croak("$CNVfile: unexpected sampling interval $sampint\n")
|
|
58 |
unless (abs($sampint-1/24) < 1e-5);
|
|
59 |
croak("$CNVfile: unknown latitude\n")
|
|
60 |
unless numberp($lat);
|
|
61 |
|
|
62 |
$pressF = fnr('prDM');
|
|
63 |
|
|
64 |
if ($opt_a) { # temp/cond alternate sensor pair
|
|
65 |
$tempF = fnr('t190C');
|
|
66 |
$condF = fnrNoErr('c1S/m');
|
|
67 |
if (defined($condF)) {
|
|
68 |
$condHistRes = 10; # 0.1 bins
|
|
69 |
} else {
|
|
70 |
$condF = fnr('c1mS/cm');
|
|
71 |
$condHistRes = 1; # 1.0 bins
|
|
72 |
}
|
|
73 |
} else { # primary sensor pair
|
|
74 |
$tempF = fnr('t090C');
|
|
75 |
$condF = fnrNoErr('c0S/m');
|
|
76 |
if (defined($condF)) {
|
|
77 |
$condHistRes = 10;
|
|
78 |
} else {
|
|
79 |
$condF = fnr('c0mS/cm');
|
|
80 |
$condHistRes = 1;
|
|
81 |
}
|
|
82 |
}
|
|
83 |
|
|
84 |
&antsInstallBufFull(0); # read entire CNV file
|
|
85 |
&SBEin(F,$ftype,$nfields,$nscans,$bad);
|
|
86 |
|
|
87 |
printf(STDERR "\n\t%d scans",$nscans) if ($opt_v > 1);
|
|
88 |
printf(STDERR "\n") if ($opt_v);
|
|
89 |
|
|
90 |
#----------------------------------------------------------------------
|
|
91 |
# Edit Data
|
|
92 |
# - pressure outliers & spikes
|
|
93 |
# - conductivity outliers & spikes
|
|
94 |
#----------------------------------------------------------------------
|
|
95 |
|
|
96 |
unless ($opt_r) {
|
|
97 |
print(STDERR "Editing Data...") if ($opt_v);
|
|
98 |
|
|
99 |
#----------------------------------------
|
|
100 |
# trim initial records with nan pressures
|
|
101 |
#----------------------------------------
|
|
102 |
my($trimmed) = 0; # trim leading nan pressures
|
|
103 |
shift(@ants_),$trimmed++
|
|
104 |
until numberp($ants_[0][$pressF]);
|
|
105 |
printf(STDERR "\n\t%d initial records with nan pressure and/or conductivity trimmed",$trimmed) if ($opt_v > 1);
|
|
106 |
my($lvp) = $ants_[0][$pressF];
|
|
107 |
|
|
108 |
#------------------------------------------------
|
|
109 |
# edit pressure outliers outside contiguous range
|
|
110 |
#------------------------------------------------
|
|
111 |
my($outliers) = 0; my($min,$max);
|
|
112 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
113 |
$phist[$ants_[$s][$pressF]+1000]++
|
|
114 |
if ($ants_[$s][$pressF]>=-100 && $ants_[$s][$pressF]<=6500);
|
|
115 |
}
|
|
116 |
for ($max=25; $phist[$max+1000]||$phist[$max+1001]; $max++) {} # outliers after 2 gaps
|
|
117 |
for ($min=$max; $phist[$min+1000]||$phist[$min+ 999]; $min--) {}
|
|
118 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
119 |
if ($ants_[$s][$pressF] > $max) { $outliers++; $ants_[$s][$pressF] = nan; }
|
|
120 |
if ($ants_[$s][$pressF] < $min) { $outliers++; $ants_[$s][$pressF] = nan; }
|
|
121 |
}
|
|
122 |
&antsAddParams("pressure_outliers",sprintf("%d",$outliers));
|
|
123 |
printf(STDERR "\n\tcontinuous pressure range: %d..%d dbar (%d outliers removed)",$min,$max,$outliers) if ($opt_v > 1);
|
|
124 |
|
|
125 |
#----------------------------------------------------
|
|
126 |
# edit conductivity outliers outside contiguous range
|
|
127 |
#----------------------------------------------------
|
|
128 |
$outliers = 0;
|
|
129 |
my($modeSamp)=0;
|
|
130 |
undef(@phist);
|
|
131 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
132 |
next unless ($ants_[$s][$condF] > 0);
|
|
133 |
my($b) = $ants_[$s][$condF]*$condHistRes; # 1/10 S/m histogram resolution (1 mS/cm)
|
|
134 |
$phist[$b]++;
|
|
135 |
next unless ($phist[$b] > $modeSamp);
|
|
136 |
$modeSamp = $phist[$b]; $modeBin = $b;
|
|
137 |
}
|
|
138 |
for ($max=$modeBin; $phist[$max]||$phist[$max+1]; $max++) {} # outliers after 2 gaps
|
|
139 |
for ($min=$modeBin; $phist[$min]||$phist[$min-1]; $min--) {}
|
|
140 |
$max /= $condHistRes; $min /= $condHistRes;
|
|
141 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
142 |
if ($ants_[$s][$condF] > $max) { $outliers++; $ants_[$s][$condF] = nan; }
|
|
143 |
if ($ants_[$s][$condF] < $min) { $outliers++; $ants_[$s][$condF] = nan; }
|
|
144 |
}
|
|
145 |
&antsAddParams("conductivity_outliers",sprintf("%d",$outliers));
|
|
146 |
printf(STDERR "\n\tcontinuous conductivity range: %.1f..%.1f S/m (%d outliers removed)",$min,$max,$outliers) if ($opt_v > 1);
|
|
147 |
|
|
148 |
#----------------------------------------------------
|
|
149 |
# edit temperature outliers outside contiguous range
|
|
150 |
#----------------------------------------------------
|
|
151 |
$outliers = 0;
|
|
152 |
my($modeSamp)=0;
|
|
153 |
undef(@phist);
|
|
154 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
155 |
next unless ($ants_[$s][$tempF] > 0);
|
|
156 |
my($b) = $ants_[$s][$tempF]*10; # 10th of a degree histogram resolution
|
|
157 |
$phist[$b]++;
|
|
158 |
next unless ($phist[$b] > $modeSamp);
|
|
159 |
$modeSamp = $phist[$b]; $modeBin = $b;
|
|
160 |
}
|
|
161 |
for ($max=$modeBin; $phist[$max]||$phist[$max+1]; $max++) {} # outliers after 2 gaps
|
|
162 |
for ($min=$modeBin; $phist[$min]||$phist[$min-1]; $min--) {}
|
|
163 |
$max /= 10; $min /= 10;
|
|
164 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
165 |
if ($ants_[$s][$tempF] > $max) { $outliers++; $ants_[$s][$tempF] = nan; }
|
|
166 |
if ($ants_[$s][$tempF] < $min) { $outliers++; $ants_[$s][$tempF] = nan; }
|
|
167 |
}
|
|
168 |
&antsAddParams("temperature_outliers",sprintf("%d",$outliers));
|
|
169 |
printf(STDERR "\n\tcontinuous temperature range: %.1f..%.1f degC (%d outliers removed)",$min,$max,$outliers) if ($opt_v > 1);
|
|
170 |
|
|
171 |
#----------------------------------------
|
|
172 |
# edit pressure spikes based on gradients
|
|
173 |
#----------------------------------------
|
|
174 |
|
|
175 |
for (my($s)=1; $s<$nscans; $s++) { # calculated pressure gradients (across gaps)
|
|
176 |
if (numberp($ants_[$s][$pressF])) {
|
|
177 |
$dp[$s-1] = $ants_[$s][$pressF] - $lvp;
|
|
178 |
$lvp = $ants_[$s][$pressF];
|
|
179 |
} else {
|
|
180 |
$dp[$s-1] = nan;
|
|
181 |
}
|
|
182 |
}
|
|
183 |
|
|
184 |
my($ns1,$ns2) = (0,0);
|
|
185 |
for (my($s)=0; $s<$nscans-2; $s++) { # consecutive large pressure gradients of opposite sign
|
|
186 |
if (($dp[$s]*$dp[$s+1] < 0) && # tests return false if either of the dps is not defined
|
|
187 |
(abs($dp[$s]) > 10) &&
|
|
188 |
(abs($dp[$s+1]) > 10)) {
|
|
189 |
$ants_[$s+1][$pressF] = nan;
|
|
190 |
$dp[$s] = $dp[$s+1] = undef;
|
|
191 |
$ns1++;
|
|
192 |
}
|
|
193 |
}
|
|
194 |
for (my($s)=0; $s<$nscans-3; $s++) { # 3 consecutive large pressure gradients of opposite sign
|
|
195 |
if (($dp[$s]>2 && $dp[$s+1]<-4 && $dp[$s+2]>2) ||
|
|
196 |
($dp[$s]<-2 && $dp[$s+1]>4 && $dp[$s+2]<-2)) {
|
|
197 |
$ants_[$s+1][$pressF] = $ants_[$s+2][$pressF] = nan;
|
|
198 |
$dp[$s] = $dp[$s+1] = $dp[$s+2] = undef;
|
|
199 |
$ns2+=2;
|
|
200 |
}
|
|
201 |
}
|
|
202 |
&antsAddParams("pressure_spikes_removed",sprintf("%d+%d",$ns1,$ns2));
|
|
203 |
printf(STDERR "\n\t%d+%d pressure spikes removed",$ns1,$ns2) if ($opt_v > 1);
|
|
204 |
|
|
205 |
printf(STDERR "\n") if ($opt_v);
|
|
206 |
|
|
207 |
} # if $opt_r
|
|
208 |
|
|
209 |
#----------------------------------------------------------------------
|
|
210 |
# Correcting for pressure bias
|
|
211 |
#----------------------------------------------------------------------
|
|
212 |
|
|
213 |
print(STDERR "Correcting for pressure bias...") if ($opt_v);
|
|
214 |
|
|
215 |
my($minP) = 9e99;
|
|
216 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
217 |
$minP = $ants_[$s][$pressF]
|
|
218 |
if numberp($ants_[$s][$pressF]) && ($ants_[$s][$pressF] < $minP);
|
|
219 |
}
|
|
220 |
croak("$CNVfile: no valid CTD pressure data below 25dbar\n")
|
|
221 |
unless ($minP < 9e99);
|
|
222 |
&antsAddParams('pressure_bias',$minP);
|
|
223 |
printf(STDERR "\n\tsubtracting %.1f dbar",$minP) if ($opt_v > 1);
|
|
224 |
for (my($s)=0; $s<$nscans; $s++) {
|
|
225 |
$ants_[$s][$pressF] -= $minP
|
|
226 |
if numberp($ants_[$s][$pressF]);
|
|
227 |
}
|
|
228 |
printf(STDERR "\n") if ($opt_v);
|
|
229 |
|
|
230 |
#----------------------------------------------------------------------
|
|
231 |
# Binning data
|
|
232 |
#----------------------------------------------------------------------
|
|
233 |
|
|
234 |
my($sps) = round(1 / $sampint / $opt_s);
|
|
235 |
print(STDERR "Creating ${opt_s}Hz time series ($sps samples per bin)...") if ($opt_v);
|
|
236 |
&antsAddParams('sampling_frequency',1/$opt_s);
|
|
237 |
&antsAddParams('sampling_rate',$opt_s);
|
|
238 |
|
|
239 |
my(@press,@temp,@cond);
|
|
240 |
my($sp,$np,$st,$nt,$sc,$nc);
|
|
241 |
|
|
242 |
$sp = $st = $sc = $np = $nt = $nc = 0;
|
|
243 |
for (my($rec)=1,my($s)=0; $s<$nscans; $s++) {
|
|
244 |
if ($s*$sampint > $rec/$opt_s) {
|
|
245 |
$rec++;
|
|
246 |
push(@press,$np>0?$sp/$np:nan);
|
|
247 |
push(@temp, $nt>0?$st/$nt:nan);
|
|
248 |
push(@cond, $nc>0?$sc/$nc:nan);
|
|
249 |
$sp = $st = $sc = $np = $nt = $nc = 0;
|
|
250 |
}
|
|
251 |
$sp+=$ants_[$s][$pressF],$np++ if numberp($ants_[$s][$pressF]);
|
|
252 |
$st+=$ants_[$s][$tempF],$nt++ if numberp($ants_[$s][$tempF]);
|
|
253 |
$sc+=$ants_[$s][$condF],$nc++ if numberp($ants_[$s][$condF]);
|
|
254 |
}
|
|
255 |
|
|
256 |
printf(STDERR "\n") if ($opt_v);
|
|
257 |
|
|
258 |
#----------------------------------------------------------------------
|
|
259 |
# Calculating derived quantities
|
|
260 |
#----------------------------------------------------------------------
|
|
261 |
|
|
262 |
print(STDERR "Calculating vertical package velocity & sound speed...") if ($opt_v);
|
|
263 |
|
|
264 |
for (my($r)=0; $r<@press; $r++) {
|
|
265 |
$elapsed[$r] = $r/$opt_s;
|
|
266 |
$depth[$r] = &depth($press[$r],$lat);
|
|
267 |
croak(sprintf("$CNVfile: unrealistic depth %d m at elapsed=%.1f s (r=$r)\n",
|
|
268 |
$depth[$r],$elapsed[$r]))
|
|
269 |
if numberp($depth[$r]) && ($depth[$r]<0 || $depth[$r]>6100);
|
|
270 |
$sspd[$r] = &sVel(&salin($cond[$r],$temp[$r],$press[$r]),$temp[$r],$press[$r]);
|
|
271 |
croak(sprintf("$CNVfile: unrealistic soundspeed %dm/s at elapsed=%.1fs & depth=%.1fm ($cond[$r],$temp[$r],$press[$r])\n",
|
|
272 |
$sspd[$r],$elapsed[$r],$depth[$r]))
|
|
273 |
if numberp($sspd[$r]) && ($sspd[$r]<1400 || $sspd[$r]>1600);
|
|
274 |
}
|
|
275 |
|
|
276 |
$w[0] = nan;
|
|
277 |
for (my($r)=1; $r<@depth-1; $r++) {
|
|
278 |
$w[$r] = numbersp($depth[$r-1],$depth[$r+1])
|
|
279 |
? ($depth[$r+1] - $depth[$r-1]) * $opt_s
|
|
280 |
: nan;
|
|
281 |
}
|
|
282 |
push(@w,nan);
|
|
283 |
|
|
284 |
printf(STDERR "\n") if ($opt_v);
|
|
285 |
|
|
286 |
#----------------------------------------------------------------------
|
|
287 |
# Low-pass filter velocity data
|
|
288 |
# - interpolate missing vertical velocities first
|
|
289 |
#----------------------------------------------------------------------
|
|
290 |
|
|
291 |
if ($opt_l > 0) {
|
|
292 |
print(STDERR "Low-pass filtering vertical package velocity...") if ($opt_v);
|
|
293 |
&antsAddParams('w_lowpass_cutoff',$opt_l);
|
|
294 |
|
|
295 |
my($trimmed) = 0;
|
|
296 |
shift(@w),shift(@depth),shift(@elapsed),shift(@sspd),$trimmed++
|
|
297 |
until numberp($w[0]);
|
|
298 |
my($interpolated) = 0;
|
|
299 |
for ($r=1; $r<@w; $r++) {
|
|
300 |
next if numberp($w[$r]);
|
|
301 |
my($lv) = $r-1;
|
|
302 |
for ($nv=$r+1; $nv<@depth && !numberp($w[$nv]); $nv++) {}
|
|
303 |
if ($nv < @depth) {
|
|
304 |
while ($r < $nv) {
|
|
305 |
$w[$r] = $w[$lv] + ($r-$lv)/($nv-$lv) * ($w[$nv]-$w[$lv]);
|
|
306 |
$interpolated++;
|
|
307 |
$r++;
|
|
308 |
}
|
|
309 |
|
|
310 |
} else {
|
|
311 |
$trimmed += @w-$r;
|
|
312 |
splice(@w,$r); splice(@depth,$r);
|
|
313 |
splice(@elapsed,$r); splice(@sspd,$r);
|
|
314 |
}
|
|
315 |
}
|
|
316 |
&antsAddParams('w_interpolated',$interpolated);
|
|
317 |
printf(STDERR "\n\t%d/%d vertical velocities trimmed/interpolated",$trimmed,$interpolated) if ($opt_v > 1);
|
|
318 |
|
|
319 |
|
|
320 |
#--------------------
|
|
321 |
# Zero Pad Data
|
|
322 |
#--------------------
|
|
323 |
|
|
324 |
for ($pot=1; $pot<@w; $pot<<=1) {} # determine power of two
|
|
325 |
|
|
326 |
for ($r=0; $r<@w; $r++) { # copy data
|
|
327 |
$fftbuf[2*$r] = $w[$r];
|
|
328 |
$fftbuf[2*$r+1] = 0;
|
|
329 |
}
|
|
330 |
printf(STDERR "\n\t%d zero records added",$pot-$r) if ($opt_v > 1);
|
|
331 |
while ($r < $pot) { # pad with zeroes
|
|
332 |
$fftbuf[2*$r] = $fftbuf[2*$r+1] = 0;
|
|
333 |
$r++;
|
|
334 |
}
|
|
335 |
|
|
336 |
#--------------------
|
|
337 |
# Low-Pass Filter
|
|
338 |
#--------------------
|
|
339 |
|
|
340 |
@fco = &FOUR1(-1,@fftbuf); # forward FFT
|
|
341 |
$n = @fco/2;
|
|
342 |
for (my($ip)=2; $ip<=$n; $ip+=2) { # +ve freq fco
|
|
343 |
my($in) = 2*$n-$ip; # -ve freq fco
|
|
344 |
my($f) = $ip/2/$n*$opt_s; # frequency
|
|
345 |
$fco[$ip] = $fco[$ip+1] = $fco[$in] = $fco[$in+1] = 0
|
|
346 |
if ($f > 1/$opt_l); # low-pass filter
|
|
347 |
}
|
|
348 |
@w_lp = &FOUR1(1,@fco); # inverse FFT
|
|
349 |
|
|
350 |
printf(STDERR "\n") if ($opt_v);
|
|
351 |
} else {
|
|
352 |
@w_lp = @w;
|
|
353 |
}
|
|
354 |
|
|
355 |
#----------------------------------------------------------------------
|
|
356 |
|
|
357 |
print(STDERR "Writing output...\n") if ($opt_v);
|
|
358 |
|
|
359 |
@antsNewLayout = ('elapsed','depth','sspd','w.raw','w');
|
|
360 |
for ($r=0; $r<@w; $r++) {
|
|
361 |
&antsOut($elapsed[$r],$depth[$r],$sspd[$r],$w[$r],$w_lp[2*$r]/@w_lp);
|
|
362 |
}
|
|
363 |
|
|
364 |
exit(0); # don't flush @ants_
|