Blame filter/gaussian.c

Packit 67cb25
/* filter/gaussian.c
Packit 67cb25
 *
Packit 67cb25
 * Gaussian smoothing 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
#include <gsl/gsl_poly.h>
Packit 67cb25
Packit 67cb25
/* maximum derivative order allowed for Gaussian filter */
Packit 67cb25
#define GSL_FILTER_GAUSSIAN_MAX_ORDER     10
Packit 67cb25
Packit 67cb25
typedef double gaussian_type_t;
Packit 67cb25
typedef double ringbuf_type_t;
Packit 67cb25
#include "ringbuf.c"
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  size_t n;        /* window size */
Packit 67cb25
  double * window; /* linear array with current window */
Packit 67cb25
  ringbuf * rbuf;  /* ring buffer storing current window */
Packit 67cb25
} gaussian_state_t;
Packit 67cb25
Packit 67cb25
static size_t gaussian_size(const size_t n);
Packit 67cb25
static int gaussian_init(const size_t n, void * vstate);
Packit 67cb25
static int gaussian_insert(const gaussian_type_t x, void * vstate);
Packit 67cb25
static int gaussian_delete(void * vstate);
Packit 67cb25
static int gaussian_get(void * params, gaussian_type_t * result, const void * vstate);
Packit 67cb25
Packit 67cb25
static const gsl_movstat_accum gaussian_accum_type;
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_filter_gaussian_alloc()
Packit 67cb25
  Allocate a workspace for Gaussian 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_gaussian_workspace *
