Blame filter/impulse.c

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
}