Source code for pyotelem.dives

[docs]def finddives2(depths, min_dive_thresh=10): """Find dives in depth data below a minimum dive threshold Args ---- depths: ndarray Datalogger depth measurements min_dive_thresh: float Minimum depth threshold for which to classify a dive Returns ------- dives: ndarray Dive summary information in a numpy record array *Columns*: * dive_id * start_idx * stop_idx * dive_dur * depth_max * depth_max_i * depth_min * depth_min_i * depth_mean * comp_mean dive_mask: ndarray Boolean mask array over depth data. Cells with `True` are dives and cells with `False` are not. """ import numpy import pandas from . import utils # Get start and stop indices for each dive above `min_dive_thresh` condition = depths > min_dive_thresh ind_start, ind_end = utils.contiguous_regions(condition) n_dives = len(ind_start) dive_mask = numpy.zeros(len(depths), dtype=bool) dtypes = numpy.dtype( [ ("dive_id", int), ("start_idx", int), ("stop_idx", int), ("dive_dur", int), ("depth_max", float), ("depth_max_idx", float), ("depth_min", float), ("depth_min_idx", float), ("depth_mean", float), ("comp_mean", float), ] ) dive_data = numpy.zeros(n_dives, dtype=dtypes) for i in range(n_dives): dive_mask[ind_start[i] : ind_end[i]] = True dive_depths = depths[ind_start[i] : ind_end[i]] dive_data["dive_id"][i] = i dive_data["start_idx"][i] = ind_start[i] dive_data["stop_idx"][i] = ind_end[i] dive_data["dive_dur"][i] = ind_end[i] - ind_start[i] dive_data["depth_max"][i] = dive_depths.max() dive_data["depth_max_idx"][i] = numpy.argmax(dive_depths) dive_data["depth_min"][i] = dive_depths.min() dive_data["depth_min_idx"][i] = numpy.argmin(dive_depths) dive_data["depth_mean"][i] = numpy.mean(dive_depths) # TODO Supposedly time of deepest dive... doesn't appear to be that dive_data["comp_mean"][i] = numpy.mean(1 + (1 / (0.1 * dive_depths))) # Filter any dives with an endpoint with an index beyond bounds of array dive_data = dive_data[dive_data["stop_idx"] < len(depths)] # Create pandas data frame with following columns, init'd with nans dives = pandas.DataFrame(dive_data) return dives, dive_mask
[docs]def get_des_asc2(depths, dive_mask, pitch, cutoff, fs, order=5): """Get boolean masks of descents and ascents in the depth data Args ---- dive_mask: ndarray Boolean mask array over depth data. Cells with `True` are dives and cells with `False` are not. pitch: ndarray Pitch angle in radians cutoff: float Cutoff frequency at which signal will be filtered fs: float Sampling frequency order: int Order of butter filter to apply Returns ------- des_mask: ndarray Boolean mask of descents in the depth data asc_mask: ndarray Boolean mask of ascents in the depth data """ import numpy from . import dsp asc_mask = numpy.zeros(len(depths), dtype=bool) des_mask = numpy.zeros(len(depths), dtype=bool) b, a = dsp.butter_filter(cutoff, fs, order, "low") dfilt = dsp.butter_apply(b, a, depths) dp = numpy.hstack([numpy.diff(dfilt), 0]) asc_mask[dive_mask] = dp[dive_mask] < 0 des_mask[dive_mask] = dp[dive_mask] > 0 # Remove descents/ascents withough a corresponding ascent/descent des_mask, asc_mask = rm_incomplete_des_asc(des_mask, asc_mask) return des_mask, asc_mask
[docs]def rm_incomplete_des_asc(des_mask, asc_mask): """Remove descents-ascents that have no corresponding ascent-descent Args ---- des_mask: ndarray Boolean mask of descents in the depth data asc_mask: ndarray Boolean mask of ascents in the depth data Returns ------- des_mask: ndarray Boolean mask of descents with erroneous regions removed asc_mask: ndarray Boolean mask of ascents with erroneous regions removed """ from . import utils # Get start/stop indices for descents and ascents des_start, des_stop = utils.contiguous_regions(des_mask) asc_start, asc_stop = utils.contiguous_regions(asc_mask) des_mask = utils.rm_regions(des_mask, asc_mask, des_start, des_stop) asc_mask = utils.rm_regions(asc_mask, des_mask, asc_start, asc_stop) return des_mask, asc_mask
[docs]def get_bottom(depths, des_mask, asc_mask): """Get boolean mask of regions in depths the animal is at the bottom Args ---- des_mask: ndarray Boolean mask of descents in the depth data asc_mask: ndarray Boolean mask of ascents in the depth data Returns ------- BOTTOM: ndarray (n,4) Indices and depths for when the animal is at the bottom *Index positions*: 0. start ind 1. depth at start 2. stop ind 3. depth at stop """ import numpy from . import utils # Get start/stop indices for descents and ascents des_start, des_stop = utils.contiguous_regions(des_mask) asc_start, asc_stop = utils.contiguous_regions(asc_mask) # Bottom time is at stop of descent until start of ascent bottom_len = min(len(des_stop), len(asc_start)) bottom_start = des_stop[:bottom_len] bottom_stop = asc_start[:bottom_len] BOTTOM = numpy.zeros((len(bottom_start), 4), dtype=float) # Time (seconds) at start of bottom phase/end of descent BOTTOM[:, 0] = bottom_start # Depth (m) at start of bottom phase/end of descent BOTTOM[:, 1] = depths[bottom_start] # Time (seconds) at end of bottom phase/start of asscent BOTTOM[:, 2] = bottom_stop # Depth (m) at end of bottom phase/start of descent BOTTOM[:, 3] = depths[bottom_stop] return BOTTOM
[docs]def get_phase(n_samples, des_mask, asc_mask): """Get the directional phase sign for each sample in depths Args ---- n_samples: int Length of output phase array des_mask: numpy.ndarray, shape (n,) Boolean mask of values where animal is descending asc_mask: numpy.ndarray, shape(n,) Boolean mask of values where animal is ascending Returns ------- phase: numpy.ndarray, shape (n,) Signed integer array values representing animal's dive phase *Phases*: * 0: neither ascending/descending * 1: ascending * -1: descending. """ import numpy phase = numpy.zeros(n_samples, dtype=int) phase[asc_mask] = 1 phase[des_mask] = -1 return phase