Packit 67cb25
gsl_filter_gaussian_alloc(const size_t K)
Packit 67cb25
{
Packit 67cb25
  const size_t H = K / 2;
Packit 67cb25
  gsl_filter_gaussian_workspace *w;
Packit 67cb25
  size_t state_size;
Packit 67cb25
Packit 67cb25
  w = calloc(1, sizeof(gsl_filter_gaussian_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->K = 2 * H + 1;
Packit 67cb25
Packit 67cb25
  w->kernel = malloc(w->K * sizeof(double));
Packit 67cb25
  if (w->kernel == 0)
Packit 67cb25
    {
Packit 67cb25
      gsl_filter_gaussian_free(w);
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for kernel", GSL_ENOMEM);
Packit 67cb25
      return NULL;
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state_size = gaussian_size(w->K);
Packit 67cb25
Packit 67cb25
  w->movstat_workspace_p = gsl_movstat_alloc_with_size(state_size, H, H);
Packit 67cb25
  if (!w->movstat_workspace_p)
Packit 67cb25
    {
Packit 67cb25
      gsl_filter_gaussian_free(w);
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for movstat workspace", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  return w;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
void
Packit 67cb25
gsl_filter_gaussian_free(gsl_filter_gaussian_workspace * w)
Packit 67cb25
{
Packit 67cb25
  if (w->kernel)
Packit 67cb25
    free(w->kernel);
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_gaussian()
Packit 67cb25
  Apply a Gaussian filter to an input vector:
Packit 67cb25
Packit 67cb25
G_{sigma}(x) = exp [ -x^2 / (2 sigma^2) ]
Packit 67cb25
Packit 67cb25
Inputs: alpha - number of standard deviations to include in Gaussian kernel
Packit 67cb25
        order - derivative order of Gaussian
Packit 67cb25
        x     - input vector, size n
Packit 67cb25
        y     - (output) filtered vector, size n
Packit 67cb25
        w     - workspace
Packit 67cb25
Packit 67cb25
Notes:
Packit 67cb25
1) If alpha = 3, then the Gaussian kernel will be a Gaussian of +/- 3 standard deviations
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_filter_gaussian(const gsl_filter_end_t endtype, const double alpha, const size_t order, const gsl_vector * x,
Packit 67cb25
                    gsl_vector * y, gsl_filter_gaussian_workspace * w)
Packit 67cb25
{
Packit 67cb25
  if (x->size != y->size)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("input and output vectors must have same length", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (alpha <= 0.0)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("alpha must be positive", GSL_EDOM);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      int status;
Packit 67cb25
      gsl_vector_view kernel = gsl_vector_view_array(w->kernel, w->K);
Packit 67cb25
Packit 67cb25
      /* construct Gaussian kernel of length K */
Packit 67cb25
      gsl_filter_gaussian_kernel(alpha, order, 1, &kernel.vector);
Packit 67cb25
Packit 67cb25
      status = gsl_movstat_apply_accum(endtype, x, &gaussian_accum_type, (void *) w->kernel, y,
Packit 67cb25
                                       NULL, w->movstat_workspace_p);
Packit 67cb25
Packit 67cb25
      return status;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_filter_gaussian_kernel()
Packit 67cb25
  Construct Gaussian kernel with given sigma and order
Packit 67cb25
Packit 67cb25
Inputs: alpha     - number of standard deviations to include in window
Packit 67cb25
        order     - kernel order (0 = gaussian, 1 = first derivative, ...)
Packit 67cb25
        normalize - normalize so sum(G) = 1
Packit 67cb25
        kernel    - (output) Gaussian kernel
Packit 67cb25
Packit 67cb25
Return: success/error
Packit 67cb25
Packit 67cb25
Notes:
Packit 67cb25
1) If alpha = 3, then the output kernel will contain a Gaussian with +/- 3 standard deviations
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_filter_gaussian_kernel(const double alpha, const size_t order, const int normalize, gsl_vector * kernel)
Packit 67cb25
{
Packit 67cb25
  const size_t N = kernel->size;
Packit 67cb25
Packit 67cb25
  if (alpha <= 0.0)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("alpha must be positive", GSL_EDOM);
Packit 67cb25
    }
Packit 67cb25
  else if (order > GSL_FILTER_GAUSSIAN_MAX_ORDER)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("derivative order is too large", GSL_EDOM);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      const double half = 0.5 * (N - 1.0); /* (N - 1) / 2 */
Packit 67cb25
      double sum = 0.0;
Packit 67cb25
      size_t i;
Packit 67cb25
Packit 67cb25
      /* check for quick return */
Packit 67cb25
      if (N == 1)
Packit 67cb25
        {
Packit 67cb25
          if (order == 0)
Packit 67cb25
            gsl_vector_set(kernel, 0, 1.0);
Packit 67cb25
          else
Packit 67cb25
            gsl_vector_set(kernel, 0, 0.0);
Packit 67cb25
Packit 67cb25
          return GSL_SUCCESS;
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      for (i = 0; i < N; ++i)
Packit 67cb25
        {
Packit 67cb25
          double xi = ((double)i - half) / half;
Packit 67cb25
          double yi = alpha * xi;
Packit 67cb25
          double gi = exp(-0.5 * yi * yi);
Packit 67cb25
Packit 67cb25
          gsl_vector_set(kernel, i, gi);
Packit 67cb25
          sum += gi;
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      /* normalize so sum(kernel) = 1 */
Packit 67cb25
      if (normalize)
Packit 67cb25
        gsl_vector_scale(kernel, 1.0 / sum);
Packit 67cb25
Packit 67cb25
      if (order > 0)
Packit 67cb25
        {
Packit 67cb25
          const double beta = -0.5 * alpha * alpha;
Packit 67cb25
          double q[GSL_FILTER_GAUSSIAN_MAX_ORDER + 1];
Packit 67cb25
          size_t k;
Packit 67cb25
Packit 67cb25
          /*
Packit 67cb25
           * Need to calculate derivatives of the Gaussian window; define
Packit 67cb25
           *
Packit 67cb25
           * w(n) = C * exp [ p(n) ]
Packit 67cb25
           *
Packit 67cb25
           * p(n) = beta * n^2
Packit 67cb25
           * beta = -1/2 * ( alpha / ((N-1)/2) )^2
Packit 67cb25
           *
Packit 67cb25
           * Then:
Packit 67cb25
           *
Packit 67cb25
           * d^k/dn^k w(n) = q_k(n) * w(n)
Packit 67cb25
           *
Packit 67cb25
           * where q_k(n) is a degree-k polynomial in n, which satisfies:
Packit 67cb25
           *
Packit 67cb25
           * q_k(n) = d/dn q_{k-1}(n) + q_{k-1}(n) * dp(n)/dn
Packit 67cb25
           * q_0(n) = 1 / half^{order}
Packit 67cb25
           */
Packit 67cb25
Packit 67cb25
          /* initialize q_0(n) = 1 / half^{order} */
Packit 67cb25
          q[0] = 1.0 / gsl_pow_uint(half, order);
Packit 67cb25
          for (i = 1; i <= GSL_FILTER_GAUSSIAN_MAX_ORDER; ++i)
Packit 67cb25
            q[i] = 0.0;
Packit 67cb25
Packit 67cb25
          /* loop through derivative orders and calculate q_k(n) for k = 1,...,order */
Packit 67cb25
          for (k = 1; k <= order; ++k)
Packit 67cb25
            {
Packit 67cb25
              double qm1 = q[0];
Packit 67cb25
Packit 67cb25
              q[0] = q[1];
Packit 67cb25
              for (i = 1; i <= k; ++i)
Packit 67cb25
                {
Packit 67cb25
                  double tmp = q[i];
Packit 67cb25
                  q[i] = (i + 1.0) * q[i + 1] + /* d/dn q_{k-1} */
Packit 67cb25
                         2.0 * beta * qm1;      /* q_{k-1}(n) p'(n) */
Packit 67cb25
                  qm1 = tmp;
Packit 67cb25
                }
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* now set w(n) := q(n) * w(n) */
Packit 67cb25
          for (i = 0; i < N; ++i)
Packit 67cb25
            {
Packit 67cb25
              double xi = ((double)i - half) / half;
Packit 67cb25
              double qn = gsl_poly_eval(q, order + 1, xi);
Packit 67cb25
              double *wn = gsl_vector_ptr(kernel, i);
Packit 67cb25
Packit 67cb25
              *wn *= qn;
Packit 67cb25
            }
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      return GSL_SUCCESS;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static size_t
Packit 67cb25
gaussian_size(const size_t n)
Packit 67cb25
{
Packit 67cb25
  size_t size = 0;
Packit 67cb25
Packit 67cb25
  size += sizeof(gaussian_state_t);
Packit 67cb25
  size += n * sizeof(gaussian_type_t);
Packit 67cb25
  size += ringbuf_size(n);
Packit 67cb25
Packit 67cb25
  return size;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
gaussian_init(const size_t n, void * vstate)
Packit 67cb25
{
Packit 67cb25
  gaussian_state_t * state = (gaussian_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  state->n = n;
Packit 67cb25
Packit 67cb25
  state->window = (gaussian_type_t *) ((unsigned char *) vstate + sizeof(gaussian_state_t));
Packit 67cb25
  state->rbuf = (ringbuf *) ((unsigned char *) state->window + n * sizeof(gaussian_type_t));
Packit 67cb25
Packit 67cb25
  ringbuf_init(n, state->rbuf);
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
gaussian_insert(const gaussian_type_t x, void * vstate)
Packit 67cb25
{
Packit 67cb25
  gaussian_state_t * state = (gaussian_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  /* add new element to ring buffer */
Packit 67cb25
  ringbuf_insert(x, state->rbuf);
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
gaussian_delete(void * vstate)
Packit 67cb25
{
Packit 67cb25
  gaussian_state_t * state = (gaussian_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (!ringbuf_is_empty(state->rbuf))
Packit 67cb25
    ringbuf_pop_back(state->rbuf);
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
gaussian_get(void * params, gaussian_type_t * result, const void * vstate)
Packit 67cb25
{
Packit 67cb25
  const gaussian_state_t * state = (const gaussian_state_t *) vstate;
Packit 67cb25
  const double * kernel = (const double *) params;
Packit 67cb25
  size_t n = ringbuf_copy(state->window, state->rbuf);
Packit 67cb25
  double sum = 0.0;
Packit 67cb25
  size_t i;
Packit 67cb25
Packit 67cb25
  for (i = 0; i < n; ++i)
Packit 67cb25
    sum += state->window[i] * kernel[n - i - 1];
Packit 67cb25
Packit 67cb25
  *result = sum;
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static const gsl_movstat_accum gaussian_accum_type =
Packit 67cb25
{
Packit 67cb25
  gaussian_size,
Packit 67cb25
  gaussian_init,
Packit 67cb25
  gaussian_insert,
Packit 67cb25
  gaussian_delete,
Packit 67cb25
  gaussian_get
Packit 67cb25
};