Skip to content

Commit 7b1106a

Browse files
authored
Merge pull request #142 from asmith75218/post_i09_update
Pre-P04 update
2 parents 62cc02d + f84dbc8 commit 7b1106a

36 files changed

Lines changed: 4187 additions & 734 deletions

ctdcal/cfg.yaml

Lines changed: 73 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Example user configuration file
22
#
3-
# To use, call ctdcal.common.load_user_config(<this file>)
3+
# To use, call ctdcal.common.load_user_config(this_file)
44

55
# Application Settings
66
# --------------------
@@ -25,119 +25,82 @@ bottleflags_man: bottle_flags_manual.json
2525
# of different types, it is recommended to use a different
2626
# configuration file (this file) for each.
2727

28-
# Sample frequency (Hz): int
29-
freq: 24
28+
inst_params:
29+
# Sample frequency (Hz): int
30+
freq: 24
31+
# Number of water sampler bottles: int
32+
max_bottles: 36
33+
34+
# Fitting Settings
35+
# ----------------
36+
37+
# Fit groups
38+
# To define a new group for a parameter, enter a new line with
39+
# the starting cast id (cast name) for the group.
40+
fit_groups:
41+
pressure:
42+
- '00101'
43+
conductivity:
44+
- '00101'
45+
temperature:
46+
- '00101'
47+
rinko:
48+
- '00101'
49+
50+
# Fit coeffs
51+
# ctdcal will use these values during calibration/fitting stages
52+
# for primary and secondary channel sensors, based on the fit
53+
# groups defined above. When additional fit groups are defined,
54+
# add corresponding lines here for prim_n and secn_n.
55+
fit_coeffs:
56+
temperature:
57+
primary:
58+
- primary_0: {P_order: 1, T_order: 0, zRange: "1000:6000"}
59+
# - primary_n: {P_order: 1, T_order: 0, zRange: "1000:6000"}
60+
secondary:
61+
- secondary_0: {P_order: 1, T_order: 0, zRange: "1000:6000"}
62+
# - secondary_n: {P_order: 1, T_order: 0, zRange: "1000:6000"}
63+
conductivity:
64+
primary:
65+
- primary_0: {P_order: 1, T_order: 0, C_order: 0, zRange: "1000:6000"}
66+
# - primary_1: {P_order: 1, T_order: 0, C_order: 0, zRange: "1000:6000"}
67+
secondary:
68+
- secondary_0: {P_order: 1, T_order: 0, C_order: 0, zRange: "1000:6000"}
69+
# - secondary_1: {P_order: 1, T_order: 0, C_order: 0, zRange: "1000:6000"}
3070

3171
# Advanced Settings
3272
# -----------------
3373
# These settings are provided for fine-tuning of specific CTDCAL
3474
# features. These should not normally need to change.
3575

36-
# Conductivity threshold (in cast units): int or float
37-
# Used to determine if the instrument is operating in the water
38-
cond_threshold: 20
39-
# Soak threshold (in pressure units): int or float
40-
# The deepest depth at which the pre-cast soak may occur
41-
soak_threshold: 20
42-
# Soak rolling average window (seconds): int
43-
soak_win: 20
44-
# Filter window (seconds): int
45-
# Used by the cast smoothing filter
46-
filter_win: 2
47-
# Filter type: boxcar, hann, triangle
48-
filter_type: hann
49-
# Columns to filter:
50-
filter_cols:
51-
- CTDPRS
52-
- CTDTMP1
53-
- CTDTMP2
54-
- CTDCOND1
55-
- CTDCOND2
56-
- CTDSAL
57-
- U_DEF_poly1
58-
# - FREE1
59-
- CTDOXYVOLTS
60-
# - REFTMP
61-
# - GPSLAT
62-
# - GPSLON
63-
64-
# Legacy Settings
65-
# ---------------
66-
# Included for transitional compatibility with legacy configs.
67-
# Any NEW CODE that uses these is not guaranteed to work in the future!
68-
# TODO: move, eliminate or redefine these as appropriate
69-
70-
expocode: ""
71-
section_id: ""
72-
ctd_serial: ""
73-
74-
# Exchange-format export cols:
75-
ctd_col_names:
76-
- CTDPRS
77-
- CTDTMP1
78-
- CTDTMP1_FLAG_W
79-
- CTDSAL1
80-
- CTDSAL1_FLAG_W
81-
- CTDOXY1
82-
- CTDOXY1_FLAG_W
83-
# - CTDRINKO
84-
# - CTDRINKO_FLAG_W
85-
- CTDXMISS1
86-
- CTDXMISS1_FLAG_W
87-
- CTDFLUOR1
88-
- CTDFLUOR1_FLAG_W
89-
90-
ctd_col_units:
91-
- DBAR
92-
- ITS-90
93-
- ""
94-
- PSS-78
95-
- ""
96-
- UMOL/KG
97-
- ""
98-
# - UMOL/KG
99-
# - ""
100-
- 0-5VDC
101-
- ""
102-
- 0-5VDC
103-
- ""
104-
105-
# Legacy data directory structure, incompatible with user-configured datadir
106-
# above. Use with caution.
107-
dirs:
108-
ssscc: "data/ssscc/"
109-
raw: "data/raw/"
110-
converted: "data/converted/"
111-
time: "data/time/"
112-
pressure: "data/pressure/"
113-
bottle: "data/bottle/"
114-
salt: "data/salt/"
115-
reft: "data/reft/"
116-
oxygen: "data/oxygen/"
117-
logs: "data/logs/"
118-
flags: "data/flags/"
119-
120-
fig_dirs:
121-
t1: "data/logs/fitting_figs/temp_primary/"
122-
t2: "data/logs/fitting_figs/temp_secondary/"
123-
c1: "data/logs/fitting_figs/cond_primary/"
124-
c2: "data/logs/fitting_figs/cond_secondary/"
125-
ox: "data/logs/fitting_figs/oxy_primary/"
126-
rinko: "data/logs/fitting_figs/oxy_rinko/"
76+
# Soak parameters - used by ctd soak detection algorithm
77+
soak_det_params:
78+
# Conductivity threshold (in cast units): int or float
79+
# Used to determine if the instrument is operating in the water
80+
cond_threshold: 20
81+
# Soak threshold (in pressure units): int or float
82+
# The deepest depth at which the pre-cast soak may occur
83+
soak_threshold: 20
84+
# Soak rolling average window (seconds): int
85+
soak_win: 20
12786

