Convolver1D.java
/*
* Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.irurueta.numerical.signal.processing;
/**
* Convolves a 1D signal with a 1D kernel.
*/
public class Convolver1D {
/**
* Default constant value to use during convolution if CONSTANT_EDGE method
* is being used for signal's edge extension.
*/
public static final double DEFAULT_CONSTANT_VALUE = 0.0;
/**
* Default position of kernel center.
*/
public static final int DEFAULT_KERNEL_CENTER = 0;
/**
* Default method to use for signal's edge extension.
*/
public static final ConvolverEdgeMethod DEFAULT_EDGE_METHOD = ConvolverEdgeMethod.ZERO_EDGE;
/**
* Signal to be convolved.
*/
private double[] signal;
/**
* Kernel to convolve the signal with.
*/
private double[] kernel;
/**
* Position of kernel center. Typical values are the start of kernel
* (position 0) or the kernel's center. For the former, the edge extension
* is applied only at the end of the resulting signal; for the latter, edge
* extension is applied symmetrically at the beginning and end of the
* resulting signal.
*/
private int kernelCenter = DEFAULT_KERNEL_CENTER;
/**
* Edge extension method to use during convolution when parts of the kernel
* are required to lie outside the signal's boundaries.
*/
private ConvolverEdgeMethod edgeMethod = DEFAULT_EDGE_METHOD;
/**
* Constant value to use during edge extension when CONSTANT_EDGE method is
* being used.
*/
private double constantValue = DEFAULT_CONSTANT_VALUE;
/**
* Listener in charge of attending events generated by this instance.
*/
private Convolver1DListener listener;
/**
* Constructor.
*/
public Convolver1D() {
}
/**
* Constructor.
*
* @param signal signal to be convolved.
* @param kernel kernel to convolve signal with.
*/
public Convolver1D(final double[] signal, final double[] kernel) {
setSignal(signal);
setKernel(kernel);
}
/**
* Gets signal to be convolved.
*
* @return signal to be convolved.
*/
public double[] getSignal() {
return signal;
}
/**
* Sets signal to be convolved.
*
* @param signal signal to be convolved.
*/
public final void setSignal(final double[] signal) {
this.signal = signal;
}
/**
* Gets kernel to convolve the signal with.
*
* @return kernel to convolve the signal with.
*/
public double[] getKernel() {
return kernel;
}
/**
* Sets kernel to convolve the signal with.
*
* @param kernel kernel to convolve the signal with.
*/
public final void setKernel(final double[] kernel) {
this.kernel = kernel;
}
/**
* Gets position of kernel center. Typical values are the start of kernel
* (position 0) or the kernel's center. For the former, the edge extension
* is applied only at the end of the resulting signal; for the latter, edge
* extension is applied symmetrically at the beginning and end of the
* resulting signal.
*
* @return position of kernel center.
*/
public int getKernelCenter() {
return kernelCenter;
}
/**
* Sets position of kernel center. Typical values are the start of kernel
* (position 0) or the kernel's center. For the former, the edge extension
* is applied only at the end of the resulting signal; for the latter, edge
* extension is applied symmetrically at the beginning and end of the
* resulting signal.
*
* @param kernelCenter position of kernel center.
* @throws IllegalArgumentException if provided position is negative.
*/
public void setKernelCenter(final int kernelCenter) {
if (kernelCenter < 0) {
throw new IllegalArgumentException("kernel center must be zero or greater and within kernel's boundaries");
}
this.kernelCenter = kernelCenter;
}
/**
* Gets edge extension method to use during convolution when parts of the
* kernel are required to lie outside the signal's boundaries.
*
* @return edge extension method.
*/
public ConvolverEdgeMethod getEdgeMethod() {
return edgeMethod;
}
/**
* Sets edge extension method to use during convolution when parts of the
* kernel are required to lie outside the signal's boundaries.
*
* @param edgeMethod edge extension method.
*/
public void setEdgeMethod(final ConvolverEdgeMethod edgeMethod) {
this.edgeMethod = edgeMethod;
}
/**
* Gets constant value to use during edge extension when CONSTANT_EDGE
* method is being used.
*
* @return constant value to use during edge extension.
*/
public double getConstantValue() {
return constantValue;
}
/**
* Sets constant value to use during edge extension when CONSTANT_EDGE
* method is being used.
*
* @param constantValue constant value to use during edge extension.
*/
public void setConstantValue(final double constantValue) {
this.constantValue = constantValue;
}
/**
* Gets listener in charge of attending events generated by this instance.
*
* @return listener in charge of attending events generated by this
* instance.
*/
public Convolver1DListener getListener() {
return listener;
}
/**
* Sets listener in charge of attending events generated by this instance.
*
* @param listener listener in charge of attending events generated by this
* instance.
*/
public void setListener(final Convolver1DListener listener) {
this.listener = listener;
}
/**
* Indicates whether this instance is ready to start a convolution.
*
* @return true if ready, false otherwise.
*/
public boolean isReady() {
return signal != null && kernel != null && kernelCenter >= 0 && kernelCenter < kernel.length;
}
/**
* Convolves provided signal with provided kernel using provided
* configuration for edge extension.
*
* @return a new array containing result of convolution.
*/
public double[] convolve() {
return convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, listener);
}
/**
* Convolves provided signal with provided kernel using provided
* configuration for edge extension.
*
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not have
* proper length.
*/
public void convolve(final double[] result) {
convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method and kernel center located at its origin.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @return a new array containing result of convolution.
*/
public static double[] convolve(final double[] signal, final double[] kernel) {
return convolve(signal, kernel, DEFAULT_KERNEL_CENTER, (Convolver1DListener) null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method and kernel center located at its origin.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param listener listener attending events generated during convolution.
* @return a new array containing result of convolution.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final Convolver1DListener listener) {
return convolve(signal, kernel, DEFAULT_KERNEL_CENTER, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method and kernel center located at its origin.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not
* have proper length.
*/
public static void convolve(final double[] signal, final double[] kernel, final double[] result) {
convolve(signal, kernel, DEFAULT_KERNEL_CENTER, result, null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method and kernel center located at its origin.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @param listener listener attending events generated during convolution.
* @throws IllegalArgumentException if provided result array does not
* have proper length.
*/
public static void convolve(final double[] signal, final double[] kernel, final double[] result,
final Convolver1DListener listener) {
convolve(signal, kernel, DEFAULT_KERNEL_CENTER, result, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter) {
return convolve(signal, kernel, kernelCenter, (Convolver1DListener) null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param listener listener attending events generated during convolution.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final Convolver1DListener listener) {
return convolve(signal, kernel, kernelCenter, DEFAULT_EDGE_METHOD, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final double[] result) {
convolve(signal, kernel, kernelCenter, result, null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value
* extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @param listener listener attending events generated during convolution.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final double[] result, final Convolver1DListener listener) {
convolve(signal, kernel, kernelCenter, DEFAULT_EDGE_METHOD, result, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value when
* constant edge extension is used (which makes it equivalent to zero
* extension method).
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod) {
return convolve(signal, kernel, kernelCenter, edgeMethod, (Convolver1DListener) null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value when
* constant edge extension is used (which makes it equivalent to zero
* extension method).
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param listener listener attending events generated during convolution.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final Convolver1DListener listener) {
return convolve(signal, kernel, kernelCenter, edgeMethod, DEFAULT_CONSTANT_VALUE, listener);
}
/**
* Convolves provided signal with provided kernel assuming a zero value when
* constant edge extension is used (which makes it equivalent to zero
* extension method).
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double[] result) {
convolve(signal, kernel, kernelCenter, edgeMethod, result, null);
}
/**
* Convolves provided signal with provided kernel assuming a zero value when
* constant edge extension is used (which makes it equivalent to zero
* extension method).
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param listener listener attending events generated during convolution.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double[] result,
final Convolver1DListener listener) {
convolve(signal, kernel, kernelCenter, edgeMethod, DEFAULT_CONSTANT_VALUE, result, listener);
}
/**
* Convolves provided signal with provided kernel.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param constantValue constant value to be used for edge extension if
* constant extension method is used.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double constantValue) {
return convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, (Convolver1DListener) null);
}
/**
* Convolves provided signal with provided kernel.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param constantValue constant value to be used for edge extension if
* constant extension method is used.
* @param listener listener attending events generated during convolution.
* @return a new array containing result of convolution.
* @throws IllegalArgumentException if kernel center is not within kernel's
* boundaries.
*/
public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double constantValue,
final Convolver1DListener listener) {
final var result = new double[signal.length + kernel.length - 1];
convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, listener);
return result;
}
/**
* Convolves provided signal with provided kernel.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param constantValue constant value to be used for edge extension if
* constant extension method is used.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double constantValue,
final double[] result) {
convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, null);
}
/**
* Convolves provided signal with provided kernel.
*
* @param signal signal to be convolved.
* @param kernel kernel used for convolution.
* @param kernelCenter position of kernel center. This determines how
* signal's edge extension is applied. Must be a value within kernel's
* boundaries.
* @param edgeMethod edge extension method.
* @param constantValue constant value to be used for edge extension if
* constant extension method is used.
* @param result array where result of convolution will be stored. Must
* have the sum of the lengths of signal and kernel minus one.
* @param listener listener attending events generated during convolution.
* @throws IllegalArgumentException if provided result array does not
* have proper length or if kernel center is not within kernel's boundaries.
*/
public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
final ConvolverEdgeMethod edgeMethod, final double constantValue, final double[] result,
final Convolver1DListener listener) {
if (kernelCenter < 0 || kernelCenter >= kernel.length) {
throw new IllegalArgumentException("kernel center must be within kernel boundaries");
}
if (result.length != signal.length + kernel.length - 1) {
throw new IllegalArgumentException(
" result array must have a length equal to the sum of signal and kernel lengths minus one");
}
switch (edgeMethod) {
case MIRROR_EDGE:
internalConvolveMirror(signal, kernel, kernelCenter, result, listener);
break;
case REPEAT_EDGE:
internalConvolveRepeat(signal, kernel, kernelCenter, result, listener);
break;
case CONSTANT_EDGE:
internalConvolveConstant(signal, kernel, kernelCenter, constantValue, result, listener);
break;
case ZERO_EDGE:
default:
internalConvolveZero(signal, kernel, kernelCenter, result, listener);
break;
}
}
/**
* Internal method to convolve signal using zero edge extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel to convolve with.
* @param kernelCenter kernel center.
* @param result array where result is stored.
* @param listener listener attending events generated during convolution.
*/
private static void internalConvolveZero(
final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
final Convolver1DListener listener) {
final var kernelLength = kernel.length;
final var resultLength = result.length;
double accum;
int signalPos;
if (listener != null) {
listener.onStartConvolution();
}
for (var i = 0; i < resultLength; i++) {
accum = 0.0;
signalPos = i - kernelCenter;
for (var j = 0; j < kernelLength; j++) {
final var signalValue = getSignalValueZero(signal, signalPos - j);
accum += signalValue * kernel[j];
}
result[i] = accum;
if (listener != null) {
final var progress = (float) i / (float) resultLength;
listener.onConvolveProgressChange(progress);
}
}
if (listener != null) {
listener.onFinishConvolution();
}
}
/**
* Internal method to determine signal value even when a position outside
* its boundaries is requested when zero edge extension is being used.
*
* @param signal signal to request a value for.
* @param pos requested position.
* @return signal value when position is within boundaries or zero
* otherwise.
*/
protected static double getSignalValueZero(final double[] signal, final int pos) {
if (pos >= 0 && pos < signal.length) {
return signal[pos];
} else {
return 0.0;
}
}
/**
* Internal method to convolve signal using constant value edge extension
* method.
*
* @param signal signal to be convolved.
* @param kernel kernel to convolve with.
* @param kernelCenter kernel center.
* @param constantValue constant value to use for positions outside signal
* boundaries.
* @param result array where result is stored.
* @param listener listener attending events generated during convolution.
*/
private static void internalConvolveConstant(
final double[] signal, final double[] kernel, final int kernelCenter, final double constantValue,
final double[] result, final Convolver1DListener listener) {
final var kernelLength = kernel.length;
final var resultLength = result.length;
double accum;
int signalPos;
if (listener != null) {
listener.onStartConvolution();
}
for (var i = 0; i < resultLength; i++) {
accum = 0.0;
signalPos = i - kernelCenter;
for (var j = 0; j < kernelLength; j++) {
final var signalValue = getSignalValueConstant(signal, signalPos - j, constantValue);
accum += signalValue * kernel[j];
}
result[i] = accum;
if (listener != null) {
final var progress = (float) i / (float) resultLength;
listener.onConvolveProgressChange(progress);
}
}
if (listener != null) {
listener.onFinishConvolution();
}
}
/**
* Internal method to determine signal value even when a position outside
* its boundaries is requested for constant value edge extension.
*
* @param signal signal to request a value for.
* @param pos requested position.
* @param constantValue constant value to use when requesting a position
* outside signal boundaries.
* @return signal value when position is within boundaries or constant
* value otherwise.
*/
protected static double getSignalValueConstant(final double[] signal, final int pos, final double constantValue) {
if (pos >= 0 && pos < signal.length) {
return signal[pos];
} else {
return constantValue;
}
}
/**
* Internal method to convolve signal using a repeat extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel to convolve with.
* @param kernelCenter kernel center.
* @param result array where result is stored.
* @param listener listener attending events generated during convolution.
*/
private static void internalConvolveRepeat(
final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
final Convolver1DListener listener) {
final var kernelLength = kernel.length;
final var resultLength = result.length;
double accum;
int signalPos;
if (listener != null) {
listener.onStartConvolution();
}
for (var i = 0; i < resultLength; i++) {
accum = 0.0;
signalPos = i - kernelCenter;
for (var j = 0; j < kernelLength; j++) {
final var signalValue = getSignalValueRepeat(signal, signalPos - j);
accum += signalValue * kernel[j];
}
result[i] = accum;
if (listener != null) {
final var progress = (float) i / (float) resultLength;
listener.onConvolveProgressChange(progress);
}
}
if (listener != null) {
listener.onFinishConvolution();
}
}
/**
* Internal method to determine signal value even when a position outside
* its boundaries is requested for repeat edge extension.
*
* @param signal signal to request a value for.
* @param pos requested position.
* @return signal value assuming that signal is repeated indefinitely
* outside its boundaries.
*/
protected static double getSignalValueRepeat(final double[] signal, int pos) {
final var signalLength = signal.length;
pos = pos % signalLength;
if (pos < 0) {
pos += signalLength;
}
return signal[pos];
}
/**
* Internal method to convolve signal using a mirror extension method.
*
* @param signal signal to be convolved.
* @param kernel kernel to convolve with.
* @param kernelCenter kernel center.
* @param result array where result is stored.
* @param listener listener attending events generated during convolution.
*/
private static void internalConvolveMirror(
final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
final Convolver1DListener listener) {
final var kernelLength = kernel.length;
final var resultLength = result.length;
double accum;
int signalPos;
if (listener != null) {
listener.onStartConvolution();
}
for (var i = 0; i < resultLength; i++) {
accum = 0.0;
signalPos = i - kernelCenter;
for (var j = 0; j < kernelLength; j++) {
final var signalValue = getSignalValueMirror(signal, signalPos - j);
accum += signalValue * kernel[j];
}
result[i] = accum;
if (listener != null) {
final var progress = (float) i / (float) resultLength;
listener.onConvolveProgressChange(progress);
}
}
if (listener != null) {
listener.onFinishConvolution();
}
}
/**
* Internal method to determine signal value even when a position outside
* its boundaries is requested for mirror edge extension.
*
* @param signal signal to request a value for.
* @param pos requested position.
* @return signal value when assuming that signal is mirrored outside its
* boundaries.
*/
protected static double getSignalValueMirror(double[] signal, int pos) {
final var signalLength = signal.length;
var times = pos / signalLength;
if (pos < 0) {
times = Math.abs(times);
if (times == 0) {
times = 1;
}
pos = 2 * signalLength * times + pos;
}
times = pos / signalLength;
final var reversed = (times % 2 != 0);
pos = pos % signalLength;
if (pos < 0) {
pos += signalLength;
} else {
if (reversed) {
// reversed
pos = signalLength - 1 - pos;
}
}
return signal[pos];
}
/**
* Interface defining events produced by this class.
*/
public interface Convolver1DListener {
/**
* Called when convolution starts.
*/
void onStartConvolution();
/**
* Called when convolution finishes.
*/
void onFinishConvolution();
/**
* Called when convolution progress changes.
*
* @param progress convolution progress as a value between 0.0f and
* 1.0f.
*/
void onConvolveProgressChange(final float progress);
}
}