|
Packit |
67cb25 |
/* filter/impulse.c
|
|
Packit |
67cb25 |
*
|
|
Packit |
67cb25 |
* Impulse detecting filters
|
|
Packit |
67cb25 |
*
|
|
Packit |
67cb25 |
* Copyright (C) 2018 Patrick Alken
|
|
Packit |
67cb25 |
*
|
|
Packit |
67cb25 |
* This program is free software; you can redistribute it and/or modify
|
|
Packit |
67cb25 |
* it under the terms of the GNU General Public License as published by
|
|
Packit |
67cb25 |
* the Free Software Foundation; either version 3 of the License, or (at
|
|
Packit |
67cb25 |
* your option) any later version.
|
|
Packit |
67cb25 |
*
|
|
Packit |
67cb25 |
* This program is distributed in the hope that it will be useful, but
|
|
Packit |
67cb25 |
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
67cb25 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
67cb25 |
* General Public License for more details.
|
|
Packit |
67cb25 |
*
|
|
Packit |
67cb25 |
* You should have received a copy of the GNU General Public License
|
|
Packit |
67cb25 |
* along with this program; if not, write to the Free Software
|
|
Packit |
67cb25 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
Packit |
67cb25 |
*/
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
#include <config.h>
|
|
Packit |
67cb25 |
#include <stdlib.h>
|
|
Packit |
67cb25 |
#include <math.h>
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
#include <gsl/gsl_math.h>
|
|
Packit |
67cb25 |
#include <gsl/gsl_vector.h>
|
|
Packit |
67cb25 |
#include <gsl/gsl_filter.h>
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
static int filter_impulse(const double scale, const double epsilon, const double t, const gsl_vector * x, const gsl_vector * xmedian,
|
|
Packit |
67cb25 |
gsl_vector * y, gsl_vector * xsigma, size_t * noutlier, gsl_vector_int * ioutlier);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/*
|
|
Packit |
67cb25 |
gsl_filter_impulse_alloc()
|
|
Packit |
67cb25 |
Allocate a workspace for impulse detection filtering.
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Inputs: K - number of samples in window; if even, it is rounded up to
|
|
Packit |
67cb25 |
the next odd, to have a symmetric window
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Return: pointer to workspace
|
|
Packit |
67cb25 |
*/
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
gsl_filter_impulse_workspace *
|
|
Packit |
67cb25 |
gsl_filter_impulse_alloc(const size_t K)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
gsl_filter_impulse_workspace *w;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
w = calloc(1, sizeof(gsl_filter_impulse_workspace));
|
|
Packit |
67cb25 |
if (w == 0)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR_NULL ("failed to allocate space for workspace", GSL_ENOMEM);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
w->movstat_workspace_p = gsl_movstat_alloc(K);
|
|
Packit |
67cb25 |
if (w->movstat_workspace_p == 0)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
gsl_filter_impulse_free(w);
|
|
Packit |
67cb25 |
return NULL;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
return w;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
void
|
|
Packit |
67cb25 |
gsl_filter_impulse_free(gsl_filter_impulse_workspace * w)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
if (w->movstat_workspace_p)
|
|
Packit |
67cb25 |
gsl_movstat_free(w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
free(w);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/*
|
|
Packit |
67cb25 |
gsl_filter_impulse()
|
|
Packit |
67cb25 |
Apply an impulse detection filter to an input vector. The filter output is
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
y_i = { x_i, |x_i - m_i| <= t * S_i
|
|
Packit |
67cb25 |
{ m_i, |x_i - m_i| > t * S_i
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
where m_i is the median of the window W_i^H and S_i is the scale estimate (MAD, IQR, S_n, Q_n)
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Inputs: endtype - how to handle signal end points
|
|
Packit |
67cb25 |
scale_type - which statistic to use for scale estimate (MAD, IQR, etc)
|
|
Packit |
67cb25 |
t - number of standard deviations required to identity outliers (>= 0)
|
|
Packit |
67cb25 |
x - input vector, size n
|
|
Packit |
67cb25 |
y - (output) filtered vector, size n
|
|
Packit |
67cb25 |
xmedian - (output) vector of median values of x, size n
|
|
Packit |
67cb25 |
xmedian_i = median of window centered on x_i
|
|
Packit |
67cb25 |
xsigma - (output) vector of estimated local standard deviations of x, size n
|
|
Packit |
67cb25 |
xsigma_i = sigma for i-th window: scale*MAD
|
|
Packit |
67cb25 |
noutlier - (output) number of outliers detected
|
|
Packit |
67cb25 |
ioutlier - (output) boolean array indicating outliers identified, size n; may be NULL
|
|
Packit |
67cb25 |
ioutlier_i = 1 if outlier detected, 0 if not
|
|
Packit |
67cb25 |
w - workspace
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Notes:
|
|
Packit |
67cb25 |
*/
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
int
|
|
Packit |
67cb25 |
gsl_filter_impulse(const gsl_filter_end_t endtype, const gsl_filter_scale_t scale_type, const double t,
|
|
Packit |
67cb25 |
const gsl_vector * x, gsl_vector * y, gsl_vector * xmedian, gsl_vector * xsigma, size_t * noutlier,
|
|
Packit |
67cb25 |
gsl_vector_int * ioutlier, gsl_filter_impulse_workspace * w)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
const size_t n = x->size;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
if (n != y->size)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("input and output vectors must have same length", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (xmedian->size != n)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("xmedian vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (xsigma->size != n)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("xsigma vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if ((ioutlier != NULL) && (ioutlier->size != n))
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("ioutlier vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (t < 0.0)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("t must be non-negative", GSL_EDOM);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
int status;
|
|
Packit |
67cb25 |
double scale = 1.0;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
switch (scale_type)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
case GSL_FILTER_SCALE_MAD:
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
/* compute window medians and MADs */
|
|
Packit |
67cb25 |
gsl_movstat_mad(endtype, x, xmedian, xsigma, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
break;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
case GSL_FILTER_SCALE_IQR:
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
/* multiplication factor for IQR to estimate stddev for Gaussian signal */
|
|
Packit |
67cb25 |
scale = 0.741301109252801;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* calculate the window medians */
|
|
Packit |
67cb25 |
gsl_movstat_median(endtype, x, xmedian, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* calculate window IQRs */
|
|
Packit |
67cb25 |
gsl_movstat_qqr(endtype, x, 0.25, xsigma, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
break;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
case GSL_FILTER_SCALE_SN:
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
/* calculate the window medians */
|
|
Packit |
67cb25 |
gsl_movstat_median(endtype, x, xmedian, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* calculate window S_n values */
|
|
Packit |
67cb25 |
gsl_movstat_Sn(endtype, x, xsigma, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
break;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
case GSL_FILTER_SCALE_QN:
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
/* calculate the window medians */
|
|
Packit |
67cb25 |
gsl_movstat_median(endtype, x, xmedian, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* calculate window Q_n values */
|
|
Packit |
67cb25 |
gsl_movstat_Qn(endtype, x, xsigma, w->movstat_workspace_p);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
break;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
default:
|
|
Packit |
67cb25 |
GSL_ERROR("unknown scale type", GSL_EDOM);
|
|
Packit |
67cb25 |
break;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* apply impulse detecting filter using previously computed scale estimate */
|
|
Packit |
67cb25 |
status = filter_impulse(scale, 0.0, t, x, xmedian, y, xsigma, noutlier, ioutlier);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
return status;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/*
|
|
Packit |
67cb25 |
filter_impulse()
|
|
Packit |
67cb25 |
Apply an impulse detection filter to an input vector. The filter output is
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
y_i = { x_i, |x_i - m_i| <= t * S_i OR S_i < epsilon
|
|
Packit |
67cb25 |
{ m_i, |x_i - m_i| > t * S_i
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
where m_i is the median of the window W_i^H and S_i is the scale estimate (MAD, IQR, etc)
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Inputs: scale - scale factor to multiply xsigma to get unbiased estimate of stddev for Gaussian data
|
|
Packit |
67cb25 |
epsilon - minimum allowed scale estimate for identifying outliers
|
|
Packit |
67cb25 |
t - number of standard deviations required to identity outliers (>= 0)
|
|
Packit |
67cb25 |
x - input vector, size n
|
|
Packit |
67cb25 |
xmedian - vector of median values of x, size n
|
|
Packit |
67cb25 |
xmedian_i = median of window centered on x_i
|
|
Packit |
67cb25 |
y - (output) filtered vector, size n
|
|
Packit |
67cb25 |
xsigma - (output) vector of estimated local standard deviations of x, size n
|
|
Packit |
67cb25 |
xsigma_i = S_n for i-th window
|
|
Packit |
67cb25 |
noutlier - (output) number of outliers detected
|
|
Packit |
67cb25 |
ioutlier - (output) boolean array indicating outliers identified, size n; may be NULL
|
|
Packit |
67cb25 |
ioutlier_i = 1 if outlier detected, 0 if not
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
Notes:
|
|
Packit |
67cb25 |
1) If S_i = 0 or is very small for a particular sample, then the filter may erroneously flag the
|
|
Packit |
67cb25 |
sample as an outlier, since it will act as a standard median filter. To avoid this scenario, the
|
|
Packit |
67cb25 |
parameter epsilon specifies the minimum value of S_i which can be used in the filter test. Any
|
|
Packit |
67cb25 |
samples for which S_i < epsilon are passed through unchanged.
|
|
Packit |
67cb25 |
*/
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
static int
|
|
Packit |
67cb25 |
filter_impulse(const double scale, const double epsilon, const double t, const gsl_vector * x, const gsl_vector * xmedian,
|
|
Packit |
67cb25 |
gsl_vector * y, gsl_vector * xsigma, size_t * noutlier, gsl_vector_int * ioutlier)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
const size_t n = x->size;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
if (n != y->size)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("input and output vectors must have same length", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (xmedian->size != n)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("xmedian vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (xsigma->size != n)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("xsigma vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if ((ioutlier != NULL) && (ioutlier->size != n))
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("ioutlier vector must match input size", GSL_EBADLEN);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else if (t < 0.0)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
GSL_ERROR("t must be non-negative", GSL_EDOM);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
size_t i;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
*noutlier = 0;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* build output vector */
|
|
Packit |
67cb25 |
for (i = 0; i < n; ++i)
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
double xi = gsl_vector_get(x, i);
|
|
Packit |
67cb25 |
double xmedi = gsl_vector_get(xmedian, i);
|
|
Packit |
67cb25 |
double absdevi = fabs(xi - xmedi); /* absolute deviation for this sample */
|
|
Packit |
67cb25 |
double *xsigmai = gsl_vector_ptr(xsigma, i);
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/* multiply by scale factor to get estimate of standard deviation */
|
|
Packit |
67cb25 |
*xsigmai *= scale;
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
/*
|
|
Packit |
67cb25 |
* If the absolute deviation for this sample is more than t stddevs
|
|
Packit |
67cb25 |
* for this window (and S_i is sufficiently large to avoid scale implosion),
|
|
Packit |
67cb25 |
* set the output value to the window median, otherwise use the original sample
|
|
Packit |
67cb25 |
*/
|
|
Packit |
67cb25 |
if ((*xsigmai >= epsilon) && (absdevi > t * (*xsigmai)))
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
gsl_vector_set(y, i, xmedi);
|
|
Packit |
67cb25 |
++(*noutlier);
|
|
Packit |
67cb25 |
if (ioutlier)
|
|
Packit |
67cb25 |
gsl_vector_int_set(ioutlier, i, 1);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
else
|
|
Packit |
67cb25 |
{
|
|
Packit |
67cb25 |
gsl_vector_set(y, i, xi);
|
|
Packit |
67cb25 |
if (ioutlier)
|
|
Packit |
67cb25 |
gsl_vector_int_set(ioutlier, i, 0);
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
|
|
Packit |
67cb25 |
return GSL_SUCCESS;
|
|
Packit |
67cb25 |
}
|
|
Packit |
67cb25 |
}
|