128-
# Legacy column name lookup
129-
column:
130-
p: "CTDPRS"
131-
t1: "CTDTMP1"
132-
t2: "CTDTMP2"
133-
c1: "CTDCOND1"
134-
c2: "CTDCOND2"
135-
sal: "CTDSAL"
136-
rinko_oxy: "U_DEF_poly1"
137-
oxyvolts: "CTDOXYVOLTS"
138-
refT: "REFTMP"
139-
refC: "BTLCOND"
140-
refS: "SALNTY"
141-
refO: "OXYGEN"
142-
lat: "GPSLAT"
143-
lon: "GPSLON"
87+
# Filter parameters - used by the cast smoothing filter
88+
filter_params:
89+
# Filter window (seconds): int
90+
filter_win: 2
91+
# Filter type: boxcar, hann, triangle
92+
filter_type: hann
93+
# Columns to filter:
94+
filter_cols:
95+
- CTDPRS
96+
- CTDTMP1
97+
- CTDTMP2
98+
- CTDCOND1
99+
- CTDCOND2
100+
- CTDSAL
101+
- U_DEF_poly1
102+
# - FREE1
103+
- CTDOXYVOLTS
104+
# - REFTMP
105+
# - GPSLAT
106+
# - GPSLON

ctdcal/common.py

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
cfg = get_ctdcal_config()
2121

2222

23+
# Constants
24+
# ---------
25+
26+
2327
# Function definitions
2428
# --------------------
2529

