"""`TDR` objects homologous to `R` package diveMove's main classes
"""
import logging
import numpy as np
import pandas as pd
from skdiveMove.tdrphases import TDRPhases
import skdiveMove.plotting as plotting
import skdiveMove.calibspeed as speedcal
from skdiveMove.helpers import (get_var_sampling_interval,
_get_dive_indices, _append_xr_attr,
_one_dive_stats, _speed_stats)
import skdiveMove.calibconfig as calibconfig
import xarray as xr
logger = logging.getLogger(__name__)
# Add the null handler if importing as library; whatever using this library
# should set up logging.basicConfig() as needed
logger.addHandler(logging.NullHandler())
# Keep attributes in xarray operations
xr.set_options(keep_attrs=True)
[docs]
class TDR(TDRPhases):
"""Base class encapsulating TDR objects and processing
Comprehensive TDR processing methods.
Attributes
----------
speed_calib_fit : quantreg model fit
Model object fit by quantile regression for speed calibration.
Notes
-----
See ``help(TDR)`` for inherited attributes. This class extends
:class:`TDRPhases`.
See Also
--------
xarray.Dataset
Examples
--------
Construct an instance from diveMove example dataset
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd()
Plot the `TDR` object
>>> tdrX.plot() # doctest: +ELLIPSIS
(<Figure ... 1 Axes>, <Axes: ...>)
"""
def __init__(self, *args, **kwargs):
"""Set up attributes for TDR objects
Parameters
----------
*args : positional arguments
Passed to :meth:`TDRPhases.__init__`
**kwargs
Optional keyword arguments passed to
:meth:`TDRPhases.__init__`.
"""
TDRPhases.__init__(self, *args, **kwargs)
# Speed calibration fit
self.speed_calib_fit = None
def __str__(self):
base = TDRPhases.__str__(self)
speed_fmt_pref = "Speed calibration coefficients:"
if self.speed_calib_fit is not None:
speed_ccoef_a, speed_ccoef_b = self.speed_calib_fit.params
speed_coefs_fmt = ("\n{0:<20} (a={1:.4f}, b={2:.4f})"
.format(speed_fmt_pref,
speed_ccoef_a, speed_ccoef_b))
else:
speed_ccoef_a, speed_ccoef_b = (None, None)
speed_coefs_fmt = ("\n{0:<20} (a=None, b=None)"
.format(speed_fmt_pref))
return base + speed_coefs_fmt
[docs]
def calibrate_speed(self, tau=0.1, contour_level=0.1, z=0, bad=[0, 0],
**kwargs):
"""Calibrate speed measurements
Set the `speed_calib_fit` attribute. This method calibrates speed
readings following the procedure outlined in Blackwell et.
al. (1999) [1]_.
Parameters
----------
tau : float, optional
Quantile on which to regress speed on rate of depth change.
contour_level : float, optional
The mesh obtained from the bivariate kernel density estimation
corresponding to this contour will be used for the quantile
regression to define the calibration line.
z : float, optional
Only changes in depth larger than this value will be used for
calibration.
bad : array_like, optional
Two-element `array_like` indicating that only rates of depth
change and speed greater than the given values should be used
for calibration, respectively.
**kwargs
Optional keyword arguments listed below.
plot : bool, default True
Whether to plot calibration results.
ax : matplotlib.Axes, optional
A :class:`~matplotlib.axes.Axes` instance to use as target.
Default is to create one.
References
----------
.. [1] Blackwell S, Haverl C, Le Boeuf B, Costa D (1999). A method
for calibrating swim-speed recorders. Marine Mammal Science
15(3):894-905.
Examples
--------
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd(has_speed=True)
>>> tdrX.zoc("offset", offset=3)
>>> tdrX.calibrate_speed(z=2)
"""
depth = self.get_depth("zoc").to_series()
ddiffs = depth.reset_index().diff().set_index(depth.index)
ddepth = ddiffs["depth"].abs()
rddepth = ddepth / ddiffs[depth.index.name].dt.total_seconds()
curspeed = self.get_speed("measured").to_series()
ok = (ddepth > z) & (rddepth > bad[0]) & (curspeed > bad[1])
rddepth = rddepth[ok]
curspeed = curspeed[ok]
kde_data = pd.concat((rddepth.rename("depth_rate"),
curspeed), axis=1)
qfit, ax = speedcal.calibrate_speed(kde_data, tau=tau,
contour_level=contour_level,
z=z, bad=bad, **kwargs)
self.speed_calib_fit = qfit
logger.info("Finished calibrating speed")
[docs]
def dive_stats(self, depth_deriv=True):
"""Calculate dive statistics in `TDR` records
Parameters
----------
depth_deriv : bool, optional
Whether to compute depth derivative statistics.
Returns
-------
pandas.DataFrame
DataFrame indexed by dive number, having the following columns:
- ``begdesc``: time stamp for the start of each dive;
- ``enddesc``: time stamp for the descent's end;
- ``begasc``: time stamp for the beginning of the ascent;
- ``desctime``: duration of descent;
- ``botttim``: duration of the bottom phase;
- ``asctim``: duration of ascent;
- ``divetim``: dive duration;
- ``descdist``: last descent depth;
- ``bottdist``: sum of the absolute depth differences while
at the bottom of the dive; a measure of the amount of
"wiggling" while at the bottom;
- ``ascdist``: first ascent depth;
- ``bottdep_mean``: mean bottom depth;
- ``bottdep_median``: median bottom depth;
- ``bottdep_sd``: standard deviation of bottom depths;
- ``maxdep``: maximum depth;
- ``desc_tdist``: descent total distance, estimated from speed;
- ``desc_mean_speed``: descent mean speed;
- ``desc_angle``: descent mean speed;
- ``bott_tdist``: total distance at the bottom, estimated
from speed;
- ``bott_mean_speed``: mean speed at the bottom;
- ``asc_tdist``: ascent total distance, estimated from speed;
- ``asc_mean_speed``: ascent mean speed;
- ``asc_angle``: ascent angle, relative to bottom plane;
- ``postdive_dur``: postdive duration;
- ``postdive_tdist``: postdive total distance, estimated from
speed;
- ``postdive_mean_speed``: postdive mean speed.
If ``depth_deriv=True`` (default), 21 additional columns with
the minimum, first quartile, median, mean, third quartile,
maximum, and standard deviation of the depth derivative for
each phase of the dive are included.
Notes
-----
This method is homologous to diveMove's `diveStats` function.
Examples
--------
ZOC using the "filter" method
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd(has_speed=True)
>>> # Window lengths and probabilities
>>> DB = [-2, 5]
>>> K = [3, 5760]
>>> P = [0.5, 0.02]
>>> tdrX.zoc("offset", offset=3)
>>> tdrX.detect_wet()
>>> tdrX.detect_dives(3)
>>> tdrX.detect_dive_phases("unimodal", descent_crit_q=0.01,
... ascent_crit_q=0, knot_factor=20)
>>> tdrX.dive_stats() # doctest: +ELLIPSIS
begdesc ... postdive_mean_speed
1 2002-01-05 ... 1.398859
2 ...
"""
phases_df = self.get_dives_details("row_ids")
idx_name = phases_df.index.name
# calib_speed=False if no fit object
if self.has_speed:
tdr = (self.get_tdr(calib_depth=True,
calib_speed=bool(self.speed_calib_fit))
[[self.depth_name, self.speed_name]])
else:
tdr = (self.get_tdr(calib_depth=True,
calib_speed=bool(self.speed_calib_fit))
[[self.depth_name]])
intvl = (get_var_sampling_interval(tdr[self.depth_name])
.total_seconds())
tdr = tdr.to_dataframe()
dive_ids = phases_df.loc[:, "dive_id"]
postdive_ids = phases_df.loc[:, "postdive_id"]
ok = (dive_ids > 0) & dive_ids.isin(postdive_ids)
okpd = (postdive_ids > 0) & postdive_ids.isin(dive_ids)
postdive_ids = postdive_ids[okpd]
postdive_dur = (postdive_ids.reset_index()
.groupby("postdive_id")
.apply(lambda x: x.iloc[-1] - x.iloc[0],
include_groups=False))
# Enforce UTC, as otherwise rpy2 uses our locale in the output of
# OneDiveStats
tdrf = (pd.concat((phases_df[["dive_id", "dive_phase"]][ok],
tdr.loc[ok.index[ok]]), axis=1)
.tz_localize("UTC").reset_index())
# Ugly hack to re-order columns for `diveMove` convention
names0 = ["dive_id", "dive_phase", idx_name, self.depth_name]
colnames = tdrf.columns.to_list()
if self.has_speed:
names0.append(self.speed_name)
colnames = names0 + list(set(colnames) - set(names0))
tdrf = tdrf.reindex(columns=colnames)
tdrf_grp = tdrf.groupby("dive_id")
ones_list = []
for name, grp in tdrf_grp:
res = _one_dive_stats(grp.loc[:, names0], interval=intvl,
has_speed=self.has_speed)
# Rename to match dive number
res = res.rename({0: name})
if depth_deriv:
deriv_stats = self._get_dive_deriv_stats(name)
res = pd.concat((res, deriv_stats), axis=1)
ones_list.append(res)
ones_df = pd.concat(ones_list, ignore_index=True)
ones_df.set_index(dive_ids[ok].unique(), inplace=True)
ones_df.index.rename("dive_id", inplace=True)
ones_df["postdive_dur"] = postdive_dur[idx_name]
# For postdive total distance and mean speed (if available)
if self.has_speed:
speed_postd = (tdr[self.speed_name][okpd]
.groupby(postdive_ids))
pd_speed_ll = []
for name, grp in speed_postd:
res = _speed_stats(grp.reset_index())
onames = ["postdive_tdist", "postdive_mean_speed"]
res_df = pd.DataFrame(res[:, :-1], columns=onames,
index=[name])
pd_speed_ll.append(res_df)
pd_speed_stats = pd.concat(pd_speed_ll)
ones_df = pd.concat((ones_df, pd_speed_stats), axis=1)
return ones_df
[docs]
def plot(self, concur_vars=None, concur_var_titles=None, **kwargs):
"""Plot `TDR` object
Parameters
----------
concur_vars : str or list, optional
String or list of strings with names of columns in input to
select additional data to plot.
concur_var_titles : str or list, optional
String or list of strings with y-axis labels for `concur_vars`.
**kwargs
Optional keyword arguments passed to plotting function,
including those listed below. Additional arguments are passed
to underlying plotting method.
xlim : tuple of (float, float)
Minimum and maximum limits for ``x`` axis. Ignored when
``concur_vars=None``.
ylim : tuple of (float, float)
Minimum and maximum limits for ``y`` axis for data other than
depth.
depth_lim : array_like of (float, float)
Minimum and maximum limits for depth to plot.
xlab : str
Label for ``x`` axis.
ylab_depth : str
Label for ``y`` axis for depth.
xlab_format : str
Format string for formatting the ``x`` axis.
sunrise_time : str
Time of sunrise, in 24 hr format. This is used for shading night
time.
sunset_time : str
Time of sunset, in 24 hr format. This is used for shading night
time.
night_col : str
Color for shading night time.
dry_time : pandas.DataFrame
Two-column DataFrame with beginning and ending times corresponding
to periods considered to be dry.
phase_cat : pandas.Series
Categorical series dividing rows into sections.
Returns
-------
tuple
:class:`~matplotlib.figure.Figure`,
:class:`~matplotlib.axes.Axes` instances.
Examples
--------
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd()
>>> tdrX.plot(xlim=["2002-01-05 21:00:00", "2002-01-06 04:10:00"],
... depth_lim=[95, -1]) # doctest: +ELLIPSIS
(<Figure ... with 1 Axes>, <Axes: ...'>)
"""
try:
depth = self.get_depth("zoc")
except LookupError:
depth = self.get_depth("measured")
if "ylab_depth" not in kwargs:
ylab_depth = ("{0} [{1}]"
.format(depth.attrs["full_name"],
depth.attrs["units"]))
kwargs.update(ylab_depth=ylab_depth)
depth = depth.to_series()
if concur_vars is None:
fig, ax = plotting.plot_tdr(depth, **kwargs)
elif concur_var_titles is None:
ccvars = self.tdr[concur_vars].to_dataframe()
fig, ax = plotting.plot_tdr(depth, concur_vars=ccvars, **kwargs)
else:
ccvars = self.tdr[concur_vars].to_dataframe()
ccvars_title = concur_var_titles # just to shorten
fig, ax = plotting.plot_tdr(depth,
concur_vars=ccvars,
concur_var_titles=ccvars_title,
**kwargs)
return (fig, ax)
[docs]
def plot_zoc(self, xlim=None, ylim=None, **kwargs):
"""Plot zero-offset correction filters
Compare the zero-offset corrected depth with the uncorrected
signal, or the progress attained in each of the filters during
recursive filtering for zero-offset correction, as illustrated in
Luque and Fried (2011) [3]_.
Parameters
----------
xlim, ylim : array_like, optional
array_like with minimum and maximum limits for ``x``- and
``y``-axis, respectively.
**kwargs
Optional keyword arguments passed to
:func:`~matplotlib.pyplot.subplots`.
Returns
-------
tuple
:class:`~matplotlib.figure.Figure`,
:class:`~matplotlib.axes.Axes` instances.
See Also
--------
TDR.zoc
Examples
--------
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd()
>>> # Window lengths and probabilities
>>> DB = [-2, 5]
>>> K = [3, 5760]
>>> P = [0.5, 0.02]
>>> tdrX.zoc("filter", k=K, probs=P, depth_bounds=DB)
>>> tdrX.detect_wet()
>>> tdrX.detect_dives(3)
>>> tdrX.detect_dive_phases("unimodal", descent_crit_q=0.01,
... ascent_crit_q=0, knot_factor=20)
>>> tdrX.plot_zoc() # doctest: +ELLIPSIS
(<Figure ... with 3 Axes>, array([<Axes: ...'>,
<Axes: ...'>, <Axes: ...>], dtype=object))
"""
zoc_method = self.zoc_method
depth_msrd = self.get_depth("measured")
ylab = ("{0} [{1}]"
.format(depth_msrd.attrs["full_name"],
depth_msrd.attrs["units"]))
if zoc_method == "filter":
zoc_filters = self.zoc_filters
depth = depth_msrd.to_series()
if "ylab" not in kwargs:
kwargs.update(ylab=ylab)
fig, ax = (plotting
._plot_zoc_filters(depth, zoc_filters, xlim, ylim,
**kwargs))
elif zoc_method == "offset":
depth_msrd = depth_msrd.to_series()
depth_zoc = self.get_depth("zoc").to_series()
fig, ax = plotting.plt.subplots(1, 1, **kwargs)
ax = depth_msrd.plot(ax=ax, rot=0, label="measured")
depth_zoc.plot(ax=ax, label="zoc")
ax.axhline(0, linestyle="--", linewidth=0.75, color="k")
ax.set_xlabel("")
ax.set_ylabel(ylab)
ax.legend(loc="lower right")
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.invert_yaxis()
return (fig, ax)
[docs]
def plot_phases(self, diveNo=None, concur_vars=None,
concur_var_titles=None, surface=False, **kwargs):
"""Plot major phases found on `TDR` object
Parameters
----------
diveNo : array_like, optional
List of dive numbers (1-based) to plot.
concur_vars : str or list, optional
String or list of strings with names of columns in input to
select additional data to plot.
concur_var_titles : str or list, optional
String or list of strings with y-axis labels for `concur_vars`.
surface : bool, optional
Whether to plot surface readings.
**kwargs
Optional keyword arguments passed to plotting function (see
:meth:`plot`)
Returns
-------
tuple
:class:`~matplotlib.figure.Figure`,
:class:`~matplotlib.axes.Axes` instances.
See Also
--------
TDR.detect_wet
Examples
--------
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd()
>>> tdrX.zoc("offset", offset=3)
>>> tdrX.detect_wet()
>>> tdrX.detect_dives(3)
>>> tdrX.detect_dive_phases("unimodal", descent_crit_q=0.01,
... ascent_crit_q=0, knot_factor=20)
>>> tdrX.plot_phases(list(range(250, 300)),
... surface=True) # doctest: +ELLIPSIS
(<Figure ... with 1 Axes>, <Axes: ...>)
"""
row_ids = self.get_dives_details("row_ids")
dive_ids = row_ids["dive_id"]
dive_ids_uniq = dive_ids.unique()
postdive_ids = row_ids["postdive_id"]
if diveNo is None:
diveNo = np.arange(1, row_ids["dive_id"].max() + 1).tolist()
else:
diveNo = [x for x in sorted(diveNo) if x in dive_ids_uniq]
depth_all = self.get_depth("zoc").to_dataframe() # DataFrame
if concur_vars is None:
dives_all = depth_all
else:
concur_df = self.tdr.to_dataframe().loc[:, concur_vars]
dives_all = pd.concat((depth_all, concur_df), axis=1)
isin_dive_ids = dive_ids.isin(diveNo)
isin_postdive_ids = postdive_ids.isin(diveNo)
if surface:
isin = isin_dive_ids | isin_postdive_ids
dives_in = dives_all[isin]
sfce0_idx = (postdive_ids[postdive_ids == diveNo[0] - 1]
.last_valid_index())
dives_df = pd.concat((dives_all.loc[[sfce0_idx]], dives_in),
axis=0)
details_df = pd.concat((row_ids.loc[[sfce0_idx]], row_ids[isin]),
axis=0)
else:
idx_ok = _get_dive_indices(dive_ids, diveNo)
dives_df = dives_all.iloc[idx_ok, :]
details_df = row_ids.iloc[idx_ok, :]
wet_dry = self.time_budget(ignore_z=True, ignore_du=True)
drys = wet_dry[wet_dry["phase_label"] == "L"][["beg", "end"]]
if (drys.shape[0] > 0):
dry_time = drys
else:
dry_time = None
if concur_vars is None:
fig, ax = (plotting
.plot_tdr(dives_df.iloc[:, 0],
phase_cat=details_df["dive_phase"],
dry_time=dry_time, **kwargs))
else:
fig, ax = (plotting
.plot_tdr(dives_df.iloc[:, 0],
concur_vars=dives_df.iloc[:, 1:],
concur_var_titles=concur_var_titles,
phase_cat=details_df["dive_phase"],
dry_time=dry_time, **kwargs))
return (fig, ax)
[docs]
def plot_dive_model(self, diveNo=None, **kwargs):
"""Plot dive model for selected dive
Diagnostic double-panel plot of dive models for selected dives. The
top panel shows depth against time, the cubic spline smoother, the
identified descent and ascent phases (which form the basis for
identifying the rest of the dive phases), while the bottom panel
shows the first derivative of the smooth trace.
Parameters
----------
diveNo : array_like, optional
List of dive numbers (1-based) to plot.
**kwargs
Optional keyword arguments passed to
:func:`~matplotlib.pyplot.subplots`.
Returns
-------
tuple
:class:`~matplotlib.figure.Figure`,
:class:`~matplotlib.axes.Axes` instances.
See Also
--------
TDR.detect_dive_phases
Examples
--------
>>> from skdiveMove.tests import diveMove2skd
>>> tdrX = diveMove2skd()
>>> tdrX.zoc("offset", offset=3)
>>> tdrX.detect_wet()
>>> tdrX.detect_dives(3)
>>> tdrX.detect_dive_phases("unimodal", descent_crit_q=0.01,
... ascent_crit_q=0, knot_factor=20)
>>> tdrX.plot_dive_model(diveNo=20,
... figsize=(10, 10)) # doctest: +ELLIPSIS
(<Figure ... with 2 Axes>, (<Axes: ...>, <Axes: ...>))
"""
dive_ids = self.get_dives_details("row_ids", "dive_id")
crit_vals = self.get_dives_details("crit_vals").loc[diveNo]
idxs = _get_dive_indices(dive_ids, diveNo)
depth = self.get_depth("zoc").to_dataframe().iloc[idxs]
depth_s = self._get_dive_spline_slot(diveNo, "xy")
depth_deriv = (self.get_dives_details("spline_derivs").loc[diveNo])
# Index with time stamp
if depth.shape[0] < 4:
depth_s_idx = pd.date_range(depth.index[0], depth.index[-1],
periods=depth_s.shape[0],
tz=depth.index.tz)
depth_s = pd.Series(depth_s.to_numpy(), index=depth_s_idx)
dderiv_idx = pd.date_range(depth.index[0], depth.index[-1],
periods=depth_deriv.shape[0],
tz=depth.index.tz)
# Extract only the series and index with time stamp
depth_deriv = pd.Series(depth_deriv["y"].to_numpy(),
index=dderiv_idx)
else:
depth_s = pd.Series(depth_s.to_numpy(),
index=depth.index[0] + depth_s.index)
# Extract only the series and index with time stamp
depth_deriv = pd.Series(depth_deriv["y"].to_numpy(),
index=depth.index[0] + depth_deriv.index)
# Force integer again as `loc` coerced to float above
d_crit = crit_vals["descent_crit"].astype(int)
a_crit = crit_vals["ascent_crit"].astype(int)
d_crit_rate = crit_vals["descent_crit_rate"]
a_crit_rate = crit_vals["ascent_crit_rate"]
title = "Dive: {:d}".format(diveNo)
fig, axs = plotting.plot_dive_model(depth, depth_s=depth_s,
depth_deriv=depth_deriv,
d_crit=d_crit, a_crit=a_crit,
d_crit_rate=d_crit_rate,
a_crit_rate=a_crit_rate,
leg_title=title, **kwargs)
return (fig, axs)
[docs]
def get_depth(self, kind="measured"):
"""Retrieve depth records
Parameters
----------
kind : {"measured", "zoc"}
Which depth to retrieve.
Returns
-------
xarray.DataArray
See Also
--------
TDR.get_tdr, TDR.get_speed
"""
kinds = ["measured", "zoc"]
if kind == kinds[0]:
odepth = self.depth
elif kind == kinds[1]:
odepth = self.depth_zoc
if odepth is None:
msg = "ZOC depth not available."
logger.error(msg)
raise LookupError(msg)
else:
msg = "kind must be one of: {}".format(kinds)
logger.error(msg)
raise LookupError(msg)
return odepth
[docs]
def get_speed(self, kind="measured"):
"""Retrieve speed records
Parameters
----------
kind : {"measured", "calibrated"}
Type of speed to retrieve.
Returns
-------
xarray.DataArray
See Also
--------
TDR.get_tdr, TDR.get_depth
"""
kinds = ["measured", "calibrated"]
ispeed = self.speed
if kind == kinds[0]:
ospeed = ispeed
elif kind == kinds[1]:
qfit = self.speed_calib_fit
if qfit is None:
msg = "Calibrated speed not available."
logger.error(msg)
raise LookupError(msg)
else:
coefs = qfit.params.to_numpy()
coef_a = coefs[0]
coef_b = coefs[1]
ospeed = (ispeed - coef_a) / coef_b
_append_xr_attr(ospeed, "history", "speed_calib_fit")
else:
msg = "kind must be one of: {}".format(kinds)
logger.error(msg)
raise LookupError(msg)
return ospeed
[docs]
def get_tdr(self, calib_depth=True, calib_speed=True):
"""Return a copy of `TDR` Dataset
Parameters
----------
calib_depth : bool, optional
Whether to return calibrated depth measurements.
calib_speed : bool, optional
Whether to return calibrated speed measurements.
Returns
-------
xarray.Dataset
See Also
--------
TDR.get_depth, TDR.get_speed
"""
tdr = self.tdr.copy()
if calib_depth:
depth_name = self.depth_name
depth_cal = self.get_depth("zoc")
tdr[depth_name] = depth_cal
if self.has_speed and calib_speed:
speed_name = self.speed_name
speed_cal = self.get_speed("calibrated")
tdr[speed_name] = speed_cal
return tdr
[docs]
def calibrate(tdr_file, config_file=None):
"""Perform all major TDR calibration operations
Detect periods of major activities in a `TDR` object, calibrate depth
readings, and speed if appropriate, in preparation for subsequent
summaries of diving behaviour.
This function is a convenience wrapper around :meth:`~TDR.detect_wet`,
:meth:`~TDR.detect_dives`, :meth:`~TDR.detect_dive_phases`,
:meth:`~TDR.zoc`, and :meth:`~TDR.calibrate_speed`. It performs
wet/dry phase detection, zero-offset correction of depth, detection of
dives, as well as proper labelling of the latter, and calibrates speed
data if appropriate.
Due to the complexity of this procedure, and the number of settings
required for it, a calibration configuration file (JSON) is used to
guide the operations.
Parameters
----------
tdr_file : str, Path or xarray.backends.*DataStore
As first argument for :func:`xarray.load_dataset`.
config_file : str
A valid string path for TDR calibration configuration file.
Returns
-------
out : TDR
See Also
--------
dump_config_template : configuration template
"""
if config_file is None:
config = calibconfig._DEFAULT_CONFIG
else:
config = calibconfig.read_config(config_file)
logger = logging.getLogger(__name__)
logger.setLevel(config["log_level"])
load_dataset_kwargs = config["read"].pop("load_dataset_kwargs")
logger.info("Reading config: {}, {}"
.format(config["read"], load_dataset_kwargs))
tdr = TDR.read_netcdf(tdr_file, **config["read"], **load_dataset_kwargs)
do_zoc = config["zoc"].pop("required")
if do_zoc:
logger.info("ZOC config: {}".format(config["zoc"]))
tdr.zoc(config["zoc"]["method"], **config["zoc"]["parameters"])
logger.info("Wet/Dry config: {}".format(config["wet_dry"]))
tdr.detect_wet(**config["wet_dry"])
logger.info("Dives config: {}".format(config["dives"]))
tdr.detect_dives(config["dives"].pop("dive_thr"))
tdr.detect_dive_phases(**config["dives"])
do_speed_calib = bool(config["speed_calib"].pop("required"))
if tdr.has_speed and do_speed_calib:
logger.info("Speed calibration config: {}"
.format(config["speed_calib"]))
tdr.calibrate_speed(**config["speed_calib"], plot=False)
return tdr
if __name__ == '__main__':
# Set up info level logging
logging.basicConfig(level=logging.INFO)
ifile = r"tests/data/ag_mk7_2002_022.nc"
tdrX = TDR.read_netcdf(ifile, has_speed=True)
# tdrX = TDRSource(ifile, has_speed=True)
# print(tdrX)