ctdproc 0.2.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.
- ctdproc/__init__.py +6 -0
- ctdproc/calcs.py +246 -0
- ctdproc/helpers.py +325 -0
- ctdproc/io.py +976 -0
- ctdproc/proc.py +770 -0
- ctdproc-0.2.0.dist-info/METADATA +76 -0
- ctdproc-0.2.0.dist-info/RECORD +9 -0
- ctdproc-0.2.0.dist-info/WHEEL +4 -0
- ctdproc-0.2.0.dist-info/licenses/LICENSE +22 -0
ctdproc/__init__.py
ADDED
ctdproc/calcs.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
import gsw
|
|
4
|
+
import numpy as np
|
|
5
|
+
from scipy import signal
|
|
6
|
+
|
|
7
|
+
from . import helpers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def swcalcs(data):
|
|
11
|
+
"""
|
|
12
|
+
Calculate SA, s, CT, theta, sigma, oxygen, depth, z
|
|
13
|
+
|
|
14
|
+
Using gsw package. Variable names for practical salinity
|
|
15
|
+
and potential temperature are kept consistent with the
|
|
16
|
+
Matlab processing package.
|
|
17
|
+
|
|
18
|
+
Salinity is calculated both as absolute salinity (SA)
|
|
19
|
+
and practical salinity (s). Note: Per TEOS-10 instructions,
|
|
20
|
+
the variable to be archived is practical salinity.
|
|
21
|
+
|
|
22
|
+
Temperature is converted to conservative temperature (CT)
|
|
23
|
+
and potential temperature (th). Potential temperature
|
|
24
|
+
is referenced to 0 dbar.
|
|
25
|
+
|
|
26
|
+
Potential density anomaly is referenced to 0 dbar pressure.
|
|
27
|
+
The anomaly is calculated as potential density minus 1000 kg/m^3.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
data : array-like
|
|
32
|
+
CTD time series data structure.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
data : array-like
|
|
37
|
+
CTD time series data structure.
|
|
38
|
+
"""
|
|
39
|
+
# Most derived variables are now calculated in proc.ctd_cleanup2()
|
|
40
|
+
# Not sure why they are calculated two times in the Matlab package.
|
|
41
|
+
|
|
42
|
+
# data = calc_sal(data)
|
|
43
|
+
# data = calc_temp(data)
|
|
44
|
+
# data = calc_sigma(data)
|
|
45
|
+
data = calc_depth(data)
|
|
46
|
+
|
|
47
|
+
# TODO: oxygen
|
|
48
|
+
|
|
49
|
+
return data
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def calc_sal(data):
|
|
53
|
+
# Salinity
|
|
54
|
+
SA1, SP1 = calc_allsal(data.c1, data.t1, data.p, data.lon, data.lat)
|
|
55
|
+
SA2, SP2 = calc_allsal(data.c2, data.t2, data.p, data.lon, data.lat)
|
|
56
|
+
|
|
57
|
+
# Absolute salinity
|
|
58
|
+
data["SA1"] = (
|
|
59
|
+
("time",),
|
|
60
|
+
SA1.data,
|
|
61
|
+
{
|
|
62
|
+
"long_name": "absolute salinity",
|
|
63
|
+
"units": "g kg-1",
|
|
64
|
+
"standard_name": "sea_water_absolute_salinity",
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
data["SA2"] = (
|
|
68
|
+
("time",),
|
|
69
|
+
SA2.data,
|
|
70
|
+
{
|
|
71
|
+
"long_name": "absolute salinity",
|
|
72
|
+
"units": "g kg-1",
|
|
73
|
+
"standard_name": "sea_water_absolute_salinity",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Practical salinity
|
|
78
|
+
data["s1"] = (
|
|
79
|
+
("time",),
|
|
80
|
+
SP1.data,
|
|
81
|
+
{
|
|
82
|
+
"long_name": "practical salinity",
|
|
83
|
+
"units": "",
|
|
84
|
+
"standard_name": "sea_water_practical_salinity",
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
data["s2"] = (
|
|
88
|
+
("time",),
|
|
89
|
+
SP2.data,
|
|
90
|
+
{
|
|
91
|
+
"long_name": "practical salinity",
|
|
92
|
+
"units": "",
|
|
93
|
+
"standard_name": "sea_water_practical_salinity",
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def calc_temp(data):
|
|
101
|
+
# Conservative temperature
|
|
102
|
+
for si in ["1", "2"]:
|
|
103
|
+
data["CT{:s}".format(si)] = (
|
|
104
|
+
("time",),
|
|
105
|
+
gsw.CT_from_t(
|
|
106
|
+
data["s{:s}".format(si)], data["t{:s}".format(si)], data.p
|
|
107
|
+
).data,
|
|
108
|
+
{
|
|
109
|
+
"long_name": "conservative temperature",
|
|
110
|
+
"units": "degree_C",
|
|
111
|
+
"standard_name": "sea_water_conservative_temperature",
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Potential temperature
|
|
116
|
+
for si in ["1", "2"]:
|
|
117
|
+
data["th{:s}".format(si)] = (
|
|
118
|
+
("time",),
|
|
119
|
+
gsw.pt_from_t(
|
|
120
|
+
data["SA{:s}".format(si)],
|
|
121
|
+
data["t{:s}".format(si)],
|
|
122
|
+
p=data.p,
|
|
123
|
+
p_ref=0,
|
|
124
|
+
).data,
|
|
125
|
+
{
|
|
126
|
+
"long_name": "potential temperature",
|
|
127
|
+
"units": "degree_C",
|
|
128
|
+
"standard_name": "sea_water_potential_temperature",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return data
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def calc_sigma(data):
|
|
136
|
+
# Potential density anomaly
|
|
137
|
+
for si in ["1", "2"]:
|
|
138
|
+
data["sg{:s}".format(si)] = (
|
|
139
|
+
("time",),
|
|
140
|
+
gsw.sigma0(
|
|
141
|
+
data["SA{:s}".format(si)],
|
|
142
|
+
data["CT{:s}".format(si)],
|
|
143
|
+
).data,
|
|
144
|
+
{
|
|
145
|
+
"long_name": "potential density anomaly",
|
|
146
|
+
"units": "kg m-3",
|
|
147
|
+
"standard_name": "sea_water_sigma_theta",
|
|
148
|
+
"reference_pressure": "0 dbar",
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
return data
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def calc_depth(data):
|
|
155
|
+
# Depth
|
|
156
|
+
data.coords["depth"] = (
|
|
157
|
+
("time",),
|
|
158
|
+
-1 * gsw.z_from_p(data.p, data.lat).data,
|
|
159
|
+
{
|
|
160
|
+
"long_name": "depth",
|
|
161
|
+
"units": "m",
|
|
162
|
+
"standard_name": "depth",
|
|
163
|
+
"positive": "down",
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
return data
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def calc_allsal(c, t, p, lon, lat):
|
|
170
|
+
"""
|
|
171
|
+
Calculate absolute and practical salinity.
|
|
172
|
+
|
|
173
|
+
Wrapper for gsw functions. Converts conductivity
|
|
174
|
+
from S/m to mS/cm if output salinity is less than 5.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
c : array-like
|
|
179
|
+
Conductivity. See notes on units above.
|
|
180
|
+
t : array-like
|
|
181
|
+
In-situ temperature (ITS-90), degrees C
|
|
182
|
+
p : array-like
|
|
183
|
+
Sea pressure, dbar
|
|
184
|
+
lon : array-like
|
|
185
|
+
Longitude, -360 to 360 degrees
|
|
186
|
+
lat : array-like
|
|
187
|
+
Latitude, -90 to 90 degrees
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
SA : array-like, g/kg
|
|
191
|
+
Absolute Salinity
|
|
192
|
+
SP : array-like
|
|
193
|
+
Practical Salinity
|
|
194
|
+
"""
|
|
195
|
+
SP = gsw.SP_from_C(c, t, p)
|
|
196
|
+
if np.nanmean(SP) < 5:
|
|
197
|
+
SP = gsw.SP_from_C(10 * c, t, p)
|
|
198
|
+
SA = gsw.SA_from_SP(SP, p, lon, lat)
|
|
199
|
+
return SA, SP
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def wsink(p, Ts, Fs):
|
|
203
|
+
"""
|
|
204
|
+
Compute sinking velocity from pressure record.
|
|
205
|
+
|
|
206
|
+
Computes the sinking (or rising) velocity from the pressure signal p
|
|
207
|
+
by first differencing. The pressure signal is smoothed with a low-pass
|
|
208
|
+
filter for differentiation. If the input signal is shorter than the
|
|
209
|
+
smoothing time scale, w is taken as the slope of the linear regression of p.
|
|
210
|
+
|
|
211
|
+
Adapted from wsink.m - Fabian Wolk, Rockland Oceanographic Services Inc.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
p : array-like
|
|
216
|
+
Pressure [dbar]
|
|
217
|
+
Ts : float
|
|
218
|
+
Smoothing time scale [s]
|
|
219
|
+
Fs : float
|
|
220
|
+
Sampling frequency [Hz]
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
w : array-like
|
|
225
|
+
Sinking velocity [dbar/s]
|
|
226
|
+
"""
|
|
227
|
+
FORDER = 1
|
|
228
|
+
# low pass filter coefficients
|
|
229
|
+
[b, a] = signal.butter(FORDER, 1 / Ts * 2 / Fs)
|
|
230
|
+
N = p.size
|
|
231
|
+
if N <= Fs * Ts * FORDER:
|
|
232
|
+
pol = np.polyfit(np.array(range(N)), p, 1)
|
|
233
|
+
w = pol[0] * Fs * np.ones(N)
|
|
234
|
+
else:
|
|
235
|
+
# pad the pressure vector left and right
|
|
236
|
+
nPad = int(FORDER * Ts * Fs)
|
|
237
|
+
if nPad > N:
|
|
238
|
+
print(
|
|
239
|
+
"warning: length of pressure vector is smaller than padding length.\n",
|
|
240
|
+
"Filter transients may occur.",
|
|
241
|
+
)
|
|
242
|
+
p = helpers.pad_lr(p, nPad)
|
|
243
|
+
w = np.gradient(Fs * signal.filtfilt(b, a, p))
|
|
244
|
+
w = w[nPad:-nPad]
|
|
245
|
+
|
|
246
|
+
return w
|
ctdproc/helpers.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from scipy.interpolate import interp1d
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def unique_arrays(*arrays):
|
|
10
|
+
"""
|
|
11
|
+
Find unique elements in more than one numpy array.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
arrays : A tuple of np.array.ndarray
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
unique : np.array
|
|
20
|
+
numpy array with unique elements from the input arrays
|
|
21
|
+
"""
|
|
22
|
+
return np.unique(np.hstack(arrays))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def findsegments(ibad):
|
|
26
|
+
"""
|
|
27
|
+
Find contiguous segments in an array of indices.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
ibad : np.array
|
|
32
|
+
Array with indices
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
istart : np.array
|
|
37
|
+
Segment start indices
|
|
38
|
+
istop : np.array
|
|
39
|
+
Segment stop indices
|
|
40
|
+
seglength : np.array
|
|
41
|
+
Segment length
|
|
42
|
+
"""
|
|
43
|
+
dibad = np.diff(ibad)
|
|
44
|
+
jj = np.argwhere(dibad > 1)
|
|
45
|
+
|
|
46
|
+
istart = jj + 1
|
|
47
|
+
istart = np.insert(istart, 0, 0)
|
|
48
|
+
istart = ibad[istart]
|
|
49
|
+
|
|
50
|
+
istop = jj
|
|
51
|
+
istop = np.hstack([np.squeeze(istop), ibad.size - 1])
|
|
52
|
+
istop = ibad[istop]
|
|
53
|
+
|
|
54
|
+
seglength = istop - istart + 1
|
|
55
|
+
|
|
56
|
+
return istart, istop, seglength
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def inearby(ibad, inearm, inearp, n):
|
|
60
|
+
"""
|
|
61
|
+
Find indices left and right of given indices and add them to
|
|
62
|
+
the index array.
|
|
63
|
+
|
|
64
|
+
Differs from inearby.m as it breaks up ibad into segments and
|
|
65
|
+
then finds nearby indices for each segment.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
ibad : np.array
|
|
70
|
+
Array with indices
|
|
71
|
+
inearm : int
|
|
72
|
+
Number of elements to include on left side
|
|
73
|
+
inearp : int
|
|
74
|
+
Number of elements to include on right side
|
|
75
|
+
n : int
|
|
76
|
+
Maximum index in output array and size of array
|
|
77
|
+
being indexed
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
k : np.array
|
|
82
|
+
New array of indices
|
|
83
|
+
"""
|
|
84
|
+
if ibad.size == 0:
|
|
85
|
+
k = np.array([]).astype("int64")
|
|
86
|
+
else:
|
|
87
|
+
istart, istop, seglength = findsegments(ibad)
|
|
88
|
+
new_ind = np.array([]).astype("int64")
|
|
89
|
+
for ia, ib in zip(istart, istop):
|
|
90
|
+
new_ind = np.append(new_ind, np.arange(ia - inearm, ib + inearp + 1))
|
|
91
|
+
k = np.unique(new_ind)
|
|
92
|
+
k = k[((k >= 0) & (k < n))]
|
|
93
|
+
return k
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def interpbadsegments(x, ibad):
|
|
97
|
+
"""
|
|
98
|
+
Interpolate over segments of bad data.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
x : np.array
|
|
103
|
+
Input data
|
|
104
|
+
ibad : np.array
|
|
105
|
+
Indices of bad data
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
y : np.array
|
|
110
|
+
Interpolated array
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def start_end_warning(loc):
|
|
114
|
+
warnings.warn(
|
|
115
|
+
message=f"no interpolation at {loc}",
|
|
116
|
+
category=RuntimeWarning,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
istart, istop, seglen = findsegments(ibad)
|
|
120
|
+
y = x.copy()
|
|
121
|
+
for iia, iis, iilen in zip(istart, istop, seglen):
|
|
122
|
+
i1 = iia - 1
|
|
123
|
+
i2 = range(iia, iis + 1)
|
|
124
|
+
i3 = iis + 1
|
|
125
|
+
if i1 < 0:
|
|
126
|
+
# start_end_warning("start")
|
|
127
|
+
pass
|
|
128
|
+
elif i3 > x.size:
|
|
129
|
+
# start_end_warning("end")
|
|
130
|
+
pass
|
|
131
|
+
else:
|
|
132
|
+
y[i2] = interp1d(np.array([i1, i3]), x[[i1, i3]])(i2)
|
|
133
|
+
return y
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def glitchcorrect(x, diffx, prodx, ibefore=0, iafter=0):
|
|
137
|
+
"""
|
|
138
|
+
Remove glitches/spikes in array.
|
|
139
|
+
|
|
140
|
+
Adapted from tms_tc_glitchcorrect.m
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
x : np.array
|
|
145
|
+
Input array
|
|
146
|
+
diffx : int
|
|
147
|
+
Threshold for differences
|
|
148
|
+
prodx : int
|
|
149
|
+
Threshold for products
|
|
150
|
+
ibefore : int, optional
|
|
151
|
+
Number of elements to interpolate on left side of glitch
|
|
152
|
+
after : int, optional
|
|
153
|
+
Number of elements to interpolate on right side of glitch
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
y : np.array
|
|
158
|
+
Interpolated array
|
|
159
|
+
"""
|
|
160
|
+
dx = np.diff(x)
|
|
161
|
+
nx = len(dx)
|
|
162
|
+
y = x.copy()
|
|
163
|
+
|
|
164
|
+
with warnings.catch_warnings():
|
|
165
|
+
# Prevent warning due to nans present in nanmin being printed
|
|
166
|
+
warnings.simplefilter("ignore")
|
|
167
|
+
dmin2 = np.nanmin(
|
|
168
|
+
np.vstack([np.absolute(dx[0:-1]), np.absolute(dx[1:])]), axis=0
|
|
169
|
+
)
|
|
170
|
+
dmin3 = np.nanmin(
|
|
171
|
+
np.vstack([np.absolute(dx[0:-2]), np.absolute(dx[2:])]), axis=0
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
dmul2 = -dx[0:-1] * dx[1:]
|
|
175
|
+
dmul3 = -dx[0:-2] * dx[2:]
|
|
176
|
+
|
|
177
|
+
ii2 = np.squeeze(
|
|
178
|
+
np.argwhere(
|
|
179
|
+
np.greater(dmul2, prodx, where=np.isfinite(dmul2))
|
|
180
|
+
& np.greater(dmin2, diffx, where=np.isfinite(dmin2))
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
ii3 = np.squeeze(
|
|
184
|
+
np.argwhere(
|
|
185
|
+
np.greater(dmul3, prodx, where=np.isfinite(dmul3))
|
|
186
|
+
& np.greater(dmin3, diffx, where=np.isfinite(dmin3))
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
ii2 = unique_arrays(ii2, ii2 + 1)
|
|
191
|
+
ii3 = unique_arrays(ii3, ii3 + 1, ii3 + 2)
|
|
192
|
+
|
|
193
|
+
jj2 = inearby(ii2, ibefore, iafter, nx)
|
|
194
|
+
jj3 = inearby(ii3, ibefore, iafter, nx)
|
|
195
|
+
|
|
196
|
+
jj = unique_arrays(jj2, jj3)
|
|
197
|
+
|
|
198
|
+
if jj.size > 0:
|
|
199
|
+
y = interpbadsegments(x, jj)
|
|
200
|
+
|
|
201
|
+
return y
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def preen(x, xmin, xmax):
|
|
205
|
+
"""
|
|
206
|
+
Eliminate values outside given range and interpolate.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
x : np.array
|
|
211
|
+
Input array
|
|
212
|
+
xmin : float
|
|
213
|
+
Lower limit
|
|
214
|
+
xmax : float
|
|
215
|
+
Upper limit
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
xp : np.array
|
|
220
|
+
Cleaned array
|
|
221
|
+
"""
|
|
222
|
+
indexall = np.array(range(0, x.size))
|
|
223
|
+
ii = np.squeeze(np.where(((x < xmin) | (x > xmax) | (np.imag(x) != 0))))
|
|
224
|
+
indexclean = np.delete(indexall, ii)
|
|
225
|
+
x = np.delete(x, ii)
|
|
226
|
+
fint = interp1d(indexclean, x, bounds_error=False, fill_value=np.nan)
|
|
227
|
+
xp = fint(indexall)
|
|
228
|
+
return xp
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def atanfit(x, f, Phi, W):
|
|
232
|
+
f = np.arctan(2 * np.pi * f * x[0]) + 2 * np.pi * f * x[1] + Phi
|
|
233
|
+
f = np.matmul(np.matmul(f.transpose(), W ** 4), f)
|
|
234
|
+
return f
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def pad_lr(p, nPad):
|
|
238
|
+
"""Pad array left and right"""
|
|
239
|
+
p0 = p[0]
|
|
240
|
+
p = p - p0
|
|
241
|
+
p = p0 + np.insert(p, 0, -p[nPad - 1 :: -1])
|
|
242
|
+
|
|
243
|
+
p0 = p[-1]
|
|
244
|
+
p = p - p0
|
|
245
|
+
p = p0 + np.insert(p, -1, -p[: -nPad - 1 : -1])
|
|
246
|
+
|
|
247
|
+
return p
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def mtlb2datetime(matlab_datenum, strip_microseconds=False, strip_seconds=False):
|
|
251
|
+
"""
|
|
252
|
+
Convert Matlab datenum format to python datetime.
|
|
253
|
+
This version also works for vector input and strips
|
|
254
|
+
milliseconds if desired.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
matlab_datenum : float or np.array
|
|
259
|
+
Matlab time vector.
|
|
260
|
+
strip_microseconds : bool
|
|
261
|
+
Get rid of microseconds (optional)
|
|
262
|
+
strip_seconds : bool
|
|
263
|
+
Get rid of seconds (optional)
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
t : np.datetime64
|
|
268
|
+
Time in numpy's datetime64 format.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
if np.size(matlab_datenum) == 1:
|
|
272
|
+
day = datetime.datetime.fromordinal(int(matlab_datenum))
|
|
273
|
+
dayfrac = datetime.timedelta(days=matlab_datenum % 1) - datetime.timedelta(
|
|
274
|
+
days=366
|
|
275
|
+
)
|
|
276
|
+
t1 = day + dayfrac
|
|
277
|
+
if strip_microseconds and strip_seconds:
|
|
278
|
+
t1 = datetime.datetime.replace(t1, microsecond=0, second=0)
|
|
279
|
+
elif strip_microseconds:
|
|
280
|
+
t1 = datetime.datetime.replace(t1, microsecond=0)
|
|
281
|
+
|
|
282
|
+
else:
|
|
283
|
+
t1 = np.ones_like(matlab_datenum) * np.nan
|
|
284
|
+
t1 = t1.tolist()
|
|
285
|
+
nonan = np.isfinite(matlab_datenum)
|
|
286
|
+
md = matlab_datenum[nonan]
|
|
287
|
+
day = [datetime.datetime.fromordinal(int(tval)) for tval in md]
|
|
288
|
+
dayfrac = [
|
|
289
|
+
datetime.timedelta(days=tval % 1) - datetime.timedelta(days=366)
|
|
290
|
+
for tval in md
|
|
291
|
+
]
|
|
292
|
+
tt = [day1 + dayfrac1 for day1, dayfrac1 in zip(day, dayfrac)]
|
|
293
|
+
if strip_microseconds and strip_seconds:
|
|
294
|
+
tt = [
|
|
295
|
+
datetime.datetime.replace(tval, microsecond=0, second=0) for tval in tt
|
|
296
|
+
]
|
|
297
|
+
elif strip_microseconds:
|
|
298
|
+
tt = [datetime.datetime.replace(tval, microsecond=0) for tval in tt]
|
|
299
|
+
tt = [np.datetime64(ti, "ns") for ti in tt]
|
|
300
|
+
xi = np.where(nonan)[0]
|
|
301
|
+
for i, ii in enumerate(xi):
|
|
302
|
+
t1[ii] = tt[i]
|
|
303
|
+
xi = np.where(~nonan)[0]
|
|
304
|
+
for i in xi:
|
|
305
|
+
t1[i] = np.datetime64("nat")
|
|
306
|
+
t1 = np.array(t1)
|
|
307
|
+
|
|
308
|
+
return t1
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def datetime2mtlb(dt):
|
|
312
|
+
pt = pd.to_datetime(dt)
|
|
313
|
+
dt = pt.to_pydatetime()
|
|
314
|
+
mdn = dt + datetime.timedelta(days=366)
|
|
315
|
+
frac_seconds = [
|
|
316
|
+
(dti - datetime.datetime(dti.year, dti.month, dti.day, 0, 0, 0)).seconds
|
|
317
|
+
/ (24.0 * 60.0 * 60.0)
|
|
318
|
+
for dti in dt
|
|
319
|
+
]
|
|
320
|
+
frac_microseconds = [
|
|
321
|
+
dti.microsecond / (24.0 * 60.0 * 60.0 * 1000000.0) for dti in dt
|
|
322
|
+
]
|
|
323
|
+
out = np.array([mdni.toordinal() for mdni in mdn])
|
|
324
|
+
out = out.astype(float) + frac_seconds + frac_microseconds
|
|
325
|
+
return out
|