microstructure-tpw 0.3.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.
- microstructure_tpw-0.3.0.dist-info/METADATA +234 -0
- microstructure_tpw-0.3.0.dist-info/RECORD +119 -0
- microstructure_tpw-0.3.0.dist-info/WHEEL +5 -0
- microstructure_tpw-0.3.0.dist-info/entry_points.txt +7 -0
- microstructure_tpw-0.3.0.dist-info/licenses/LICENSE +674 -0
- microstructure_tpw-0.3.0.dist-info/top_level.txt +1 -0
- odas_tpw/__init__.py +18 -0
- odas_tpw/chi/__init__.py +48 -0
- odas_tpw/chi/batchelor.py +189 -0
- odas_tpw/chi/chi.py +857 -0
- odas_tpw/chi/fp07.py +649 -0
- odas_tpw/chi/l2_chi.py +153 -0
- odas_tpw/chi/l3_chi.py +388 -0
- odas_tpw/chi/l4_chi.py +391 -0
- odas_tpw/config_base.py +423 -0
- odas_tpw/perturb/__init__.py +18 -0
- odas_tpw/perturb/adcp.py +274 -0
- odas_tpw/perturb/atomic_io.py +47 -0
- odas_tpw/perturb/binning.py +761 -0
- odas_tpw/perturb/cli.py +591 -0
- odas_tpw/perturb/combo.py +346 -0
- odas_tpw/perturb/config.py +1046 -0
- odas_tpw/perturb/ctd.py +424 -0
- odas_tpw/perturb/diag/__init__.py +12 -0
- odas_tpw/perturb/diag/_common.py +140 -0
- odas_tpw/perturb/diag/chi.py +42 -0
- odas_tpw/perturb/diag/cli.py +67 -0
- odas_tpw/perturb/diag/data.py +662 -0
- odas_tpw/perturb/diag/epsilon.py +45 -0
- odas_tpw/perturb/diag/inspector.py +342 -0
- odas_tpw/perturb/diag/mixing.py +94 -0
- odas_tpw/perturb/diag/render.py +543 -0
- odas_tpw/perturb/discover.py +40 -0
- odas_tpw/perturb/fp07_cal.py +354 -0
- odas_tpw/perturb/gps.py +458 -0
- odas_tpw/perturb/hotel.py +572 -0
- odas_tpw/perturb/logging_setup.py +181 -0
- odas_tpw/perturb/merge.py +320 -0
- odas_tpw/perturb/mk_sections.py +310 -0
- odas_tpw/perturb/netcdf_schema.py +520 -0
- odas_tpw/perturb/pipeline.py +2654 -0
- odas_tpw/perturb/plot/__init__.py +15 -0
- odas_tpw/perturb/plot/cli.py +107 -0
- odas_tpw/perturb/plot/diagnostics.py +195 -0
- odas_tpw/perturb/plot/eps_chi.py +524 -0
- odas_tpw/perturb/plot/figure.py +679 -0
- odas_tpw/perturb/plot/gamma_scaling.py +1097 -0
- odas_tpw/perturb/plot/grid.py +112 -0
- odas_tpw/perturb/plot/layout.py +361 -0
- odas_tpw/perturb/plot/overview.py +547 -0
- odas_tpw/perturb/plot/presets/eps-chi.yaml +16 -0
- odas_tpw/perturb/plot/presets/profiles.yaml +32 -0
- odas_tpw/perturb/plot/presets/scalar.yaml +21 -0
- odas_tpw/perturb/plot/profiles.py +652 -0
- odas_tpw/perturb/plot/scalar.py +391 -0
- odas_tpw/perturb/plot/sections.py +506 -0
- odas_tpw/perturb/plot/xaxis.py +396 -0
- odas_tpw/perturb/qc_gate.py +177 -0
- odas_tpw/perturb/qc_rules.py +373 -0
- odas_tpw/perturb/resolve.py +306 -0
- odas_tpw/perturb/seawater.py +76 -0
- odas_tpw/perturb/speed.py +14 -0
- odas_tpw/perturb/trim.py +313 -0
- odas_tpw/processing/__init__.py +62 -0
- odas_tpw/processing/bottom.py +215 -0
- odas_tpw/processing/chi_combine.py +300 -0
- odas_tpw/processing/ct_align.py +172 -0
- odas_tpw/processing/epsilon_combine.py +210 -0
- odas_tpw/processing/mixing.py +630 -0
- odas_tpw/processing/thorpe.py +458 -0
- odas_tpw/processing/top_trim.py +274 -0
- odas_tpw/pyturb/__init__.py +3 -0
- odas_tpw/pyturb/__main__.py +5 -0
- odas_tpw/pyturb/_compat.py +204 -0
- odas_tpw/pyturb/_profind.py +124 -0
- odas_tpw/pyturb/bin.py +113 -0
- odas_tpw/pyturb/cli.py +175 -0
- odas_tpw/pyturb/eps.py +353 -0
- odas_tpw/pyturb/merge.py +101 -0
- odas_tpw/pyturb/p2nc.py +76 -0
- odas_tpw/rsi/__init__.py +31 -0
- odas_tpw/rsi/adapter.py +314 -0
- odas_tpw/rsi/bench.py +904 -0
- odas_tpw/rsi/binning.py +193 -0
- odas_tpw/rsi/channels.py +376 -0
- odas_tpw/rsi/chi_io.py +980 -0
- odas_tpw/rsi/cli.py +1411 -0
- odas_tpw/rsi/combine.py +120 -0
- odas_tpw/rsi/config.py +126 -0
- odas_tpw/rsi/config_patch.py +790 -0
- odas_tpw/rsi/convert.py +651 -0
- odas_tpw/rsi/deconvolve.py +119 -0
- odas_tpw/rsi/diss_look.py +343 -0
- odas_tpw/rsi/dissipation.py +669 -0
- odas_tpw/rsi/helpers.py +527 -0
- odas_tpw/rsi/mixing_look.py +509 -0
- odas_tpw/rsi/p_file.py +771 -0
- odas_tpw/rsi/pipeline.py +924 -0
- odas_tpw/rsi/profile.py +624 -0
- odas_tpw/rsi/quick_look.py +758 -0
- odas_tpw/rsi/sensor_inventory.py +684 -0
- odas_tpw/rsi/shear_noise.py +128 -0
- odas_tpw/rsi/speed.py +221 -0
- odas_tpw/rsi/vehicle.py +74 -0
- odas_tpw/rsi/viewer_base.py +1071 -0
- odas_tpw/rsi/window.py +459 -0
- odas_tpw/scor160/__init__.py +36 -0
- odas_tpw/scor160/cli.py +242 -0
- odas_tpw/scor160/compare.py +484 -0
- odas_tpw/scor160/despike.py +227 -0
- odas_tpw/scor160/goodman.py +288 -0
- odas_tpw/scor160/io.py +690 -0
- odas_tpw/scor160/l2.py +311 -0
- odas_tpw/scor160/l3.py +279 -0
- odas_tpw/scor160/l4.py +850 -0
- odas_tpw/scor160/nasmyth.py +208 -0
- odas_tpw/scor160/ocean.py +287 -0
- odas_tpw/scor160/profile.py +231 -0
- odas_tpw/scor160/spectral.py +482 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Mar-2026, Claude and Pat Welch, pat@mousebrains.com
|
|
2
|
+
"""CF/ACDD NetCDF schema definitions.
|
|
3
|
+
|
|
4
|
+
Python replacement for Matlab's Combo.json and CTD.json.
|
|
5
|
+
Provides variable metadata (units, standard_name, long_name) for
|
|
6
|
+
CF-1.13 / ACDD-1.3 compliant NetCDF output.
|
|
7
|
+
|
|
8
|
+
Reference: Code/Combo.json, Code/CTD.json
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import xarray as xr
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# UDUNITS fixups for unit strings emitted by RSI's INI config
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
#
|
|
17
|
+
# RSI .p files carry unit strings in the embedded ASCII config that are
|
|
18
|
+
# either UDUNITS-incompatible (``umol_L-1`` instead of ``umol L-1``) or
|
|
19
|
+
# ambiguous (``deg`` — degree of arc or degree Celsius?). compliance-checker
|
|
20
|
+
# emits hard CF errors on these. The mapping below normalizes everything to
|
|
21
|
+
# UDUNITS-parseable strings and is applied by :func:`canonicalize_units`
|
|
22
|
+
# wherever we write per-channel attributes (e.g. extract_profiles).
|
|
23
|
+
|
|
24
|
+
UNITS_FIXUP: dict[str, str] = {
|
|
25
|
+
"umol_L-1": "umol L-1",
|
|
26
|
+
"mS_cm-1": "mS cm-1",
|
|
27
|
+
"deg_C": "degree_Celsius",
|
|
28
|
+
"deg": "degree",
|
|
29
|
+
# Turbidity: FTU is widely used in oceanography but absent from UDUNITS.
|
|
30
|
+
# CF guidance is to mark it dimensionless and document in long_name.
|
|
31
|
+
"FTU": "1",
|
|
32
|
+
"NTU": "1",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def canonicalize_units(units: str) -> str:
|
|
37
|
+
"""Translate an RSI-emitted unit string to a UDUNITS-parseable one."""
|
|
38
|
+
return UNITS_FIXUP.get(units.strip(), units.strip())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Schema definitions — maps variable names to CF attributes
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
COMBO_SCHEMA: dict[str, dict] = {
|
|
46
|
+
"bin": {
|
|
47
|
+
"units": "m",
|
|
48
|
+
"standard_name": "depth",
|
|
49
|
+
"long_name": "depth bin center",
|
|
50
|
+
"positive": "down",
|
|
51
|
+
"axis": "Z",
|
|
52
|
+
},
|
|
53
|
+
"profile": {
|
|
54
|
+
"long_name": "profile index",
|
|
55
|
+
"cf_role": "profile_id",
|
|
56
|
+
},
|
|
57
|
+
"time": {
|
|
58
|
+
"nc_name": "time",
|
|
59
|
+
"units": "seconds since 1970-01-01",
|
|
60
|
+
"standard_name": "time",
|
|
61
|
+
"long_name": "time",
|
|
62
|
+
},
|
|
63
|
+
"stime": {
|
|
64
|
+
"nc_name": "stime",
|
|
65
|
+
"units": "seconds since 1970-01-01",
|
|
66
|
+
"standard_name": "time",
|
|
67
|
+
"long_name": "profile start time",
|
|
68
|
+
},
|
|
69
|
+
"etime": {
|
|
70
|
+
"nc_name": "etime",
|
|
71
|
+
"units": "seconds since 1970-01-01",
|
|
72
|
+
"standard_name": "time",
|
|
73
|
+
"long_name": "profile end time",
|
|
74
|
+
},
|
|
75
|
+
"depth": {
|
|
76
|
+
"nc_name": "depth",
|
|
77
|
+
"units": "m",
|
|
78
|
+
"standard_name": "depth",
|
|
79
|
+
"long_name": "depth",
|
|
80
|
+
"positive": "down",
|
|
81
|
+
},
|
|
82
|
+
"P": {
|
|
83
|
+
"nc_name": "pressure",
|
|
84
|
+
"units": "dbar",
|
|
85
|
+
"standard_name": "sea_water_pressure",
|
|
86
|
+
"long_name": "sea water pressure",
|
|
87
|
+
},
|
|
88
|
+
"T": {
|
|
89
|
+
"nc_name": "temp",
|
|
90
|
+
"units": "degree_Celsius",
|
|
91
|
+
"standard_name": "sea_water_temperature",
|
|
92
|
+
"long_name": "in-situ temperature",
|
|
93
|
+
"units_metadata": "temperature: on_scale",
|
|
94
|
+
},
|
|
95
|
+
"T1": {
|
|
96
|
+
"units": "degree_Celsius",
|
|
97
|
+
"standard_name": "sea_water_temperature",
|
|
98
|
+
"long_name": "FP07 thermistor temperature (probe 1)",
|
|
99
|
+
"units_metadata": "temperature: on_scale",
|
|
100
|
+
},
|
|
101
|
+
"T2": {
|
|
102
|
+
"units": "degree_Celsius",
|
|
103
|
+
"standard_name": "sea_water_temperature",
|
|
104
|
+
"long_name": "FP07 thermistor temperature (probe 2)",
|
|
105
|
+
"units_metadata": "temperature: on_scale",
|
|
106
|
+
},
|
|
107
|
+
"DO_T": {
|
|
108
|
+
"units": "degree_Celsius",
|
|
109
|
+
"long_name": "dissolved-oxygen sensor temperature",
|
|
110
|
+
"units_metadata": "temperature: on_scale",
|
|
111
|
+
},
|
|
112
|
+
"DO": {
|
|
113
|
+
"units": "umol L-1",
|
|
114
|
+
"standard_name": "mole_concentration_of_dissolved_molecular_oxygen_in_sea_water",
|
|
115
|
+
"long_name": "dissolved oxygen",
|
|
116
|
+
},
|
|
117
|
+
"Chlorophyll": {
|
|
118
|
+
"units": "ug L-1",
|
|
119
|
+
"standard_name": "mass_concentration_of_chlorophyll_in_sea_water",
|
|
120
|
+
"long_name": "chlorophyll concentration",
|
|
121
|
+
},
|
|
122
|
+
"Turbidity": {
|
|
123
|
+
"units": "1",
|
|
124
|
+
"long_name": "turbidity (formazin turbidity units)",
|
|
125
|
+
},
|
|
126
|
+
"P_dP": {
|
|
127
|
+
"units": "dbar",
|
|
128
|
+
"long_name": "pressure offset (P - low-pass filtered P)",
|
|
129
|
+
},
|
|
130
|
+
"Gnd": {
|
|
131
|
+
"units": "V",
|
|
132
|
+
"long_name": "ground reference voltage",
|
|
133
|
+
},
|
|
134
|
+
"V_Bat": {
|
|
135
|
+
"units": "V",
|
|
136
|
+
"long_name": "battery voltage",
|
|
137
|
+
},
|
|
138
|
+
"PV": {
|
|
139
|
+
"units": "V",
|
|
140
|
+
"long_name": "pressure transducer voltage",
|
|
141
|
+
},
|
|
142
|
+
"Incl_X": {
|
|
143
|
+
"units": "degree",
|
|
144
|
+
"long_name": "inclinometer X angle",
|
|
145
|
+
},
|
|
146
|
+
"Incl_Y": {
|
|
147
|
+
"units": "degree",
|
|
148
|
+
"long_name": "inclinometer Y angle",
|
|
149
|
+
},
|
|
150
|
+
"Incl_T": {
|
|
151
|
+
"units": "degree_Celsius",
|
|
152
|
+
"long_name": "inclinometer temperature",
|
|
153
|
+
"units_metadata": "temperature: on_scale",
|
|
154
|
+
},
|
|
155
|
+
"W_slow": {
|
|
156
|
+
"units": "dbar s-1",
|
|
157
|
+
"long_name": "profiling rate (smoothed |dP/dt|)",
|
|
158
|
+
},
|
|
159
|
+
"JAC_T": {
|
|
160
|
+
"nc_name": "temp",
|
|
161
|
+
"units": "degree_Celsius",
|
|
162
|
+
"standard_name": "sea_water_temperature",
|
|
163
|
+
"long_name": "in-situ temperature (JFE)",
|
|
164
|
+
"units_metadata": "temperature: on_scale",
|
|
165
|
+
},
|
|
166
|
+
"JAC_C": {
|
|
167
|
+
"nc_name": "cond",
|
|
168
|
+
"units": "mS/cm",
|
|
169
|
+
"standard_name": "sea_water_electrical_conductivity",
|
|
170
|
+
"long_name": "conductivity (JFE)",
|
|
171
|
+
},
|
|
172
|
+
"SP": {
|
|
173
|
+
"nc_name": "SP",
|
|
174
|
+
# CF/TEOS-10: practical salinity is dimensionless ("1"), not "PSU"
|
|
175
|
+
# (not UDUNITS-parseable); the standard_name carries the meaning (#38).
|
|
176
|
+
"units": "1",
|
|
177
|
+
"standard_name": "sea_water_practical_salinity",
|
|
178
|
+
"long_name": "practical salinity (PSU)",
|
|
179
|
+
},
|
|
180
|
+
"SA": {
|
|
181
|
+
"nc_name": "SA",
|
|
182
|
+
"units": "g/kg",
|
|
183
|
+
"standard_name": "sea_water_absolute_salinity",
|
|
184
|
+
"long_name": "absolute salinity",
|
|
185
|
+
},
|
|
186
|
+
"CT": {
|
|
187
|
+
"nc_name": "CT",
|
|
188
|
+
"units": "degree_Celsius",
|
|
189
|
+
"standard_name": "sea_water_conservative_temperature",
|
|
190
|
+
"long_name": "conservative temperature",
|
|
191
|
+
"units_metadata": "temperature: on_scale",
|
|
192
|
+
},
|
|
193
|
+
"sigma0": {
|
|
194
|
+
"nc_name": "sigma0",
|
|
195
|
+
"units": "kg m-3",
|
|
196
|
+
"standard_name": "sea_water_sigma_theta",
|
|
197
|
+
"long_name": "potential density anomaly",
|
|
198
|
+
},
|
|
199
|
+
"rho": {
|
|
200
|
+
"nc_name": "rho",
|
|
201
|
+
"units": "kg m-3",
|
|
202
|
+
# No CF standard_name: this is in-situ density MINUS 1000 (an offset
|
|
203
|
+
# storage convention, mirroring sigma0), not full in-situ density.
|
|
204
|
+
# CF's "sea_water_density" denotes the real ~1025 kg/m^3 value, so
|
|
205
|
+
# claiming it would mislead a standard_name-aware consumer by ~1000.
|
|
206
|
+
"long_name": "in-situ density - 1000",
|
|
207
|
+
},
|
|
208
|
+
"lat": {
|
|
209
|
+
"nc_name": "lat",
|
|
210
|
+
"units": "degrees_north",
|
|
211
|
+
"standard_name": "latitude",
|
|
212
|
+
"long_name": "latitude",
|
|
213
|
+
},
|
|
214
|
+
"lon": {
|
|
215
|
+
"nc_name": "lon",
|
|
216
|
+
"units": "degrees_east",
|
|
217
|
+
"standard_name": "longitude",
|
|
218
|
+
"long_name": "longitude",
|
|
219
|
+
},
|
|
220
|
+
"e_1": {
|
|
221
|
+
"nc_name": "e_1",
|
|
222
|
+
"units": "W/kg",
|
|
223
|
+
"standard_name": "specific_turbulent_kinetic_energy_dissipation_in_sea_water",
|
|
224
|
+
"long_name": "TKE dissipation rate (probe 1)",
|
|
225
|
+
},
|
|
226
|
+
"e_2": {
|
|
227
|
+
"nc_name": "e_2",
|
|
228
|
+
"units": "W/kg",
|
|
229
|
+
"standard_name": "specific_turbulent_kinetic_energy_dissipation_in_sea_water",
|
|
230
|
+
"long_name": "TKE dissipation rate (probe 2)",
|
|
231
|
+
},
|
|
232
|
+
"epsilonMean": {
|
|
233
|
+
"nc_name": "epsilonMean",
|
|
234
|
+
"units": "W/kg",
|
|
235
|
+
"standard_name": "specific_turbulent_kinetic_energy_dissipation_in_sea_water",
|
|
236
|
+
"long_name": "combined TKE dissipation rate",
|
|
237
|
+
},
|
|
238
|
+
"epsilonLnSigma": {
|
|
239
|
+
"nc_name": "epsilonLnSigma",
|
|
240
|
+
"units": "",
|
|
241
|
+
"long_name": "sigma of ln(epsilon)",
|
|
242
|
+
"comment": (
|
|
243
|
+
"Expected single-probe standard deviation of ln(epsilon) from the "
|
|
244
|
+
"Lueck variance model; this is NOT the sigma of the combined mean "
|
|
245
|
+
"(no sqrt(n) reduction is applied). After depth-binning it is the "
|
|
246
|
+
"RMS of the contributing per-window sigmas in the bin, not a spread "
|
|
247
|
+
"recomputed from the binned estimates."
|
|
248
|
+
),
|
|
249
|
+
},
|
|
250
|
+
"FM_1": {
|
|
251
|
+
"nc_name": "FM_1",
|
|
252
|
+
"units": "",
|
|
253
|
+
"long_name": "figure of merit (probe 1)",
|
|
254
|
+
},
|
|
255
|
+
"FM_2": {
|
|
256
|
+
"nc_name": "FM_2",
|
|
257
|
+
"units": "",
|
|
258
|
+
"long_name": "figure of merit (probe 2)",
|
|
259
|
+
},
|
|
260
|
+
# "cpm" (cycles per meter) is deliberately retained as the conventional
|
|
261
|
+
# oceanographic wavenumber unit here and on the K_max/kB vars below: it is
|
|
262
|
+
# not strict UDUNITS (the parseable equivalent is "m-1"), but it is
|
|
263
|
+
# unambiguous and matches the per-profile rsi/chi outputs. Left as-is by
|
|
264
|
+
# decision (#38); only the PSU -> "1" salinity fix was applied.
|
|
265
|
+
"K_max_1": {
|
|
266
|
+
"nc_name": "K_max_1",
|
|
267
|
+
"units": "cpm",
|
|
268
|
+
"long_name": "maximum resolved wavenumber (probe 1)",
|
|
269
|
+
},
|
|
270
|
+
"K_max_2": {
|
|
271
|
+
"nc_name": "K_max_2",
|
|
272
|
+
"units": "cpm",
|
|
273
|
+
"long_name": "maximum resolved wavenumber (probe 2)",
|
|
274
|
+
},
|
|
275
|
+
"speed": {
|
|
276
|
+
"nc_name": "speed",
|
|
277
|
+
"units": "m/s",
|
|
278
|
+
"long_name": "profiling speed",
|
|
279
|
+
},
|
|
280
|
+
"nu": {
|
|
281
|
+
"nc_name": "nu",
|
|
282
|
+
"units": "m2 s-1",
|
|
283
|
+
"long_name": "kinematic viscosity",
|
|
284
|
+
},
|
|
285
|
+
"T_mean": {
|
|
286
|
+
"nc_name": "T_mean",
|
|
287
|
+
"units": "degree_Celsius",
|
|
288
|
+
"long_name": "mean in-situ temperature within FFT window",
|
|
289
|
+
"units_metadata": "temperature: on_scale",
|
|
290
|
+
},
|
|
291
|
+
# Background stratification (Thorpe-sorted). The averaging scale differs by
|
|
292
|
+
# product (a pressure window for the profile product, the processing window
|
|
293
|
+
# for diss); each product NetCDF carries the precise scale in its variable
|
|
294
|
+
# comment, so the schema long_name stays scale-neutral. Profile-only (not on
|
|
295
|
+
# the CTD product, which spans the whole up/down trajectory).
|
|
296
|
+
"N2": {
|
|
297
|
+
"nc_name": "N2",
|
|
298
|
+
"units": "s-2",
|
|
299
|
+
"standard_name": "square_of_brunt_vaisala_frequency_in_sea_water",
|
|
300
|
+
"long_name": "buoyancy frequency squared (Thorpe-sorted)",
|
|
301
|
+
},
|
|
302
|
+
"dTdz": {
|
|
303
|
+
"nc_name": "dTdz",
|
|
304
|
+
"units": "K m-1",
|
|
305
|
+
"long_name": "background temperature gradient (positive down)",
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
CHI_SCHEMA: dict[str, dict] = {
|
|
310
|
+
# Headline combined chi products. Without these entries, binning strips all
|
|
311
|
+
# per-variable attrs and the published combo carries chiMean/chiLnSigma with
|
|
312
|
+
# no units or long_name.
|
|
313
|
+
"chiMean": {
|
|
314
|
+
"nc_name": "chiMean",
|
|
315
|
+
"units": "K2 s-1",
|
|
316
|
+
"long_name": "combined thermal variance dissipation rate",
|
|
317
|
+
},
|
|
318
|
+
"chiLnSigma": {
|
|
319
|
+
"nc_name": "chiLnSigma",
|
|
320
|
+
"units": "",
|
|
321
|
+
"long_name": "sigma of ln(chi)",
|
|
322
|
+
"comment": (
|
|
323
|
+
"Expected single-probe standard deviation of ln(chi); this is NOT "
|
|
324
|
+
"the sigma of the combined mean (no sqrt(n) reduction is applied). "
|
|
325
|
+
"After depth-binning it is the RMS of the contributing per-window "
|
|
326
|
+
"sigmas in the bin, not a spread recomputed from the binned "
|
|
327
|
+
"estimates."
|
|
328
|
+
),
|
|
329
|
+
},
|
|
330
|
+
"epsilon_paired": {
|
|
331
|
+
"nc_name": "epsilon_paired",
|
|
332
|
+
"units": "W/kg",
|
|
333
|
+
"standard_name": "specific_turbulent_kinetic_energy_dissipation_in_sea_water",
|
|
334
|
+
"long_name": "epsilonMean paired onto the chi window grid",
|
|
335
|
+
"comment": (
|
|
336
|
+
"Nearest-window epsilonMean from the matching diss dataset — the "
|
|
337
|
+
"epsilon that entered the per-window Gamma and K_rho. In binned/"
|
|
338
|
+
"combo products this is the bin aggregate of that pairing (see "
|
|
339
|
+
"the per-profile chi product for exact per-window traceability)."
|
|
340
|
+
),
|
|
341
|
+
},
|
|
342
|
+
"chi_1": {
|
|
343
|
+
"nc_name": "chi_1",
|
|
344
|
+
"units": "K2 s-1",
|
|
345
|
+
"long_name": "thermal variance dissipation (probe 1)",
|
|
346
|
+
},
|
|
347
|
+
"chi_2": {
|
|
348
|
+
"nc_name": "chi_2",
|
|
349
|
+
"units": "K2 s-1",
|
|
350
|
+
"long_name": "thermal variance dissipation (probe 2)",
|
|
351
|
+
},
|
|
352
|
+
"kB_1": {
|
|
353
|
+
"nc_name": "kB_1",
|
|
354
|
+
"units": "cpm",
|
|
355
|
+
"long_name": "Batchelor wavenumber (probe 1)",
|
|
356
|
+
},
|
|
357
|
+
"kB_2": {
|
|
358
|
+
"nc_name": "kB_2",
|
|
359
|
+
"units": "cpm",
|
|
360
|
+
"long_name": "Batchelor wavenumber (probe 2)",
|
|
361
|
+
},
|
|
362
|
+
"fom_T_1": {
|
|
363
|
+
"nc_name": "fom_T_1",
|
|
364
|
+
"units": "",
|
|
365
|
+
"long_name": "figure of merit chi (probe 1)",
|
|
366
|
+
},
|
|
367
|
+
"fom_T_2": {
|
|
368
|
+
"nc_name": "fom_T_2",
|
|
369
|
+
"units": "",
|
|
370
|
+
"long_name": "figure of merit chi (probe 2)",
|
|
371
|
+
},
|
|
372
|
+
"K_max_T_1": {
|
|
373
|
+
"nc_name": "K_max_T_1",
|
|
374
|
+
"units": "cpm",
|
|
375
|
+
"long_name": "max wavenumber chi (probe 1)",
|
|
376
|
+
},
|
|
377
|
+
"K_max_T_2": {
|
|
378
|
+
"nc_name": "K_max_T_2",
|
|
379
|
+
"units": "cpm",
|
|
380
|
+
"long_name": "max wavenumber chi (probe 2)",
|
|
381
|
+
},
|
|
382
|
+
# Shared diagnostic vars carried over from the diss/profile pipeline
|
|
383
|
+
"speed": COMBO_SCHEMA["speed"],
|
|
384
|
+
"nu": COMBO_SCHEMA["nu"],
|
|
385
|
+
"T_mean": COMBO_SCHEMA["T_mean"],
|
|
386
|
+
"lat": COMBO_SCHEMA["lat"],
|
|
387
|
+
"lon": COMBO_SCHEMA["lon"],
|
|
388
|
+
"stime": COMBO_SCHEMA["stime"],
|
|
389
|
+
"etime": COMBO_SCHEMA["etime"],
|
|
390
|
+
"depth": COMBO_SCHEMA["depth"],
|
|
391
|
+
"P": COMBO_SCHEMA["P"],
|
|
392
|
+
"bin": COMBO_SCHEMA["bin"],
|
|
393
|
+
"profile": COMBO_SCHEMA["profile"],
|
|
394
|
+
# Stratification + derived mixing quantities on the chi grid.
|
|
395
|
+
"N2": COMBO_SCHEMA["N2"],
|
|
396
|
+
"dTdz": COMBO_SCHEMA["dTdz"],
|
|
397
|
+
"K_T": {
|
|
398
|
+
"nc_name": "K_T",
|
|
399
|
+
"units": "m2 s-1",
|
|
400
|
+
"long_name": "Osborn-Cox eddy diffusivity of heat",
|
|
401
|
+
},
|
|
402
|
+
"Gamma": {
|
|
403
|
+
"nc_name": "Gamma",
|
|
404
|
+
"units": "1",
|
|
405
|
+
"long_name": "mixing coefficient (measured)",
|
|
406
|
+
},
|
|
407
|
+
"K_rho": {
|
|
408
|
+
"nc_name": "K_rho",
|
|
409
|
+
"units": "m2 s-1",
|
|
410
|
+
"long_name": "Osborn diapycnal diffusivity (Gamma_0 = 0.2)",
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
CTD_SCHEMA: dict[str, dict] = {
|
|
415
|
+
"time": {
|
|
416
|
+
"nc_name": "time",
|
|
417
|
+
"units": "seconds since 1970-01-01",
|
|
418
|
+
"standard_name": "time",
|
|
419
|
+
"long_name": "time bin center",
|
|
420
|
+
},
|
|
421
|
+
"JAC_T": COMBO_SCHEMA["JAC_T"],
|
|
422
|
+
"JAC_C": COMBO_SCHEMA["JAC_C"],
|
|
423
|
+
"P": COMBO_SCHEMA["P"],
|
|
424
|
+
"depth": COMBO_SCHEMA["depth"],
|
|
425
|
+
"SP": COMBO_SCHEMA["SP"],
|
|
426
|
+
"SA": COMBO_SCHEMA["SA"],
|
|
427
|
+
"CT": COMBO_SCHEMA["CT"],
|
|
428
|
+
"sigma0": COMBO_SCHEMA["sigma0"],
|
|
429
|
+
"rho": COMBO_SCHEMA["rho"],
|
|
430
|
+
"lat": COMBO_SCHEMA["lat"],
|
|
431
|
+
"lon": COMBO_SCHEMA["lon"],
|
|
432
|
+
"DO": COMBO_SCHEMA["DO"],
|
|
433
|
+
"Chlorophyll": COMBO_SCHEMA["Chlorophyll"],
|
|
434
|
+
"Turbidity": COMBO_SCHEMA["Turbidity"],
|
|
435
|
+
# N2/dTdz are profile-only (down-cast) quantities and are intentionally not
|
|
436
|
+
# carried on the CTD product; they live on the profile/diss/chi schemas.
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
# Fixed global attributes for CF-1.13 / ACDD-1.3 compliance
|
|
440
|
+
GLOBAL_ATTRS: dict[str, str] = {
|
|
441
|
+
"Conventions": "CF-1.13, ACDD-1.3",
|
|
442
|
+
"standard_name_vocabulary": "CF Standard Name Table v89",
|
|
443
|
+
"featureType": "profile",
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
_COVERAGE_CONTENT_TYPE: dict[str, str] = {
|
|
448
|
+
# Geometric / time coordinate variables
|
|
449
|
+
"bin": "coordinate",
|
|
450
|
+
"depth": "coordinate",
|
|
451
|
+
"profile": "coordinate",
|
|
452
|
+
"time": "coordinate",
|
|
453
|
+
"stime": "coordinate",
|
|
454
|
+
"etime": "coordinate",
|
|
455
|
+
"lat": "coordinate",
|
|
456
|
+
"lon": "coordinate",
|
|
457
|
+
# Auxiliary diagnostics — CF/ACDD distinguishes these from the actual
|
|
458
|
+
# measured field. ACDD §coverage_content_type calls this
|
|
459
|
+
# ``auxiliaryInformation``.
|
|
460
|
+
"FM_1": "auxiliaryInformation",
|
|
461
|
+
"FM_2": "auxiliaryInformation",
|
|
462
|
+
"fom_T_1": "auxiliaryInformation",
|
|
463
|
+
"fom_T_2": "auxiliaryInformation",
|
|
464
|
+
"K_max_1": "auxiliaryInformation",
|
|
465
|
+
"K_max_2": "auxiliaryInformation",
|
|
466
|
+
"K_max_T_1": "auxiliaryInformation",
|
|
467
|
+
"K_max_T_2": "auxiliaryInformation",
|
|
468
|
+
"epsilonLnSigma": "auxiliaryInformation",
|
|
469
|
+
"n_samples": "auxiliaryInformation",
|
|
470
|
+
"Gnd": "auxiliaryInformation",
|
|
471
|
+
"PV": "auxiliaryInformation",
|
|
472
|
+
"P_dP": "auxiliaryInformation",
|
|
473
|
+
"V_Bat": "auxiliaryInformation",
|
|
474
|
+
"Incl_X": "auxiliaryInformation",
|
|
475
|
+
"Incl_Y": "auxiliaryInformation",
|
|
476
|
+
"Incl_T": "auxiliaryInformation",
|
|
477
|
+
"DO_T": "auxiliaryInformation",
|
|
478
|
+
"speed": "auxiliaryInformation",
|
|
479
|
+
"W_slow": "auxiliaryInformation",
|
|
480
|
+
"nu": "auxiliaryInformation",
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def coverage_content_type_for(var_name: str) -> str:
|
|
485
|
+
"""Return ACDD ``coverage_content_type`` for *var_name*.
|
|
486
|
+
|
|
487
|
+
Defaults to ``physicalMeasurement`` for any variable not explicitly
|
|
488
|
+
classified — that is the right call for T, S, P, epsilon, chi, density,
|
|
489
|
+
DO, etc. Coordinate vars and pure diagnostics get their own values.
|
|
490
|
+
"""
|
|
491
|
+
return _COVERAGE_CONTENT_TYPE.get(var_name, "physicalMeasurement")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def apply_schema(ds: xr.Dataset, schema: dict[str, dict]) -> xr.Dataset:
|
|
495
|
+
"""Apply CF attributes from *schema* to variables in *ds*.
|
|
496
|
+
|
|
497
|
+
For each variable in *ds* that has a matching entry in *schema*,
|
|
498
|
+
the schema's metadata (units, standard_name, long_name, etc.) is
|
|
499
|
+
applied as variable attributes. Every variable also receives an ACDD
|
|
500
|
+
``coverage_content_type`` (auxiliary diagnostics, coordinates, and
|
|
501
|
+
physical measurements all get distinct types).
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
ds : xr.Dataset
|
|
506
|
+
schema : dict mapping variable names to attribute dicts.
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
xr.Dataset with updated attributes.
|
|
511
|
+
"""
|
|
512
|
+
ds = ds.copy()
|
|
513
|
+
for vname in [str(v) for v in ds.data_vars] + [str(c) for c in ds.coords]:
|
|
514
|
+
if vname in schema:
|
|
515
|
+
attrs = schema[vname]
|
|
516
|
+
for key, val in attrs.items():
|
|
517
|
+
if key != "nc_name":
|
|
518
|
+
ds[vname].attrs[key] = val
|
|
519
|
+
ds[vname].attrs.setdefault("coverage_content_type", coverage_content_type_for(vname))
|
|
520
|
+
return ds
|