PyMVP 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyMVP/__init__.py +1 -0
- PyMVP/main.py +1776 -0
- PyMVP/mvp_routines.py +996 -0
- pymvp-0.1.0.dist-info/METADATA +8 -0
- pymvp-0.1.0.dist-info/RECORD +7 -0
- pymvp-0.1.0.dist-info/WHEEL +5 -0
- pymvp-0.1.0.dist-info/top_level.txt +1 -0
PyMVP/mvp_routines.py
ADDED
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# Collection of routines for MVP treatment
|
|
5
|
+
#
|
|
6
|
+
#
|
|
7
|
+
# Function get_log: Read MPV log file to get starting and ending times of the cycle
|
|
8
|
+
#
|
|
9
|
+
#
|
|
10
|
+
# Function read_mvp_cycle: Read one MVP cycle from a mvp_2022XXX.m1 file
|
|
11
|
+
#
|
|
12
|
+
#
|
|
13
|
+
# Function time_mvp_cycle_up:
|
|
14
|
+
# Allocate time to each sample for a MVP cycle
|
|
15
|
+
# Select the ascending MVP cycle (less noise)
|
|
16
|
+
#
|
|
17
|
+
# Function time_mvp_cycle_down:
|
|
18
|
+
# Allocate time to each sample for a MVP cycle
|
|
19
|
+
# Select the descending MVP cycle
|
|
20
|
+
#
|
|
21
|
+
#
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
#
|
|
27
|
+
#
|
|
28
|
+
################################################################################
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Import libraries
|
|
32
|
+
#
|
|
33
|
+
|
|
34
|
+
import numpy as np
|
|
35
|
+
import scipy.stats as st
|
|
36
|
+
from datetime import date
|
|
37
|
+
from datetime import datetime
|
|
38
|
+
from scipy import interpolate
|
|
39
|
+
from scipy.signal import butter, freqz
|
|
40
|
+
from scipy import signal
|
|
41
|
+
import gsw
|
|
42
|
+
from scipy.interpolate import pchip_interpolate
|
|
43
|
+
import similaritymeasures
|
|
44
|
+
from netCDF4 import Dataset
|
|
45
|
+
from scipy.signal import butter, filtfilt, correlate, correlation_lags
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
################################################################################
|
|
49
|
+
#
|
|
50
|
+
# Function get_log: Read MPV log file to get starting and ending times of the cycle, latitude, longitude, and datetime
|
|
51
|
+
#
|
|
52
|
+
# input:
|
|
53
|
+
# mvp_log_name : ASCII MVP Log file (MVP_2022xxx.log)
|
|
54
|
+
# Yorig : time is counted in days since Yorig/1/1 (here 1950/1/1)
|
|
55
|
+
#
|
|
56
|
+
# output:
|
|
57
|
+
# mvp_tstart : Start of the dive in days since Yorig/1/1
|
|
58
|
+
# mvp_tend : End of the dive in days since Yorig/1/1
|
|
59
|
+
# cycle_dur : Duration of the cycle in seconds
|
|
60
|
+
# lat : Latitude (float, if available)
|
|
61
|
+
# lon : Longitude (float, if available)
|
|
62
|
+
# dt_station : Datetime object (datetime.datetime) of the station (if available)
|
|
63
|
+
#
|
|
64
|
+
################################################################################
|
|
65
|
+
|
|
66
|
+
def get_log(mvp_log_name,Yorig):
|
|
67
|
+
|
|
68
|
+
#print('Reading '+mvp_log_name)
|
|
69
|
+
|
|
70
|
+
# Get start time and end time of the cycle
|
|
71
|
+
flog = open(mvp_log_name, 'r', encoding = "ISO-8859-1")
|
|
72
|
+
|
|
73
|
+
# --- Read header and extract date, lat, lon, datetime ---
|
|
74
|
+
lat = None
|
|
75
|
+
lon = None
|
|
76
|
+
dt_station = None
|
|
77
|
+
mvptime = None
|
|
78
|
+
mvpdate = None
|
|
79
|
+
header_lines = []
|
|
80
|
+
# Read first 14 lines (header)
|
|
81
|
+
for i in range(14):
|
|
82
|
+
line = flog.readline()
|
|
83
|
+
header_lines.append(line)
|
|
84
|
+
# Parse LAT (line 9, index 8)
|
|
85
|
+
try:
|
|
86
|
+
lat_line = header_lines[8]
|
|
87
|
+
lat_str = lat_line.split(':')[1].strip().split(',')[0] # e.g. '4253.6113800'
|
|
88
|
+
lat_dir = lat_line.strip().split(',')[-1].replace(':','').strip() # e.g. 'N'
|
|
89
|
+
lat_deg = float(lat_str[:2])
|
|
90
|
+
lat_min = float(lat_str[2:])
|
|
91
|
+
lat = lat_deg + lat_min/60.0
|
|
92
|
+
if lat_dir.upper() == 'S':
|
|
93
|
+
lat = -lat
|
|
94
|
+
except Exception:
|
|
95
|
+
lat = None
|
|
96
|
+
# Parse LON (line 10, index 9)
|
|
97
|
+
try:
|
|
98
|
+
lon_line = header_lines[9]
|
|
99
|
+
lon_str = lon_line.split(':')[1].strip().split(',')[0] # e.g. '00614.5387900'
|
|
100
|
+
lon_dir = lon_line.strip().split(',')[-1].replace(':','').strip() # e.g. 'E'
|
|
101
|
+
lon_deg = float(lon_str[:3])
|
|
102
|
+
lon_min = float(lon_str[3:])
|
|
103
|
+
lon = lon_deg + lon_min/60.0
|
|
104
|
+
if lon_dir.upper() == 'W':
|
|
105
|
+
lon = -lon
|
|
106
|
+
except Exception:
|
|
107
|
+
lon = None
|
|
108
|
+
# Parse Time (line 12, index 11)
|
|
109
|
+
try:
|
|
110
|
+
time_line = header_lines[11]
|
|
111
|
+
time_str = time_line.split(':',1)[1].strip() # e.g. '10:28:58.6'
|
|
112
|
+
except Exception:
|
|
113
|
+
time_str = None
|
|
114
|
+
# Parse Date (line 13, index 12)
|
|
115
|
+
try:
|
|
116
|
+
date_line = header_lines[12]
|
|
117
|
+
date_str = date_line.split(':',1)[1].strip() # e.g. '09/08/2025'
|
|
118
|
+
mvpdate = datetime.strptime(date_str, "%d/%m/%Y").date()
|
|
119
|
+
mvptime = mvpdate.toordinal() - date.toordinal(date(Yorig, 1, 1))
|
|
120
|
+
except Exception:
|
|
121
|
+
mvpdate = None
|
|
122
|
+
mvptime = None
|
|
123
|
+
# Compose datetime object
|
|
124
|
+
try:
|
|
125
|
+
if time_str is not None and mvpdate is not None:
|
|
126
|
+
dt_station = datetime.strptime(date_str + ' ' + time_str, "%d/%m/%Y %H:%M:%S.%f")
|
|
127
|
+
except ValueError:
|
|
128
|
+
try:
|
|
129
|
+
dt_station = datetime.strptime(date_str + ' ' + time_str, "%d/%m/%Y %H:%M:%S")
|
|
130
|
+
except Exception:
|
|
131
|
+
dt_station = None
|
|
132
|
+
|
|
133
|
+
# Read 3 more lines (as before)
|
|
134
|
+
for i in range(3):
|
|
135
|
+
line = flog.readline()
|
|
136
|
+
|
|
137
|
+
# --- Read data for start/stop times ---
|
|
138
|
+
mvp_tstart = None
|
|
139
|
+
mvp_tend = None
|
|
140
|
+
hh1 = mn1 = sc1 = None
|
|
141
|
+
while True:
|
|
142
|
+
line = flog.readline()
|
|
143
|
+
if line == '':
|
|
144
|
+
break
|
|
145
|
+
words = line.split()
|
|
146
|
+
if len(words) < 3:
|
|
147
|
+
continue
|
|
148
|
+
if words[1] == 'EVENT:':
|
|
149
|
+
if words[2][:5] == 'START':
|
|
150
|
+
hh1 = float(words[2][6:8])
|
|
151
|
+
mn1 = float(words[2][9:11])
|
|
152
|
+
sc1 = float(words[2][12:16])
|
|
153
|
+
mvp_tstart = mvptime + (hh1 + (mn1 + sc1 / 60.) / 60.) / 24.
|
|
154
|
+
if words[2][:5] == 'STOP_':
|
|
155
|
+
hh2 = float(words[2][6:8])
|
|
156
|
+
mn2 = float(words[2][9:11])
|
|
157
|
+
sc2 = float(words[2][12:16])
|
|
158
|
+
if hh2 < hh1:
|
|
159
|
+
hh2 = hh2 + 24.
|
|
160
|
+
mvp_tend = mvptime + (hh2 + (mn2 + sc2 / 60.) / 60.) / 24.
|
|
161
|
+
|
|
162
|
+
cycle_dur = (mvp_tend - mvp_tstart) * (24 * 60 * 60)
|
|
163
|
+
|
|
164
|
+
flog.close()
|
|
165
|
+
|
|
166
|
+
return mvp_tstart, mvp_tend, cycle_dur, lat, lon, dt_station
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
################################################################################
|
|
175
|
+
#
|
|
176
|
+
# Function read_mvp_cycle_raw: Read one MVP cycle from a mvp_2022XXX.raw file
|
|
177
|
+
#
|
|
178
|
+
# input:
|
|
179
|
+
# mvp_dat_name : ASCII MVP .raw file (mvp_2022xxx.raw)
|
|
180
|
+
#
|
|
181
|
+
#
|
|
182
|
+
# output:
|
|
183
|
+
#
|
|
184
|
+
# pres : pressure [dbar]
|
|
185
|
+
# soundvel : Sound velocity [m/s]
|
|
186
|
+
# do : dissolved oxygen [umol/kg]
|
|
187
|
+
# temp2 : Temperature from DO sensor [oC]
|
|
188
|
+
# suna : SUNA data [umol/kg]
|
|
189
|
+
# fluo : Fluorometer data [ug/l]
|
|
190
|
+
# turb : Turbidity data [NTU]
|
|
191
|
+
# ph : pH data [pH units]
|
|
192
|
+
#
|
|
193
|
+
#
|
|
194
|
+
################################################################################
|
|
195
|
+
#
|
|
196
|
+
|
|
197
|
+
def read_mvp_cycle_raw(mvp_dat_name):
|
|
198
|
+
|
|
199
|
+
#print('Reading '+mvp_dat_name)
|
|
200
|
+
|
|
201
|
+
# Open the file (there was a problem with encoding)
|
|
202
|
+
fdat = open(mvp_dat_name, 'r', encoding = "ISO-8859-1")
|
|
203
|
+
|
|
204
|
+
# Préparer les listes pour chaque variable utile
|
|
205
|
+
pres = [] # Pressure
|
|
206
|
+
cond = [] # Conductivity
|
|
207
|
+
temp = [] # Temperature
|
|
208
|
+
soundvel = [] # Sound velocity
|
|
209
|
+
dox = [] # Dissolved oxygen
|
|
210
|
+
temp2 = [] # Temperature from DO sensor
|
|
211
|
+
suna = [] # SUNA data
|
|
212
|
+
fluo = [] # Fluorometer data
|
|
213
|
+
turb = [] # Turbidity data
|
|
214
|
+
ph = [] # pH data
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Sauter le header : lire jusqu'à la première ligne commençant par 'M'
|
|
218
|
+
while True:
|
|
219
|
+
pos = fdat.tell()
|
|
220
|
+
line = fdat.readline()
|
|
221
|
+
if line == '':
|
|
222
|
+
break
|
|
223
|
+
line = line.strip()
|
|
224
|
+
if len(line) == 0:
|
|
225
|
+
continue
|
|
226
|
+
if line[0] == 'M':
|
|
227
|
+
# On revient en arrière pour traiter cette ligne dans la boucle principale
|
|
228
|
+
fdat.seek(pos)
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
# Lecture des données à partir de la première ligne 'M'
|
|
232
|
+
while True:
|
|
233
|
+
line = fdat.readline()
|
|
234
|
+
if line == '':
|
|
235
|
+
break
|
|
236
|
+
line = line.strip()
|
|
237
|
+
if len(line) == 0:
|
|
238
|
+
continue
|
|
239
|
+
if line[0] == 'Z':
|
|
240
|
+
continue # ignorer les lignes Z
|
|
241
|
+
if line[0] == 'M':
|
|
242
|
+
words = line.split()
|
|
243
|
+
if len(words) < 11:
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
pres.append(float(words[1]))
|
|
247
|
+
soundvel.append(float(words[2]))
|
|
248
|
+
cond.append(float(words[3]))
|
|
249
|
+
temp.append(float(words[4]))
|
|
250
|
+
dox.append(float(words[5]))
|
|
251
|
+
temp2.append(float(words[6]))
|
|
252
|
+
suna.append(float(words[7]))
|
|
253
|
+
fluo.append(float(words[8]))
|
|
254
|
+
turb.append(float(words[9]))
|
|
255
|
+
ph.append(float(words[10]))
|
|
256
|
+
except Exception:
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
fdat.close()
|
|
260
|
+
|
|
261
|
+
# Convertir en numpy arrays
|
|
262
|
+
pres = np.array(pres)
|
|
263
|
+
soundvel = np.array(soundvel)
|
|
264
|
+
cond = np.array(cond)
|
|
265
|
+
temp = np.array(temp)
|
|
266
|
+
do = np.array(dox)
|
|
267
|
+
temp2 = np.array(temp2)
|
|
268
|
+
suna = np.array(suna)
|
|
269
|
+
fluo = np.array(fluo)
|
|
270
|
+
turb = np.array(turb)
|
|
271
|
+
ph = np.array(ph)
|
|
272
|
+
|
|
273
|
+
return pres, soundvel, cond, temp, do, temp2, suna, fluo, turb, ph
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
#
|
|
278
|
+
################################################################################
|
|
279
|
+
#
|
|
280
|
+
# Function read_mvp_cycle_ncdf: Read one MVP cycle from a mvp_2022XXX.ncdf file
|
|
281
|
+
#
|
|
282
|
+
# input:
|
|
283
|
+
# mvp_dat_name : NetCDF MVP .ncdf file (mvp_2022xxx.ncdf)
|
|
284
|
+
#
|
|
285
|
+
#
|
|
286
|
+
# output:
|
|
287
|
+
#
|
|
288
|
+
# pres : pressure [dbar]
|
|
289
|
+
# soundvel : Sound velocity [m/s]
|
|
290
|
+
# do : dissolved oxygen [umol/kg]
|
|
291
|
+
# temp2 : Temperature from DO sensor [oC]
|
|
292
|
+
# suna : SUNA data [umol/kg]
|
|
293
|
+
# fluo : Fluorometer data [ug/l]
|
|
294
|
+
# turb : Turbidity data [NTU]
|
|
295
|
+
# ph : pH data [pH units]
|
|
296
|
+
#
|
|
297
|
+
#
|
|
298
|
+
################################################################################
|
|
299
|
+
#
|
|
300
|
+
|
|
301
|
+
def read_mvp_cycle_ncdf(mvp_dat_name):
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
#print('Reading '+mvp_dat_name)
|
|
305
|
+
|
|
306
|
+
# Open the file
|
|
307
|
+
nc = Dataset(mvp_dat_name, 'r')
|
|
308
|
+
|
|
309
|
+
# Read variables
|
|
310
|
+
pres = nc.variables['PRES'][:]
|
|
311
|
+
soundvel = nc.variables['SOUNDVEL'][:]
|
|
312
|
+
cond = nc.variables['COND'][:]
|
|
313
|
+
temp = nc.variables['TEMP'][:]
|
|
314
|
+
do = nc.variables['DO'][:]
|
|
315
|
+
temp2 = nc.variables['TEMP2'][:]
|
|
316
|
+
suna = nc.variables['SUNA'][:] if 'SUNA' in nc.variables else None
|
|
317
|
+
fluo = nc.variables['FLUO'][:] if 'FLUO' in nc.variables else None
|
|
318
|
+
turb = nc.variables['TURB'][:] if 'TURB' in nc.variables else None
|
|
319
|
+
ph = nc.variables['PH'][:] if 'PH' in nc.variables else None
|
|
320
|
+
|
|
321
|
+
nc.close()
|
|
322
|
+
|
|
323
|
+
return pres, soundvel, cond, temp, do, temp2, suna, fluo, turb, ph
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ################################################################################
|
|
335
|
+
#
|
|
336
|
+
# Function time_mvp_cycle_up_bgc:
|
|
337
|
+
# Allocate time to each sample for a MVP cycle (BGC version)
|
|
338
|
+
# Select the ascending MVP cycle (less noise)
|
|
339
|
+
#
|
|
340
|
+
# input:
|
|
341
|
+
# pres : pressure [dbar]
|
|
342
|
+
# soundvel : sound velocity [m/s]
|
|
343
|
+
# do : dissolved oxygen [umol/kg]
|
|
344
|
+
# temp2 : temperature from DO sensor [°C]
|
|
345
|
+
# suna : SUNA data [umol/kg]
|
|
346
|
+
# fluo : fluorometer data [ug/l]
|
|
347
|
+
# turb : turbidity data [NTU]
|
|
348
|
+
# ph : pH data [pH units]
|
|
349
|
+
# mvp_tstart: start of the dive in days since Yorig/1/1
|
|
350
|
+
# mvp_tend : end of the dive in days since Yorig/1/1
|
|
351
|
+
#
|
|
352
|
+
# output:
|
|
353
|
+
# pres_up, soundvel_up, do_up, temp2_up, suna_up, fluo_up, turb_up, ph_up
|
|
354
|
+
# (données ascendantes pour chaque variable)
|
|
355
|
+
# time_cycle : Time of each sample in days since Yorig/1/1
|
|
356
|
+
#
|
|
357
|
+
# ################################################################################
|
|
358
|
+
#
|
|
359
|
+
|
|
360
|
+
def time_mvp_cycle_up(args,mvp_tstart,mvp_tend):
|
|
361
|
+
|
|
362
|
+
# Allocate time to each data point
|
|
363
|
+
N = np.size(args[0])
|
|
364
|
+
time_cycle = np.linspace(mvp_tstart, mvp_tend, N)
|
|
365
|
+
|
|
366
|
+
# Get only the ascending lines
|
|
367
|
+
ibot = np.min(np.where(args[0] == args[0].max()))
|
|
368
|
+
for i, arg in enumerate(args):
|
|
369
|
+
args[i] = arg[ibot:]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
time_cycle_up = time_cycle[ibot:]
|
|
373
|
+
|
|
374
|
+
return args + [time_cycle_up]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ################################################################################
|
|
378
|
+
#
|
|
379
|
+
# Function time_mvp_cycle_down_bgc:
|
|
380
|
+
# Allocate time to each sample for a MVP cycle (BGC version)
|
|
381
|
+
# Select the descending MVP cycle
|
|
382
|
+
#
|
|
383
|
+
# input:
|
|
384
|
+
# pres : pressure [dbar]
|
|
385
|
+
# soundvel : sound velocity [m/s]
|
|
386
|
+
# do : dissolved oxygen [umol/kg]
|
|
387
|
+
# temp2 : temperature from DO sensor [°C]
|
|
388
|
+
# suna : SUNA data [umol/kg]
|
|
389
|
+
# fluo : fluorometer data [ug/l]
|
|
390
|
+
# turb : turbidity data [NTU]
|
|
391
|
+
# ph : pH data [pH units]
|
|
392
|
+
# mvp_tstart: start of the dive in days since Yorig/1/1
|
|
393
|
+
# mvp_tend : end of the dive in days since Yorig/1/1
|
|
394
|
+
#
|
|
395
|
+
# output:
|
|
396
|
+
# pres_down, soundvel_down, do_down, temp2_down, suna_down, fluo_down, turb_down, ph_down
|
|
397
|
+
# (données descendantes pour chaque variable)
|
|
398
|
+
# time_cycle_down: Time of each sample in days since Yorig/1/1
|
|
399
|
+
#
|
|
400
|
+
# ################################################################################
|
|
401
|
+
def time_mvp_cycle_down(args, mvp_tstart, mvp_tend):
|
|
402
|
+
|
|
403
|
+
N = np.size(args[0])
|
|
404
|
+
time_cycle = np.linspace(mvp_tstart, mvp_tend, N)
|
|
405
|
+
|
|
406
|
+
# Trouver les indices pour la partie descendante
|
|
407
|
+
ibot = np.min(np.where(args[0] == args[0].max()))
|
|
408
|
+
|
|
409
|
+
for i, arg in enumerate(args):
|
|
410
|
+
args[i] = arg[:ibot]
|
|
411
|
+
|
|
412
|
+
time_cycle_down = time_cycle[:ibot]
|
|
413
|
+
|
|
414
|
+
return args + [time_cycle_down]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
# ################################################################################
|
|
419
|
+
#
|
|
420
|
+
# Function raw_data_conversion:
|
|
421
|
+
# Converts raw BGC sensor data (in V or mV) to physical units.
|
|
422
|
+
# Not very efficien because of np.vectorize, if too long, recode the conversion functions to work with numpy arrays directly.
|
|
423
|
+
#
|
|
424
|
+
# input:
|
|
425
|
+
# pres, soundvel : already in physical units
|
|
426
|
+
# do_raw, temp2_raw, suna_raw, fluo_raw, turb_raw, ph_raw : raw sensor data to convert
|
|
427
|
+
#
|
|
428
|
+
# output:
|
|
429
|
+
# pres, soundvel, do, temp2, suna, fluo, turb, ph : all variables in physical units (currently identical to input)
|
|
430
|
+
#
|
|
431
|
+
# ################################################################################
|
|
432
|
+
def raw_data_conversion(pres, soundvel, cond, temp, do_raw, temp2_raw, suna_raw, fluo_raw, turb_raw, ph_raw):
|
|
433
|
+
"""
|
|
434
|
+
Converts raw BGC sensor data (in V or mV) to physical units.
|
|
435
|
+
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
temp2 = np.vectorize(TEMP2_conversion)(temp2_raw)
|
|
439
|
+
do = np.vectorize(DO_conversion)(do_raw, temp, pres)
|
|
440
|
+
suna = np.vectorize(SUNA_conversion)(suna_raw)
|
|
441
|
+
fluo = np.vectorize(FLUO_conversion)(fluo_raw)
|
|
442
|
+
turb = np.vectorize(TURBIDITY_conversion)(turb_raw)
|
|
443
|
+
ph = np.vectorize(PH_conversion)(ph_raw, temp)
|
|
444
|
+
return pres, soundvel, cond, temp, do, temp2, suna, fluo, turb, ph
|
|
445
|
+
|
|
446
|
+
def TEMP2_conversion(temp_raw2):
|
|
447
|
+
"""
|
|
448
|
+
Converts raw temperature data from the DO sensor to physical units.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
A = -1.191875e1
|
|
452
|
+
B = 2.145289e1
|
|
453
|
+
C = -3.611291
|
|
454
|
+
D = 6.788267e-1
|
|
455
|
+
|
|
456
|
+
# Assuming temp_raw2 is in V
|
|
457
|
+
temp2 = A + B * temp_raw2 + C * temp_raw2**2 + D * temp_raw2**3
|
|
458
|
+
|
|
459
|
+
return temp2
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def DO_conversion(do_raw, temp, pres):
|
|
463
|
+
"""
|
|
464
|
+
Converts raw dissolved oxygen data to physical units.
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
A = -4.329108e1
|
|
469
|
+
B = 1.326965e2
|
|
470
|
+
C = -3.459148e-1
|
|
471
|
+
D = 1.011300e-2
|
|
472
|
+
E = 3.9e-3
|
|
473
|
+
F = 4.03e-5
|
|
474
|
+
G = 0
|
|
475
|
+
H = 1
|
|
476
|
+
|
|
477
|
+
P_ = A/(1+D*(temp-25)+F*(temp-25)**2) + B/(do_raw*(1+D*(temp-25)+F*(temp-25)**2)+C)
|
|
478
|
+
|
|
479
|
+
do = G + H*P_
|
|
480
|
+
do = do*(1+E*(pres+10.1325)/100) # Apply pressure correction in MPa (air+water column)
|
|
481
|
+
|
|
482
|
+
return do
|
|
483
|
+
|
|
484
|
+
def SUNA_conversion(suna_raw):
|
|
485
|
+
"""
|
|
486
|
+
Converts raw SUNA data to physical units.
|
|
487
|
+
"""
|
|
488
|
+
# Not sur at all about these coefficients TO CHECK
|
|
489
|
+
Vmax = 1.8621
|
|
490
|
+
Vmin = 0.3666
|
|
491
|
+
DACmax = 39.56
|
|
492
|
+
DACmin = -0.2919
|
|
493
|
+
|
|
494
|
+
A1 = (DACmax - DACmin)/(Vmax-Vmin)
|
|
495
|
+
A0 = DACmin - A1 * Vmin
|
|
496
|
+
|
|
497
|
+
# Assuming suna_raw is in mV
|
|
498
|
+
# suna is Cnitrate in umol/L
|
|
499
|
+
suna = A0 + A1 * suna_raw*1e-3
|
|
500
|
+
|
|
501
|
+
suna = suna_raw*1e-3
|
|
502
|
+
return suna
|
|
503
|
+
|
|
504
|
+
def TURBIDITY_conversion(turb_raw):
|
|
505
|
+
|
|
506
|
+
"""
|
|
507
|
+
Converts raw turbidity data to physical units.
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
# for chlorophyll concentration
|
|
511
|
+
# Scale_factor =6
|
|
512
|
+
# Darkcounts = 0.091
|
|
513
|
+
|
|
514
|
+
# for turbidity in NTU
|
|
515
|
+
Scale_factor = 2
|
|
516
|
+
Dark_counts = 0.098
|
|
517
|
+
|
|
518
|
+
# Assuming turb_raw is in mV
|
|
519
|
+
turb = Scale_factor * (turb_raw*1e-3 - Dark_counts)
|
|
520
|
+
|
|
521
|
+
return turb
|
|
522
|
+
|
|
523
|
+
def FLUO_conversion(fluo_raw):
|
|
524
|
+
"""
|
|
525
|
+
Converts raw fluorometer data to physical units.
|
|
526
|
+
"""
|
|
527
|
+
|
|
528
|
+
# for chlorophyll concentration in ug/l
|
|
529
|
+
Scale_factor = 6
|
|
530
|
+
Dark_counts = 0.091
|
|
531
|
+
fluo = Scale_factor * (fluo_raw*1e-3 - Dark_counts)
|
|
532
|
+
|
|
533
|
+
return fluo
|
|
534
|
+
|
|
535
|
+
def PH_conversion(ph_raw, temp):
|
|
536
|
+
"""
|
|
537
|
+
Converts raw pH data to physical units.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
pHslope = 4.6630
|
|
541
|
+
pHoffset = 2.5330
|
|
542
|
+
# Assuming ph_raw is in m
|
|
543
|
+
pH = 7.0 + (ph_raw*1e-3 - pHoffset) / (pHslope * (temp+273.15) * 1.98416e-4)
|
|
544
|
+
|
|
545
|
+
return pH
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
#
|
|
549
|
+
################################################################################
|
|
550
|
+
#
|
|
551
|
+
#
|
|
552
|
+
#
|
|
553
|
+
# Function remove surface waves : apply a butterworth filter to the profiles
|
|
554
|
+
# to remove the surface waves, set here to 0.2 Hz
|
|
555
|
+
#
|
|
556
|
+
# input:
|
|
557
|
+
# data : variable to filter
|
|
558
|
+
# TIME : time is counted in days since Yorig/1/1 (here 1950/1/1)
|
|
559
|
+
#
|
|
560
|
+
# output:
|
|
561
|
+
# data_final : filtered data
|
|
562
|
+
# fluo_chla : fluorometer intensity
|
|
563
|
+
#
|
|
564
|
+
################################################################################
|
|
565
|
+
#
|
|
566
|
+
|
|
567
|
+
def remove_surface_waves(data,TIME,f_s,f_c,order):
|
|
568
|
+
data_final = np.zeros((data.shape[0],data.shape[1]))
|
|
569
|
+
data_final[:] = np.nan
|
|
570
|
+
|
|
571
|
+
for i_profile in range(data.shape[0]):
|
|
572
|
+
if np.isnan(np.nanmean(TIME[i_profile,:]))==0:
|
|
573
|
+
time_sampling=np.arange(np.nanmin(TIME[i_profile,:]),np.nanmax(TIME[i_profile,:]),1/f_s)
|
|
574
|
+
time_sampling = time_sampling[np.where(time_sampling<=np.nanmax(TIME[i_profile,:]))[0]]
|
|
575
|
+
|
|
576
|
+
ind = np.where((np.isnan(TIME[i_profile,:])==0) & (np.isnan(data[i_profile,:])==0))[0]
|
|
577
|
+
if len(ind)>6:
|
|
578
|
+
f1 = interpolate.interp1d(TIME[i_profile,ind], data[i_profile,ind],'linear',fill_value="extrapolate")
|
|
579
|
+
W_sampling = f1(time_sampling)
|
|
580
|
+
|
|
581
|
+
# Fourier transform
|
|
582
|
+
#f_s = 10*f_s
|
|
583
|
+
#f_s = 25*10
|
|
584
|
+
#f_c = 1 # Cut-off frequency in Hz
|
|
585
|
+
#order = 10 # Order of the butterworth filter
|
|
586
|
+
|
|
587
|
+
omega_c = f_c # Cut-off angular frequency
|
|
588
|
+
omega_c_d = omega_c / (f_s) # Normalized cut-off frequency (digital)
|
|
589
|
+
|
|
590
|
+
# Design the digital Butterworth filter
|
|
591
|
+
b, a = butter(order, omega_c_d)
|
|
592
|
+
|
|
593
|
+
W_lisse = signal.filtfilt(b, a, signal.detrend(W_sampling))
|
|
594
|
+
|
|
595
|
+
W_lisse = W_lisse + W_sampling - signal.detrend(W_sampling)
|
|
596
|
+
ind = np.where(TIME[i_profile,:]<=np.nanmax(time_sampling))[0]
|
|
597
|
+
f2 = interpolate.interp1d(time_sampling, W_lisse,'linear',fill_value="extrapolate")
|
|
598
|
+
data_final[i_profile,ind] = f2(TIME[i_profile,ind])
|
|
599
|
+
del ind, time_sampling, b, a, f1, f2, W_lisse, W_sampling, omega_c, omega_c_d
|
|
600
|
+
|
|
601
|
+
return data_final
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
#
|
|
605
|
+
################################################################################
|
|
606
|
+
#
|
|
607
|
+
#
|
|
608
|
+
#
|
|
609
|
+
# Correct thermistor viscous heating
|
|
610
|
+
#
|
|
611
|
+
# input:
|
|
612
|
+
# TEMP0 : In-situ Temperature
|
|
613
|
+
# SAL_PRA0 : Practical Salinity
|
|
614
|
+
# PRES0 : Pressure
|
|
615
|
+
# LON0 : Longitude
|
|
616
|
+
# LAT0 : Latitude
|
|
617
|
+
# TIME : time is counted in days since Yorig/1/1 (here 1950/1/1)
|
|
618
|
+
#
|
|
619
|
+
# output:
|
|
620
|
+
# TEMP1 : Corrected temperature
|
|
621
|
+
#
|
|
622
|
+
################################################################################
|
|
623
|
+
#
|
|
624
|
+
|
|
625
|
+
def viscous_heating(TEMP0, SAL_PRA0, PRES0, LON0, LAT0, TIME):
|
|
626
|
+
TEMP1 = np.zeros((TEMP0.shape[0], TEMP0.shape[1]))
|
|
627
|
+
TEMP1[:] = np.nan
|
|
628
|
+
for i in range(TEMP0.shape[0]):
|
|
629
|
+
|
|
630
|
+
T = np.zeros(TEMP0.shape[1])
|
|
631
|
+
S = np.zeros(TEMP0.shape[1])
|
|
632
|
+
P = np.zeros(PRES0.shape[1])
|
|
633
|
+
S = gsw.SA_from_SP(SAL_PRA0[i,:], PRES0[i,:], LON0[i,:], LAT0[i,:])
|
|
634
|
+
T = TEMP0[i,:]
|
|
635
|
+
|
|
636
|
+
T = T + 273.15;
|
|
637
|
+
|
|
638
|
+
a = [-5.8002206e+03, 1.3914993e00, -4.8640239e-02, 4.1764768e-05, -1.4452093e-08, 6.5459673e+00]
|
|
639
|
+
Pv_w = np.exp((a[0]/T) + a[1] + a[2]*T + a[3]*T**2 + a[4]*T**3 + a[5]*np.log(T))
|
|
640
|
+
|
|
641
|
+
b = [-4.5818e-4,-2.0443e-6]
|
|
642
|
+
P0 = Pv_w*np.exp(b[0]*S+b[1]*S**2)/1e6
|
|
643
|
+
|
|
644
|
+
T = TEMP0[i,:]
|
|
645
|
+
P0[np.where(T<100)[0]] = 0.101325
|
|
646
|
+
T68 = 1.00024*(T+273.15)
|
|
647
|
+
|
|
648
|
+
S = SAL_PRA0[i,:]
|
|
649
|
+
S_gkg=S
|
|
650
|
+
|
|
651
|
+
P = PRES0[i,:]
|
|
652
|
+
|
|
653
|
+
A = 5.328 - 9.76e-2*S + 4.04e-4*S**2
|
|
654
|
+
B = -6.913e-3 + 7.351e-4*S - 3.15e-6*S**2
|
|
655
|
+
C = 9.6e-6 - 1.927e-6*S + 8.23e-9*S**2
|
|
656
|
+
D = 2.5e-9 + 1.666e-9*S - 7.125e-12*S**2
|
|
657
|
+
cp_sw_P0 = 1000*(A + B*T68 + C*(T68**2) + D*(T68**3))
|
|
658
|
+
|
|
659
|
+
c1 = -3.1118
|
|
660
|
+
c2 = 0.0157
|
|
661
|
+
c3 = 5.1014e-5
|
|
662
|
+
c4 = -1.0302e-6
|
|
663
|
+
c5 = 0.0107
|
|
664
|
+
c6 = -3.9716e-5
|
|
665
|
+
c7 = 3.2088e-8
|
|
666
|
+
c8 = 1.0119e-9
|
|
667
|
+
|
|
668
|
+
cp_sw_P = (P - P0)*(c1 + c2*T + c3*(T**2) + c4*(T**3) + S_gkg*(c5 + c6*T + c7*(T**2) + c8*(T**3)))
|
|
669
|
+
|
|
670
|
+
cp = cp_sw_P0 + cp_sw_P
|
|
671
|
+
del cp_sw_P0, cp_sw_P, P, P0, c1, c2, c3, c4, c5, c6, c7, c8, T, S, T68, S_gkg
|
|
672
|
+
|
|
673
|
+
T = np.zeros(TEMP0.shape[1])
|
|
674
|
+
S = np.zeros(TEMP0.shape[1])
|
|
675
|
+
S = SAL_PRA0[i,:]
|
|
676
|
+
T = TEMP0[i,:]
|
|
677
|
+
S = S/1000;
|
|
678
|
+
|
|
679
|
+
a = [1.5700386464e-01, 6.4992620050e01, -9.1296496657e+01, 4.2844324477e-05, 1.5409136040e+00, 1.9981117208e-02, -9.5203865864e-05, 7.9739318223e+00, -7.5614568881e-02, 4.7237011074e-04]
|
|
680
|
+
|
|
681
|
+
mu_w = a[3] + 1/(a[0]*(T+a[1])**2+a[2])
|
|
682
|
+
|
|
683
|
+
A = a[4] + a[5]*T + a[6]*T**2
|
|
684
|
+
B = a[7] + a[8]*T + a[9]*T**2
|
|
685
|
+
mu = mu_w*(1 + A*S + B*S**2)
|
|
686
|
+
del mu_w, A, S, B, T, a
|
|
687
|
+
|
|
688
|
+
T = np.zeros(TEMP0.shape[1])
|
|
689
|
+
S = np.zeros(TEMP0.shape[1])
|
|
690
|
+
S = SAL_PRA0[i,:]
|
|
691
|
+
T = TEMP0[i,:]
|
|
692
|
+
T68 = 1.00024*T
|
|
693
|
+
S = S / 1.00472
|
|
694
|
+
k = 10**(np.log10(240+0.0002*S)+0.434*(2.3-(343.5+0.037*S)/(T+273.15))*(1-(T+273.15)/(647.3+0.03*S))**(1/3)-3)
|
|
695
|
+
del T, S, T68
|
|
696
|
+
|
|
697
|
+
W = np.zeros(TEMP0.shape[1])
|
|
698
|
+
W[1:-1] = (PRES0[i,2::]-PRES0[i,0:-2])/(TIME[i,2::]*24*3600-TIME[i,0:-2]*24*3600)
|
|
699
|
+
Pr = cp*mu/k
|
|
700
|
+
|
|
701
|
+
dT=0.80e-4*(W**2)*(Pr**(1/2))
|
|
702
|
+
TEMP1[i,:] = TEMP0[i,:] - dT
|
|
703
|
+
del dT, Pr, cp, mu, k, W
|
|
704
|
+
return(TEMP1)
|
|
705
|
+
|
|
706
|
+
#
|
|
707
|
+
################################################################################
|
|
708
|
+
#
|
|
709
|
+
# Function vertical_interp: Interpolate each profile on a required variable
|
|
710
|
+
#
|
|
711
|
+
# input:
|
|
712
|
+
# Depth_mat : original variable acquisition
|
|
713
|
+
# Mat : field to be interpolated
|
|
714
|
+
# Depth_interp : variable on which the field is interpolated (regularly sampled)
|
|
715
|
+
#
|
|
716
|
+
# output:
|
|
717
|
+
#
|
|
718
|
+
# Mat_Z_interp : interpolated field
|
|
719
|
+
#
|
|
720
|
+
#
|
|
721
|
+
################################################################################
|
|
722
|
+
#
|
|
723
|
+
|
|
724
|
+
def vertical_interp(Depth_mat,Mat,Depth_interp):
|
|
725
|
+
|
|
726
|
+
Mat_Z_interp = np.zeros((Mat.shape[0],len(Depth_interp)))
|
|
727
|
+
Mat_Z_interp[:] = np.nan
|
|
728
|
+
|
|
729
|
+
for i in range(Mat_Z_interp.shape[0]):
|
|
730
|
+
Depth_temp, ind = np.unique(Depth_mat[i,:],return_index=True)
|
|
731
|
+
Mat_temp = Mat[i,ind]
|
|
732
|
+
del ind
|
|
733
|
+
Mat_temp = Mat_temp[np.where(np.isnan(Depth_temp)==0)[0]]
|
|
734
|
+
Depth_temp = Depth_temp[np.where(np.isnan(Depth_temp)==0)[0]]
|
|
735
|
+
Depth_temp = Depth_temp[np.where(np.isnan(Mat_temp)==0)[0]]
|
|
736
|
+
Mat_temp = Mat_temp[np.where(np.isnan(Mat_temp)==0)[0]]
|
|
737
|
+
if (len(Mat_temp)>2) & (len(Depth_temp)>2):
|
|
738
|
+
ind = np.arange(np.where(Depth_interp>=np.nanmin(Depth_temp))[0][0], np.where(Depth_interp<=np.nanmax(Depth_temp))[0][-1])
|
|
739
|
+
f1 = interpolate.interp1d(Depth_temp, Mat_temp,'linear')
|
|
740
|
+
Mat_Z_interp[i,ind] = f1(Depth_interp[ind])
|
|
741
|
+
#Mat_Z_interp[i,ind] = pchip_interpolate(Depth_temp, Mat_temp, Depth_interp[ind])
|
|
742
|
+
del ind
|
|
743
|
+
del Depth_temp, Mat_temp
|
|
744
|
+
del i
|
|
745
|
+
return Mat_Z_interp
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
#
|
|
749
|
+
################################################################################
|
|
750
|
+
#
|
|
751
|
+
#
|
|
752
|
+
#
|
|
753
|
+
# Function mvp_bin(pres,salt,temp,time_cycle,Pbin,bin_method)
|
|
754
|
+
#
|
|
755
|
+
# Do a binning of MVP data on regulat pressure levels
|
|
756
|
+
#
|
|
757
|
+
# input:
|
|
758
|
+
# pres : pressure [dbar]
|
|
759
|
+
# fluo : fluo data [mg m-3]
|
|
760
|
+
# salt : Salinity [PSS-78]
|
|
761
|
+
# temp : Temperature [oC]
|
|
762
|
+
# time_cycle : Time of each sample in days since Yorig/1/1
|
|
763
|
+
# Pbin : pressure bins [dbar]
|
|
764
|
+
# bin_method : binning method ('mean', 'median',...)
|
|
765
|
+
# add_fluo : Flag to process fluorometer
|
|
766
|
+
#
|
|
767
|
+
# output:
|
|
768
|
+
# pres : pressure [dbar]
|
|
769
|
+
# fluo : fluo data [mg m-3]
|
|
770
|
+
# salt : Salinity [PSS-78]
|
|
771
|
+
# temp : Temperature [oC]
|
|
772
|
+
# time_cycle : Time in days since Yorig/1/1
|
|
773
|
+
#
|
|
774
|
+
#
|
|
775
|
+
################################################################################
|
|
776
|
+
#
|
|
777
|
+
|
|
778
|
+
def median(Depth_mat,Mat,Depth_interp):
|
|
779
|
+
|
|
780
|
+
Mat_Z_interp = np.zeros((Mat.shape[0],len(Depth_interp)))
|
|
781
|
+
Mat_Z_interp[:] = np.nan
|
|
782
|
+
|
|
783
|
+
for i in range(Mat_Z_interp.shape[0]):
|
|
784
|
+
Depth_temp, ind = np.unique(Depth_mat[i,:],return_index=True)
|
|
785
|
+
Mat_temp = Mat[i,ind]
|
|
786
|
+
del ind
|
|
787
|
+
ind = np.argsort(Depth_temp, axis=0)
|
|
788
|
+
Mat_temp=np.take_along_axis(Mat_temp, ind, axis=0)
|
|
789
|
+
Depth_temp=np.take_along_axis(Depth_temp, ind, axis=0)
|
|
790
|
+
del ind
|
|
791
|
+
Mat_temp = Mat_temp[np.where(np.isnan(Depth_temp)==0)[0]]
|
|
792
|
+
Depth_temp = Depth_temp[np.where(np.isnan(Depth_temp)==0)[0]]
|
|
793
|
+
Depth_temp = Depth_temp[np.where(np.isnan(Mat_temp)==0)[0]]
|
|
794
|
+
Mat_temp = Mat_temp[np.where(np.isnan(Mat_temp)==0)[0]]
|
|
795
|
+
if (len(Mat_temp)>2) & (len(np.where(Depth_interp>=np.nanmin(Depth_temp))[0])>2) & (len(np.where(Depth_interp<=np.nanmax(Depth_temp))[0])>2):
|
|
796
|
+
ind = np.arange(np.where(Depth_interp>=np.nanmin(Depth_temp))[0][0], np.where(Depth_interp<=np.nanmax(Depth_temp))[0][-1])
|
|
797
|
+
if len(ind)>0:
|
|
798
|
+
vout = st.binned_statistic(Depth_temp,Mat_temp,statistic='median', bins=Depth_interp[ind])
|
|
799
|
+
Mat_Z_interp[i,ind[0:-1]] = vout.statistic
|
|
800
|
+
del ind, vout
|
|
801
|
+
del Depth_temp, Mat_temp
|
|
802
|
+
del i
|
|
803
|
+
return Mat_Z_interp
|
|
804
|
+
|
|
805
|
+
def Calc_dist_time(TIME1, LON1, LAT1, TIME2, LON2, LAT2):
|
|
806
|
+
Dist = np.zeros((len(TIME1), len(TIME2)))
|
|
807
|
+
Time = np.zeros((len(TIME1), len(TIME2)))
|
|
808
|
+
R = 6373.0
|
|
809
|
+
|
|
810
|
+
for i in range(len(TIME1)):
|
|
811
|
+
lat1 = np.radians(LAT1[i])
|
|
812
|
+
lon1 = np.radians(LON1[i])
|
|
813
|
+
lat2 = np.radians(LAT2[:])
|
|
814
|
+
lon2 = np.radians(LON2[:])
|
|
815
|
+
|
|
816
|
+
dlon = lon2 - lon1
|
|
817
|
+
dlat = lat2 - lat1
|
|
818
|
+
|
|
819
|
+
a = np.sin(dlat / 2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2
|
|
820
|
+
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
|
|
821
|
+
|
|
822
|
+
Dist[i,:] = R * c * 1e3
|
|
823
|
+
Time[i,:] = np.abs(TIME2[:]-TIME1[i])
|
|
824
|
+
|
|
825
|
+
return Dist, Time
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def filtering_tc(T,C,freq_echant,high_cutoff=1):
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
sampling_frequency = freq_echant
|
|
834
|
+
order = 2
|
|
835
|
+
|
|
836
|
+
nyquist = sampling_frequency / 2.0
|
|
837
|
+
normalized_cutoff = high_cutoff / nyquist
|
|
838
|
+
|
|
839
|
+
b_bp, a_bp = butter(order,normalized_cutoff, btype='lowpass')
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
# Filter only the valid (non-NaN) leading segment
|
|
843
|
+
valid_idx = np.where(np.isfinite(T) & np.isfinite(C))[0]
|
|
844
|
+
T_low = np.full_like(T, np.nan)
|
|
845
|
+
C_low = np.full_like(C, np.nan)
|
|
846
|
+
|
|
847
|
+
if valid_idx.size > 0:
|
|
848
|
+
n_valid = valid_idx[-1] + 1
|
|
849
|
+
T_low[:n_valid] = filtfilt(b_bp, a_bp, T[:n_valid])
|
|
850
|
+
C_low[:n_valid] = filtfilt(b_bp, a_bp, C[:n_valid])
|
|
851
|
+
return T_low,C_low
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def temporal_lag(T,C,P,freq_echant):
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
# Band-pass filter to keep frequencies between 0.1 Hz and 9 Hz
|
|
858
|
+
low_cutoff = 0.1
|
|
859
|
+
high_cutoff = 5
|
|
860
|
+
sampling_frequency = freq_echant
|
|
861
|
+
order = 1
|
|
862
|
+
|
|
863
|
+
nyquist = sampling_frequency / 2.0
|
|
864
|
+
normalized_low = low_cutoff / nyquist
|
|
865
|
+
normalized_high = high_cutoff / nyquist
|
|
866
|
+
|
|
867
|
+
b_bp, a_bp = butter(order, [normalized_low, normalized_high], btype='band')
|
|
868
|
+
|
|
869
|
+
# Filter only the valid (non-NaN) leading segment
|
|
870
|
+
valid_idx = np.where(np.isfinite(T) & np.isfinite(C))[0]
|
|
871
|
+
T_high = np.full_like(T, np.nan)
|
|
872
|
+
C_high = np.full_like(C, np.nan)
|
|
873
|
+
|
|
874
|
+
if valid_idx.size > 0:
|
|
875
|
+
n_valid = valid_idx[-1] + 1
|
|
876
|
+
T_high[:n_valid] = filtfilt(b_bp, a_bp, T[:n_valid])
|
|
877
|
+
C_high[:n_valid] = filtfilt(b_bp, a_bp, C[:n_valid])
|
|
878
|
+
|
|
879
|
+
C_high = C_high - np.nanmean(C_high)
|
|
880
|
+
T_high = T_high - np.nanmean(T_high)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
corr = correlate(T_high,
|
|
885
|
+
C_high,
|
|
886
|
+
mode='full')
|
|
887
|
+
|
|
888
|
+
lags = correlation_lags(len(T_high), len(C_high), mode='full')
|
|
889
|
+
|
|
890
|
+
corr = corr / (np.std(T_high) * np.std(C_high) * len(T_high))
|
|
891
|
+
|
|
892
|
+
# lag optimal
|
|
893
|
+
lag_samples = lags[np.argmax(np.abs(corr))]
|
|
894
|
+
lag_time = lag_samples / 20 # 20 Hz
|
|
895
|
+
|
|
896
|
+
# print("Lag (samples) =", lag_samples)
|
|
897
|
+
# print("Lag (sec) =", lag_time)
|
|
898
|
+
|
|
899
|
+
if lag_samples == 0:
|
|
900
|
+
return T, gsw.SP_from_C(C,T,P)
|
|
901
|
+
|
|
902
|
+
T_corr = T.copy()
|
|
903
|
+
T_corr[:-lag_samples] = T[lag_samples:]
|
|
904
|
+
T_corr[-lag_samples:] = T_corr[-lag_samples-1]
|
|
905
|
+
S_corr = gsw.SP_from_C(C,T_corr,P)
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
# t_shifted = Time + lag_time_sub
|
|
909
|
+
# T_corr2 = np.interp(t_shifted, Time, T, left=np.nan, right=np.nan)
|
|
910
|
+
# S_corr2 = gsw.SP_from_C(C,T_corr2,P)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
normalized_cutoff = 0.5 / freq_echant / 2.0
|
|
915
|
+
b_bp, a_bp = butter(4,normalized_cutoff, btype='lowpass')
|
|
916
|
+
n_valid = np.isfinite(S_corr)
|
|
917
|
+
S_corr[n_valid] = filtfilt(b_bp, a_bp, S_corr[n_valid])
|
|
918
|
+
|
|
919
|
+
return T_corr,S_corr
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def bin_average(P,T,C,time,dp=0.05):
|
|
925
|
+
if np.sum(np.isnan(P)) > 0:
|
|
926
|
+
print('nan in pressure, cannot bin')
|
|
927
|
+
if np.sum(np.isnan(T)) > 0:
|
|
928
|
+
print('nan in temperature, cannot bin')
|
|
929
|
+
if np.sum(np.isnan(C)) > 0:
|
|
930
|
+
print('nan in conductivity, cannot bin')
|
|
931
|
+
|
|
932
|
+
idx = np.argsort(P)
|
|
933
|
+
P, T, C, time = P[idx], T[idx], C[idx], time[idx]
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
bins = np.arange(P.min(), P.max(), dp)
|
|
937
|
+
digitized = np.digitize(P, bins)
|
|
938
|
+
|
|
939
|
+
P_bin = []
|
|
940
|
+
T_bin = []
|
|
941
|
+
C_bin = []
|
|
942
|
+
time_bin = []
|
|
943
|
+
|
|
944
|
+
for i in range(1, len(bins)):
|
|
945
|
+
mask_bin = digitized == i
|
|
946
|
+
if np.any(mask_bin):
|
|
947
|
+
P_bin.append(P[mask_bin].mean())
|
|
948
|
+
T_bin.append(T[mask_bin].mean())
|
|
949
|
+
C_bin.append(C[mask_bin].mean())
|
|
950
|
+
time_bin.append(time[mask_bin].mean())
|
|
951
|
+
|
|
952
|
+
return (np.array(P_bin),
|
|
953
|
+
np.array(T_bin),
|
|
954
|
+
np.array(C_bin),
|
|
955
|
+
np.array(time_bin))
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def bin_average_v2(P,T,C,S,time,dp=0.05):
|
|
960
|
+
if np.sum(np.isnan(P)) > 0:
|
|
961
|
+
print('nan in pressure, cannot bin')
|
|
962
|
+
if np.sum(np.isnan(T)) > 0:
|
|
963
|
+
print('nan in temperature, cannot bin')
|
|
964
|
+
if np.sum(np.isnan(C)) > 0:
|
|
965
|
+
print('nan in conductivity, cannot bin')
|
|
966
|
+
if np.sum(np.isnan(S)) > 0:
|
|
967
|
+
print('nan in salinity, cannot bin')
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
idx = np.argsort(P)
|
|
971
|
+
P, T, C, S, time = P[idx], T[idx], C[idx], S[idx], time[idx]
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
bins = np.arange(P.min(), P.max(), dp)
|
|
975
|
+
digitized = np.digitize(P, bins)
|
|
976
|
+
|
|
977
|
+
P_bin = []
|
|
978
|
+
T_bin = []
|
|
979
|
+
C_bin = []
|
|
980
|
+
S_bin = []
|
|
981
|
+
time_bin = []
|
|
982
|
+
|
|
983
|
+
for i in range(1, len(bins)):
|
|
984
|
+
mask_bin = digitized == i
|
|
985
|
+
if np.any(mask_bin):
|
|
986
|
+
P_bin.append(P[mask_bin].mean())
|
|
987
|
+
T_bin.append(T[mask_bin].mean())
|
|
988
|
+
C_bin.append(C[mask_bin].mean())
|
|
989
|
+
S_bin.append(S[mask_bin].mean())
|
|
990
|
+
time_bin.append(time[mask_bin].mean())
|
|
991
|
+
|
|
992
|
+
return (np.array(P_bin),
|
|
993
|
+
np.array(T_bin),
|
|
994
|
+
np.array(C_bin),
|
|
995
|
+
np.array(S_bin),
|
|
996
|
+
np.array(time_bin))
|