def __init__(self, freq, mincutoff=1.0, beta=0.0, dcutoff=1.0): if float(freq) <= 0: raise ValueError("freq should be >0") if float(mincutoff) <= 0: raise ValueError("mincutoff should be >0") if float(dcutoff) <= 0: raise ValueError("dcutoff should be >0") self.__freq = float(freq) self.__mincutoff = float(mincutoff) self.__beta = float(beta) self.__dcutoff = float(dcutoff) self.__x = SingleExponentialFilter(self.__alpha(self.__mincutoff), variant="lowpass") self.__dx = SingleExponentialFilter(self.__alpha(self.__dcutoff), variant="lowpass") self.__lasttime = None
class OneEuroFilter: # FIXME: find a better name for beta and dcutoff? def __init__(self, freq, mincutoff=1.0, beta=0.0, dcutoff=1.0): if float(freq) <= 0: raise ValueError("freq should be >0") if float(mincutoff) <= 0: raise ValueError("mincutoff should be >0") if float(dcutoff) <= 0: raise ValueError("dcutoff should be >0") self.__freq = float(freq) self.__mincutoff = float(mincutoff) self.__beta = float(beta) self.__dcutoff = float(dcutoff) self.__x = SingleExponentialFilter(self.__alpha(self.__mincutoff), variant="lowpass") self.__dx = SingleExponentialFilter(self.__alpha(self.__dcutoff), variant="lowpass") self.__lasttime = None def __alpha(self, cutoff): te = 1.0 / self.__freq tau = 1.0 / (2 * math.pi * cutoff) return 1.0 / (1.0 + tau / te) def __call__(self, x, timestamp=None): # ---- update the sampling frequency based on timestamps if self.__lasttime and timestamp: self.__freq = 1.0 / (timestamp - self.__lasttime) # print "OneEuroFilter: updating frequency, now %s Hz"%self.__freq self.__lasttime = timestamp # ---- estimate the current variation per second prev_x = self.__x.lastValue() dx = 0 if prev_x is None else (x - prev_x) * self.__freq edx = self.__dx(dx, timestamp, alpha=self.__alpha(self.__dcutoff)) # ---- use it to update the cutoff frequency cutoff = self.__mincutoff + self.__beta * math.fabs(edx) # ---- filter the given value # print "cutoff:", cutoff, "alpha:", self.__alpha(cutoff) return self.__x(x, timestamp, alpha=self.__alpha(cutoff)) def getURL(self): return "fltr:/oneeuro?freq=%g&mincutoff=%g&beta=%g&dcutoff=%g" % ( self.__freq, self.__mincutoff, self.__beta, self.__dcutoff, ) def __str__(self): return self.getURL() @staticmethod def generateConfigurations(freq, cutoffstep, maxcutoff, betastep, maxbeta, dcutoffs=[1.0]): nbcutoffsteps = int(math.floor((maxcutoff - cutoffstep) / cutoffstep)) cutoffs = [(i + 1) * cutoffstep for i in range(nbcutoffsteps + 1)] betas = [i * betastep for i in range(int(math.floor(maxbeta / betastep) + 1))] if dcutoffs is None: dcutoffs = cutoffs configs = [(freq, mincutoff, beta, dcutoff) for mincutoff in cutoffs for beta in betas for dcutoff in dcutoffs] return ["fltr:/oneeuro?freq=%g&mincutoff=%g&beta=%g&dcutoff=%g" % args for args in configs] @staticmethod def randomConfiguration(freq, **params): # FIXME: we're in dire need of heuristics and fancy # probability distributions... maxcutoff = params.setdefault("maxcutoff", 20) # FIXME: this is an arbitrary choice if "mincutoff" not in params: # will be in (0.0, maxcutoff] params["mincutoff"] = maxcutoff - maxcutoff * numpy.random.random_sample() if "beta" not in params: maxbeta = params.get("maxbeta", 100.0) # will be in [0.0, maxbeta) params["beta"] = maxbeta * numpy.random.random_sample() if "dcutoff" not in params: # will be in (0.0, freq] params["dcutoff"] = freq - freq * numpy.random.random_sample() return "fltr:/oneeuro?freq=%g&mincutoff=%g&beta=%g&dcutoff=%g" % ( freq, params["mincutoff"], params["beta"], params["dcutoff"], )