Adding filtering techniques
Filters must inherit from franc.evaluation.FilterInterface to be usable with this framework.
The filter interface includes the following aspects:
Must have the @dataclass decorator
This enables saving all class variables through the dataclasses.asdict() interface.
All values used here must be exportable through the numpy.save interface without using pickle to support saving / loading
Must declare a filter_name
Can overwrite the
franc.evaluation.FilterInterface.supports_saving_loading()functionBy default, it is assumed that saving/loading is supported. Overwriting the function to return False disables all related functionality and arbitrary class variables can be used.
Must provide a
franc.evaluation.FilterInterface.condition_multi_sequence()functionThis function receives the conditioning data and initializes the filter
If a method does not require/support conditioning, this method must be implemented and can just do nothing
Must provide a
franc.evaluation.FilterInterface.apply_multi_sequence()functionThis function applies the filter to the given data and returns the predicted sequences.
Minimal example
This is a minimal example implementing a simple multichannel FIR filter.
from dataclasses import dataclass
import numpy as np
from scipy.signal import correlate
import franc
@dataclass
class FIRFilter(franc.evaluation.FilterInterface):
filter_coefficients:np.typing.NDArray
filter_name = "FIR Filter"
# the handle_from_dict decorator is required to support saving and loading
@franc.evaluation.handle_from_dict
def __init__(self, n_channel, filter_coefficients):
# parameters must be passed to parent constructor for hashing
super().__init__(n_channel, filter_coefficients)
if len(filter_coefficients) != n_channel:
raise ValueError( "filter_coefficitnes length of " + \
f"{len(filter_coefficients)} does not match n_channel={n_channel}" )
self.filter_coefficients = np.array(filter_coefficients).astype(np.longdouble)
self.n_filter = len(self.filter_coefficients[0])
def condition_multi_sequence(self, witness, target):
# not needed here
pass
def apply_multi_sequence(self, witness, target, pad, update_state):
"""Apply the filter to input data
:param witness: Witness sensor data
:param target: Target sensor data (is ignored)
:param pad: if True, apply padding zeros so that the length matches the target signal
:param update_state: ignored
:return: prediction
"""
witness, target = self.check_data_dimensions_multi_sequence(witness, target)
predictions = []
for w_sequence in witness:
prediction_sequence = np.sum(
[ correlate(inpt, coeffcients, mode="valid")
for inpt, coeffcients
in zip(w_sequence, self.filter_coefficients) ],
axis=0,
)
if pad:
prediction_sequence = np.concatenate(
[
np.zeros(self.n_filter - 1),
prediction_sequence,
]
)
predictions.append(prediction_sequence)
return predictions