@@ -38,6 +42,7 @@ def load_user_config(cfgfile):
3842
-------
3943
Munch object
4044
"""
45+
cfgfile = validate_file(cfgfile)
4146
with open(cfgfile, 'r') as f:
4247
cfg = yaml.safe_load(f)
4348
return munchify(cfg)
@@ -202,6 +207,22 @@ def load_exchange_btl(btl_file: Union[str, Path]) -> pd.DataFrame:
202207
)
203208

204209

210+
# File exports
211+
def list_to_file(fname, outdir, lst):
212+
"""
213+
Writes a list to a file, one list item to a line.
214+
215+
Parameters
216+
----------
217+
fname : str or Path-like
218+
outdir : str or Path-like
219+
lst : iterable
220+
"""
221+
outdir = validate_dir(outdir)
222+
outfile = Path(outdir, fname)
223+
with open(outfile, 'w') as f:
224+
f.write('\n'.join(lst))
225+
205226
def load_exchange_ctd(
206227
ctd_file: Union[str, Path, BufferedIOBase],
207228
n_files=None,
@@ -300,17 +321,74 @@ def load_exchange_ctd(
300321
)
301322

302323

303-
## Cast list manipulation
304-
def make_ssscc_list(fname="data/ssscc.csv"):
324+
## Cast list functions
325+
def make_cast_id_list(rawdir, outdir=None, pattern='*.hex', fname='cast_id_list.csv'):
305326
"""
306327
Attempt to automatically generate list of station/casts from raw files.
307328
"""
308-
raw_files = Path(cfg.dirs["raw"]).glob("*.hex")
309-
ssscc_list = sorted([f.stem for f in raw_files])
310-
pd.Series(ssscc_list, dtype=str).to_csv(fname, header=None, index=False, mode="x")
329+
search_dir = validate_dir(Path(rawdir))
330+
raw_files = Path(search_dir).glob(pattern)
331+
cast_id_list = sorted([f.stem for f in raw_files])
332+
if len(cast_id_list) < 1:
333+
raise FileNotFoundError('No raw data files found.')
334+
# pd.Series(casts, dtype=str).to_csv(fname, header=None, index=False, mode="x")
335+
if outdir is not None:
336+
outdir = validate_dir(outdir, create=True)
337+
list_to_file(fname, outdir, cast_id_list)
338+
return cast_id_list
339+
340+
341+
def load_fit_groups(casts, fit_groups):
342+
fit_groups_all = dict()
343+
for name, fit_group in fit_groups.items():
344+
for value in fit_group:
345+
ct = casts.count(value)
346+
if ct != 1:
347+
raise ValueError("Invalid fit group definitions: %s must occur only once, but %s occurrances were found."
348+
% (value, ct))
349+
group_casts = []
350+
for starting_cast in fit_group:
351+
start_idx = casts.index(starting_cast)
352+
if starting_cast == fit_group[-1]:
353+
end_idx = None
354+
else:
355+
next_cast = fit_group[fit_group.index(starting_cast) + 1]
356+
end_idx = casts.index(next_cast)
357+
group_casts.append([cast for cast in casts[start_idx: end_idx]])
358+
fit_groups_all[name] = group_casts
359+
return munchify(fit_groups_all)
360+
361+
def get_cast_id_list(fname, rawdir, outdir, auto_generate=True):
362+
"""
363+
Loads a cast list from a file. If the file is not found, the cast list will be
364+
auto-generated from a directory of raw files when auto_generate is True (default
365+
behavior).
311366
312-
return ssscc_list
367+
Parameters
368+
----------
369+
fname
370+
auto_generate
371+
372+
Returns
373+
-------
374+
375+
"""
376+
fname = Path(outdir, fname)
377+
try:
378+
# test if file exists
379+
fname = validate_file(fname)
380+
except FileNotFoundError:
381+
# auto-generate or die
382+
if auto_generate is True:
383+
make_cast_id_list(rawdir, outdir, fname=fname)
384+
else:
385+
raise
313386

387+
# file exists or has been auto-generated
388+
with open(fname, "r") as f:
389+
# skip comment lines
390+
id_list = [line.strip() for line in f.readlines() if not line.startswith('#')]
391+
return id_list
314392

315393
def get_ssscc_list(fname="data/ssscc.csv"):
316394
"""
@@ -327,6 +405,7 @@ def get_ssscc_list(fname="data/ssscc.csv"):
327405
list
328406
Cast names or identifiers, as a list of strings.
329407
"""
408+
log.warning("Use of get_ssscc_list() is deprecated. Use get_cast_id_list() instead.")
330409
ssscc_list = []
331410
with open(fname, "r") as lines:
332411
for line in lines:

ctdcal/fitting/fit_common.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,28 @@ class NodeNotFoundError(Exception):
7070

7171
# BottleFlag wrangling
7272
# TODO: Move to ctdcal.flagging.common after reorg
73+
def bf_from_json(fname):
74+
"""
75+
Reads a JSON file and returns the contents as a BottleFlags object.
76+
77+
Parameters
78+
----------
79+
fname : str or Path-like
80+
81+
Returns
82+
-------
83+
BottleFlags object
84+
"""
85+
with open(fname, 'r') as f:
86+
buf = f.read()
87+
if buf == '':
88+
# File exists but is empty
89+
flags = BottleFlags()
90+
else:
91+
flags = BottleFlags.fromJSON(buf)
92+
return flags
93+
94+
7395
def df_node_to_BottleFlags(df):
7496
"""
7597
Convert a flag node from a DataFrame to a formatted BottleFlags object
@@ -129,13 +151,7 @@ def save_node(fname, node, label, create_new=False):
129151
If true, create a new node if it does not exist. Default is false.
130152
131153
"""
132-
with open(fname, 'r') as f:
133-
buf = f.read()
134-
if buf == '':
135-
# File exists but is empty
136-
flags = BottleFlags()
137-
else:
138-
flags = BottleFlags.fromJSON(buf)
154+
flags = bf_from_json(fname)
139155
if label in flags or create_new is True:
140156
flags[label] = node
141157
else:

0 commit comments

Comments
 (0)