Source code for mhkit.dolfyn.tools.misc

import numpy as np
from scipy.signal import medfilt2d, convolve2d


def _nans(*args, **kwargs):
    out = np.empty(*args, **kwargs)
    if np.issubdtype(out.flatten()[0], np.integer):
        out[:] = 0
        return out
    else:
        out[:] = np.nan
        return out


def _nans_like(*args, **kwargs):
    out = np.empty_like(*args, **kwargs)
    out[:] = np.nan
    return out


def _find(arr):
    return np.nonzero(np.ravel(arr))[0]


[docs] def detrend_array(arr, axis=-1, in_place=False): """ Remove a linear trend from arr. Parameters ---------- arr : array_like The array from which to remove a linear trend. axis : int The axis along which to operate. Notes ----- This method is copied from the matplotlib.mlab library, but implements the covariance calcs explicitly for added speed. This works much faster than mpl.mlab.detrend for multi-dimensional arrays, and is also faster than linalg.lstsq methods. """ arr = np.asarray(arr) if not in_place: arr = arr.copy() sz = np.ones(arr.ndim, dtype=int) sz[axis] = arr.shape[axis] x = np.arange(sz[axis], dtype=np.float64).reshape(sz) x -= np.nanmean(x, axis=axis, keepdims=True) arr -= np.nanmean(arr, axis=axis, keepdims=True) b = np.nanmean((x * arr), axis=axis, keepdims=True) / np.nanmean( (x**2), axis=axis, keepdims=True ) arr -= b * x return arr
[docs] def group(bl, min_length=0): """ Find continuous segments in a boolean array. Parameters ---------- bl : numpy.ndarray (dtype='bool') The input boolean array. min_length : int (optional) Specifies the minimum number of continuous points to consider a `group` (i.e. that will be returned). Returns ------- out : np.ndarray(slices,) a vector of slice objects, which indicate the continuous sections where `bl` is True. Notes ----- This function has funny behavior for single points. It will return the same two indices for the beginning and end. """ if not any(bl): return np.empty(0) vl = np.diff(bl.astype("int")) ups = np.nonzero(vl == 1)[0] + 1 dns = np.nonzero(vl == -1)[0] + 1 if bl[0]: if len(ups) == 0: ups = np.array([0]) else: ups = np.concatenate((np.array([0]), [len(ups)])) if bl[-1]: if len(dns) == 0: dns = np.array([len(bl)]) else: dns = np.concatenate((dns, [len(bl)])) out = np.empty(len(dns), dtype="O") idx = 0 for u, d in zip(ups, dns): if d - u < min_length: continue out[idx] = slice(u, d) idx += 1 return out[:idx]
[docs] def slice1d_along_axis(arr_shape, axis=0): """ Return an iterator object for looping over 1-D slices, along ``axis``, of an array of shape arr_shape. Parameters ---------- arr_shape : tuple,list Shape of the array over which the slices will be made. axis : integer Axis along which `arr` is sliced. Returns ------- Iterator : object The iterator object returns slice objects which slices arrays of shape arr_shape into 1-D arrays. Examples -------- >> out=np.empty(replace(arr.shape,0,1)) >> for slc in slice1d_along_axis(arr.shape,axis=0): >> out[slc]=my_1d_function(arr[slc]) """ nd = len(arr_shape) if axis < 0: axis += nd ind = [0] * (nd - 1) i = np.zeros(nd, "O") indlist = list(range(nd)) indlist.remove(axis) i[axis] = slice(None) itr_dims = np.asarray(arr_shape).take(indlist) Ntot = np.prod(itr_dims) i.put(indlist, ind) k = 0 while k < Ntot: # increment the index n = -1 while (ind[n] >= itr_dims[n]) and (n > (1 - nd)): ind[n - 1] += 1 ind[n] = 0 n -= 1 i.put(indlist, ind) yield tuple(i) ind[-1] += 1 k += 1
[docs] def convert_degrees(deg, tidal_mode=True): """ Converts between the 'cartesian angle' (counter-clockwise from East) and the 'polar angle' in (degrees clockwise from North) Parameters ---------- deg: float or array-like Number or array in 'degrees CCW from East' or 'degrees CW from North' tidal_mode : bool If true, range is set from 0 to +/-180 degrees. If false, range is 0 to 360 degrees. Default = True Returns ------- out : float or array-like Input data transformed to 'degrees CW from North' or 'degrees CCW from East', respectively (based on `deg`) Notes ----- The same algorithm is used to convert back and forth between 'CCW from E' and 'CW from N' """ out = -(deg - 90) % 360 if tidal_mode: out[out > 180] -= 360 return out
[docs] def fillgaps(a, maxgap=np.inf, dim=0, extrapFlg=False): """ Linearly fill NaN value in an array. Parameters ---------- a : numpy.ndarray The array to be filled. maxgap : numpy.ndarray (optional: inf) The maximum gap to fill. dim : int (optional: 0) The dimension to operate along. extrapFlg : bool (optional: False) Whether to extrapolate if NaNs are found at the ends of the array. See Also -------- mhkit.dolfyn.tools.misc._interpgaps : Linearly interpolates in time. Notes ----- This function interpolates assuming spacing/timestep between successive points is constant. If the spacing is not constant, use _interpgaps. """ # If this is a multi-dimensional array, operate along axis dim. if a.ndim > 1: for inds in slice1d_along_axis(a.shape, dim): fillgaps(a[inds], maxgap, 0, extrapFlg) return a = np.asarray(a) nd = a.ndim if dim < 0: dim += nd if dim >= nd: raise ValueError("dim must be less than a.ndim; dim=%d, rank=%d." % (dim, nd)) ind = [0] * (nd - 1) i = np.zeros(nd, "O") indlist = list(range(nd)) indlist.remove(dim) i[dim] = slice(None, None) i.put(indlist, ind) gd = np.nonzero(~np.isnan(a))[0] # Here we extrapolate the ends, if necessary: if extrapFlg and gd.__len__() > 0: if gd[0] != 0 and gd[0] <= maxgap: a[: gd[0]] = a[gd[0]] if gd[-1] != a.__len__() and (a.__len__() - (gd[-1] + 1)) <= maxgap: a[gd[-1] :] = a[gd[-1]] # Here is the main loop if gd.__len__() > 1: inds = np.nonzero((1 < np.diff(gd)) & (np.diff(gd) <= maxgap + 1))[0] for i2 in range(0, inds.__len__()): ii = list(range(gd[inds[i2]] + 1, gd[inds[i2] + 1])) a[ii] = ( np.diff(a[gd[[inds[i2], inds[i2] + 1]]]) * (np.arange(0, ii.__len__()) + 1) / (ii.__len__() + 1) + a[gd[inds[i2]]] ).astype(a.dtype) return a
[docs] def interpgaps(a, t, maxgap=np.inf, dim=0, extrapFlg=False): """ Fill gaps (NaN values) in ``a`` by linear interpolation along dimension ``dim`` with the point spacing specified in ``t``. Parameters ---------- a : numpy.ndarray The array containing NaN values to be filled. t : numpy.ndarray (len(t) == a.shape[dim]) Independent variable of the points in ``a``, e.g. timestep maxgap : numpy.ndarray (optional: inf) The maximum gap to fill. dim : int (optional: 0) The dimension to operate along. extrapFlg : bool (optional: False) Whether to extrapolate if NaNs are found at the ends of the array. See Also -------- mhkit.dolfyn.tools.misc.fillgaps : Linearly interpolates in array-index space. """ # If this is a multi-dimensional array, operate along dim dim. if a.ndim > 1: for inds in slice1d_along_axis(a.shape, dim): interpgaps(a[inds], t, maxgap, 0, extrapFlg) return gd = _find(~np.isnan(a)) # Here we extrapolate the ends, if necessary: if extrapFlg and gd.__len__() > 0: if gd[0] != 0 and gd[0] <= maxgap: a[: gd[0]] = a[gd[0]] if gd[-1] != a.__len__() and (a.__len__() - (gd[-1] + 1)) <= maxgap: a[gd[-1] :] = a[gd[-1]] # Here is the main loop if gd.__len__() > 1: inds = _find((1 < np.diff(gd)) & (np.diff(gd) <= maxgap + 1)) for i2 in range(0, inds.__len__()): ii = np.arange(gd[inds[i2]] + 1, gd[inds[i2] + 1]) ti = (t[ii] - t[gd[inds[i2]]]) / np.diff( t[[gd[inds[i2]], gd[inds[i2] + 1]]] ) a[ii] = ( np.diff(a[gd[[inds[i2], inds[i2] + 1]]]) * ti + a[gd[inds[i2]]] ).astype(a.dtype) return a
[docs] def medfiltnan(a, kernel, thresh=0): """ Do a running median filter of the data. Regions where more than ``thresh`` fraction of the points are NaN are set to NaN. Parameters ---------- a : numpy.ndarray 2D array containing data to be filtered. kernel_size : numpy.ndarray or list, optional A scalar or a list of length 2, giving the size of the median filter window in each dimension. Elements of kernel_size should be odd. If kernel_size is a scalar, then this scalar is used as the size in each dimension. thresh : int Maximum gap in *a* to filter over Returns ------- out : numpy.ndarray 2D array of same size containing filtered data See Also -------- scipy.signal.medfilt2d """ flag_1D = False if a.ndim == 1: a = a[None, :] flag_1D = True try: len(kernel) except: kernel = [1, kernel] out = medfilt2d(a, kernel) if thresh > 0: out[ convolve2d(np.isnan(a), np.ones(kernel) / np.prod(kernel), "same") > thresh ] = np.nan if flag_1D: return out[0] return out