ctdproc 0.2.0__tar.gz → 0.3.0__tar.gz
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-0.2.0 → ctdproc-0.3.0}/PKG-INFO +4 -4
- ctdproc-0.3.0/pyproject.toml +78 -0
- ctdproc-0.2.0/pyproject.toml → ctdproc-0.3.0/pyproject.toml.orig +4 -1
- {ctdproc-0.2.0 → ctdproc-0.3.0}/src/ctdproc/calcs.py +78 -52
- {ctdproc-0.2.0 → ctdproc-0.3.0}/src/ctdproc/helpers.py +42 -32
- {ctdproc-0.2.0 → ctdproc-0.3.0}/src/ctdproc/io.py +261 -72
- {ctdproc-0.2.0 → ctdproc-0.3.0}/src/ctdproc/proc.py +361 -125
- {ctdproc-0.2.0 → ctdproc-0.3.0}/LICENSE +0 -0
- {ctdproc-0.2.0 → ctdproc-0.3.0}/README.rst +0 -0
- {ctdproc-0.2.0 → ctdproc-0.3.0}/src/ctdproc/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctdproc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: CTD data processing
|
|
5
5
|
Author: Gunnar Voet
|
|
6
6
|
Author-email: Gunnar Voet <gvoet@ucsd.edu>
|
|
@@ -22,10 +22,10 @@ Requires-Dist: scipy>=1.13.1
|
|
|
22
22
|
Requires-Dist: xarray>=2024.7.0
|
|
23
23
|
Requires-Dist: xmltodict>=1.0.2
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
|
-
Project-URL: Bug Tracker, https://github.com/gunnarvoet/ctdproc/issues
|
|
26
|
-
Project-URL: Changelog, https://github.com/gunnarvoet/ctdproc/blob/main/HISTORY.rst
|
|
27
|
-
Project-URL: Documentation, https://ctdproc.readthedocs.io
|
|
28
25
|
Project-URL: Homepage, https://github.com/gunnarvoet/ctdproc
|
|
26
|
+
Project-URL: Documentation, https://ctdproc.readthedocs.io
|
|
27
|
+
Project-URL: Changelog, https://github.com/gunnarvoet/ctdproc/blob/main/HISTORY.rst
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/gunnarvoet/ctdproc/issues
|
|
29
29
|
Description-Content-Type: text/x-rst
|
|
30
30
|
|
|
31
31
|
=======
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ctdproc"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "CTD data processing"
|
|
5
|
+
readme = "README.rst"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"gsw>=3.6.20",
|
|
11
|
+
"matplotlib>=3.9.4",
|
|
12
|
+
"munch>=4.0.0",
|
|
13
|
+
"netcdf4>=1.7.2",
|
|
14
|
+
"numpy>=2.0.2",
|
|
15
|
+
"pandas>=2.3.3",
|
|
16
|
+
"scipy>=1.13.1",
|
|
17
|
+
"xarray>=2024.7.0",
|
|
18
|
+
"xmltodict>=1.0.2",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Natural Language :: English",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[[project.authors]]
|
|
30
|
+
name = "Gunnar Voet"
|
|
31
|
+
email = "gvoet@ucsd.edu"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/gunnarvoet/ctdproc"
|
|
35
|
+
Documentation = "https://ctdproc.readthedocs.io"
|
|
36
|
+
Changelog = "https://github.com/gunnarvoet/ctdproc/blob/main/HISTORY.rst"
|
|
37
|
+
"Bug Tracker" = "https://github.com/gunnarvoet/ctdproc/issues"
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["uv_build>=0.8.22,<0.10.0"]
|
|
41
|
+
build-backend = "uv_build"
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8.4.2",
|
|
46
|
+
"ruff>=0.14.2",
|
|
47
|
+
{ include-group = "docs" },
|
|
48
|
+
]
|
|
49
|
+
docs = [
|
|
50
|
+
"sphinx>=7.4.7",
|
|
51
|
+
"sphinx-issues>=5.0.1",
|
|
52
|
+
"sphinx-rtd-theme>=3.0.2",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
minversion = "6.0"
|
|
57
|
+
addopts = [
|
|
58
|
+
"--strict-markers",
|
|
59
|
+
"--import-mode=importlib",
|
|
60
|
+
]
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
norecursedirs = [
|
|
63
|
+
".git",
|
|
64
|
+
".venv",
|
|
65
|
+
"dist",
|
|
66
|
+
"build",
|
|
67
|
+
"__pycache__",
|
|
68
|
+
]
|
|
69
|
+
xfail_strict = true
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
exclude = [".venv"]
|
|
73
|
+
|
|
74
|
+
[[tool.uv.index]]
|
|
75
|
+
name = "testpypi"
|
|
76
|
+
url = "https://test.pypi.org/simple/"
|
|
77
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
78
|
+
explicit = true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctdproc"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "CTD data processing"
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
authors = [ { name = "Gunnar Voet", email = "gvoet@ucsd.edu" }, ]
|
|
@@ -41,6 +41,9 @@ build-backend = "uv_build"
|
|
|
41
41
|
dev = [
|
|
42
42
|
"pytest>=8.4.2",
|
|
43
43
|
"ruff>=0.14.2",
|
|
44
|
+
{include-group = "docs"},
|
|
45
|
+
]
|
|
46
|
+
docs = [
|
|
44
47
|
"sphinx>=7.4.7",
|
|
45
48
|
"sphinx-issues>=5.0.1",
|
|
46
49
|
"sphinx-rtd-theme>=3.0.2",
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
# coding: utf-8
|
|
3
3
|
import gsw
|
|
4
4
|
import numpy as np
|
|
5
|
-
from scipy import signal
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
__all__ = [
|
|
7
|
+
"swcalcs",
|
|
8
|
+
"calc_sal",
|
|
9
|
+
"calc_temp",
|
|
10
|
+
"calc_sigma",
|
|
11
|
+
"calc_depth",
|
|
12
|
+
"calc_allsal",
|
|
13
|
+
]
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
def swcalcs(data):
|
|
@@ -50,6 +56,25 @@ def swcalcs(data):
|
|
|
50
56
|
|
|
51
57
|
|
|
52
58
|
def calc_sal(data):
|
|
59
|
+
"""
|
|
60
|
+
Add absolute and practical salinity for both sensor pairs.
|
|
61
|
+
|
|
62
|
+
Adds variables ``SA1``, ``SA2`` (absolute salinity, g/kg) and ``s1``,
|
|
63
|
+
``s2`` (practical salinity) to the dataset, computed from
|
|
64
|
+
conductivity, temperature, pressure, and the cast position via
|
|
65
|
+
:func:`calc_allsal`.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
data : xarray.Dataset
|
|
70
|
+
CTD time series with ``c1``, ``c2``, ``t1``, ``t2``, ``p``,
|
|
71
|
+
``lon``, ``lat``.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
xarray.Dataset
|
|
76
|
+
Input augmented with ``SA1``, ``SA2``, ``s1``, ``s2``.
|
|
77
|
+
"""
|
|
53
78
|
# Salinity
|
|
54
79
|
SA1, SP1 = calc_allsal(data.c1, data.t1, data.p, data.lon, data.lat)
|
|
55
80
|
SA2, SP2 = calc_allsal(data.c2, data.t2, data.p, data.lon, data.lat)
|
|
@@ -98,13 +123,29 @@ def calc_sal(data):
|
|
|
98
123
|
|
|
99
124
|
|
|
100
125
|
def calc_temp(data):
|
|
126
|
+
"""
|
|
127
|
+
Add conservative and potential temperature for both sensor pairs.
|
|
128
|
+
|
|
129
|
+
Adds variables ``CT1``, ``CT2`` (conservative temperature, deg C) and
|
|
130
|
+
``th1``, ``th2`` (potential temperature, deg C, referenced to 0 dbar)
|
|
131
|
+
to the dataset.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
data : xarray.Dataset
|
|
136
|
+
CTD time series with ``SA1``/``SA2``, ``t1``/``t2``, and ``p``. Run
|
|
137
|
+
:func:`calc_sal` first.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
xarray.Dataset
|
|
142
|
+
Input augmented with ``CT1``, ``CT2``, ``th1``, ``th2``.
|
|
143
|
+
"""
|
|
101
144
|
# Conservative temperature
|
|
102
145
|
for si in ["1", "2"]:
|
|
103
146
|
data["CT{:s}".format(si)] = (
|
|
104
147
|
("time",),
|
|
105
|
-
gsw.CT_from_t(
|
|
106
|
-
data["s{:s}".format(si)], data["t{:s}".format(si)], data.p
|
|
107
|
-
).data,
|
|
148
|
+
gsw.CT_from_t(data[f"SA{si}"], data[f"t{si}"], data.p).data,
|
|
108
149
|
{
|
|
109
150
|
"long_name": "conservative temperature",
|
|
110
151
|
"units": "degree_C",
|
|
@@ -133,6 +174,23 @@ def calc_temp(data):
|
|
|
133
174
|
|
|
134
175
|
|
|
135
176
|
def calc_sigma(data):
|
|
177
|
+
"""
|
|
178
|
+
Add potential density anomaly for both sensor pairs.
|
|
179
|
+
|
|
180
|
+
Adds variables ``sg1``, ``sg2`` (potential density anomaly, kg/m^3,
|
|
181
|
+
referenced to 0 dbar) to the dataset.
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
data : xarray.Dataset
|
|
186
|
+
CTD time series with ``SA1``/``SA2`` and ``CT1``/``CT2``. Run
|
|
187
|
+
:func:`calc_sal` and :func:`calc_temp` first.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
xarray.Dataset
|
|
192
|
+
Input augmented with ``sg1``, ``sg2``.
|
|
193
|
+
"""
|
|
136
194
|
# Potential density anomaly
|
|
137
195
|
for si in ["1", "2"]:
|
|
138
196
|
data["sg{:s}".format(si)] = (
|
|
@@ -152,6 +210,21 @@ def calc_sigma(data):
|
|
|
152
210
|
|
|
153
211
|
|
|
154
212
|
def calc_depth(data):
|
|
213
|
+
"""
|
|
214
|
+
Add a ``depth`` coordinate (m, positive down) computed from pressure.
|
|
215
|
+
|
|
216
|
+
Uses :func:`gsw.z_from_p` with latitude.
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
data : xarray.Dataset
|
|
221
|
+
CTD time series with ``p`` and ``lat``.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
xarray.Dataset
|
|
226
|
+
Input with a new ``depth`` coordinate.
|
|
227
|
+
"""
|
|
155
228
|
# Depth
|
|
156
229
|
data.coords["depth"] = (
|
|
157
230
|
("time",),
|
|
@@ -197,50 +270,3 @@ def calc_allsal(c, t, p, lon, lat):
|
|
|
197
270
|
SP = gsw.SP_from_C(10 * c, t, p)
|
|
198
271
|
SA = gsw.SA_from_SP(SP, p, lon, lat)
|
|
199
272
|
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
|
|
@@ -5,6 +5,17 @@ import numpy as np
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
from scipy.interpolate import interp1d
|
|
7
7
|
|
|
8
|
+
__all__ = [
|
|
9
|
+
"findsegments",
|
|
10
|
+
"inearby",
|
|
11
|
+
"interpbadsegments",
|
|
12
|
+
"glitchcorrect",
|
|
13
|
+
"preen",
|
|
14
|
+
"unique_arrays",
|
|
15
|
+
"mtlb2datetime",
|
|
16
|
+
"datetime2mtlb",
|
|
17
|
+
]
|
|
18
|
+
|
|
8
19
|
|
|
9
20
|
def unique_arrays(*arrays):
|
|
10
21
|
"""
|
|
@@ -125,14 +136,29 @@ def interpbadsegments(x, ibad):
|
|
|
125
136
|
if i1 < 0:
|
|
126
137
|
# start_end_warning("start")
|
|
127
138
|
pass
|
|
128
|
-
elif i3
|
|
139
|
+
elif i3 >= x.size:
|
|
129
140
|
# start_end_warning("end")
|
|
130
141
|
pass
|
|
142
|
+
elif np.isnan(x[i1]) or np.isnan(x[i3]):
|
|
143
|
+
# a NaN neighbor, such as a sample removed by despiking, would
|
|
144
|
+
# turn the whole segment into NaN; leave the segment as it is
|
|
145
|
+
pass
|
|
131
146
|
else:
|
|
132
147
|
y[i2] = interp1d(np.array([i1, i3]), x[[i1, i3]])(i2)
|
|
133
148
|
return y
|
|
134
149
|
|
|
135
150
|
|
|
151
|
+
def _greater(a, b):
|
|
152
|
+
"""``a > b`` where ``a`` is finite, False elsewhere.
|
|
153
|
+
|
|
154
|
+
``np.greater`` with ``where=`` and no ``out=`` leaves the masked entries
|
|
155
|
+
uninitialized, so a comparison against NaN came back True or False at
|
|
156
|
+
random and processing was not repeatable.
|
|
157
|
+
"""
|
|
158
|
+
a = np.asarray(a)
|
|
159
|
+
return np.greater(a, b, out=np.zeros(a.shape, dtype=bool), where=np.isfinite(a))
|
|
160
|
+
|
|
161
|
+
|
|
136
162
|
def glitchcorrect(x, diffx, prodx, ibefore=0, iafter=0):
|
|
137
163
|
"""
|
|
138
164
|
Remove glitches/spikes in array.
|
|
@@ -174,18 +200,8 @@ def glitchcorrect(x, diffx, prodx, ibefore=0, iafter=0):
|
|
|
174
200
|
dmul2 = -dx[0:-1] * dx[1:]
|
|
175
201
|
dmul3 = -dx[0:-2] * dx[2:]
|
|
176
202
|
|
|
177
|
-
ii2 = np.squeeze(
|
|
178
|
-
|
|
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
|
-
)
|
|
203
|
+
ii2 = np.squeeze(np.argwhere(_greater(dmul2, prodx) & _greater(dmin2, diffx)))
|
|
204
|
+
ii3 = np.squeeze(np.argwhere(_greater(dmul3, prodx) & _greater(dmin3, diffx)))
|
|
189
205
|
|
|
190
206
|
ii2 = unique_arrays(ii2, ii2 + 1)
|
|
191
207
|
ii3 = unique_arrays(ii3, ii3 + 1, ii3 + 2)
|
|
@@ -228,25 +244,6 @@ def preen(x, xmin, xmax):
|
|
|
228
244
|
return xp
|
|
229
245
|
|
|
230
246
|
|
|
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
247
|
def mtlb2datetime(matlab_datenum, strip_microseconds=False, strip_seconds=False):
|
|
251
248
|
"""
|
|
252
249
|
Convert Matlab datenum format to python datetime.
|
|
@@ -309,6 +306,19 @@ def mtlb2datetime(matlab_datenum, strip_microseconds=False, strip_seconds=False)
|
|
|
309
306
|
|
|
310
307
|
|
|
311
308
|
def datetime2mtlb(dt):
|
|
309
|
+
"""
|
|
310
|
+
Convert a numpy datetime64 array to MATLAB datenum.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
dt : array-like of numpy.datetime64
|
|
315
|
+
Input timestamps.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
numpy.ndarray of float
|
|
320
|
+
MATLAB datenum values (days since the MATLAB epoch).
|
|
321
|
+
"""
|
|
312
322
|
pt = pd.to_datetime(dt)
|
|
313
323
|
dt = pt.to_pydatetime()
|
|
314
324
|
mdn = dt + datetime.timedelta(days=366)
|