|
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 |
};
|