GithubHelp home page GithubHelp logo

Comments (11)

ar1st0crat avatar ar1st0crat commented on September 27, 2024

Ordering of zeros and poles in output vectors doesn't matter (it's just a set of complex numbers).
Gain should also be the same (or, at least, very close to scipy's result). If the gain is very small, there may be some insignificant difference, but it's caused by round-off errors.
What input parameters did you specify (lower, upper, fs)?

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

Yes, scipy seems more precise. Maybe the calculation of the warped frequencies and the transformation into lowpass, bandpass, highpass, or bandstop are different?

Here some test code:
`var fs = 48000d;
var downsamplingFactor = 50;
var fsd = fs / downsamplingFactor;
var lower = 11.220184543019641;//calculated fractional-octave band
var upper = 14.125375446227549;

var bw = new Filters.Butterworth.BandPassFilter(lower / fsd, upper / fsd, 6);
var (z, p, k) = (bw.Tf.Zeros, bw.Tf.Poles, bw.Tf.Gain);

var tf = DesignFilter.TfToSos(bw.Tf);
var sosFilter = new FilterChain(tf);`

from nwaves.

ar1st0crat avatar ar1st0crat commented on September 27, 2024

The calculation is essentially the same (as far as I can judge from reading a sciPy codebase). But in your particular case the filter is very narrow, so round-off errors become an important factor. Actually, in sciPy the frequency response for your parameters is not good too:

image

check #33 and #30

and this

from nwaves.

ar1st0crat avatar ar1st0crat commented on September 27, 2024

UPD: anyway, the filtering will be OK if you do it as below, using SOS:

var fs = 48000d;
var downsamplingFactor = 50;
var fsd = fs / downsamplingFactor;
var lower = 11.220184543019641;//calculated fractional-octave band
var upper = 14.125375446227549;

var filter = new Filters.Butterworth.BandPassFilter(lower / fsd, upper / fsd, 6);
var sos = DesignFilter.TfToSos(filter.Tf);
var sosFilter = new FilterChain(sos);

// 1) SOS will mitigate the numeric effects:
// 2) re-estimate gain (this isn't available in ver.0.9.4 yet, but it's in current codebase)

var gain = sosFilter.EstimateGain();
var filteredSignal = sosFilter.ApplyTo(_signal, gain);

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

Good to know, I'll try the estimate gain solution.

But at the moment I'm stuck with the scipy 'y = filtfilt(num, den, x)' equivalent ZiFilter.ZeroPhase. It returns big differences. I think because of the pad type. Default in scipy is odd padding. NWaves has only zero padding...

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

Oh ... I'm just beginning to understand. ZiFilter.ZeroPhase has an "implicit" odd padding, right? I'm trying to figure out where the big differences come from...

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

I found one issue. In TransferFunction.Zi the 'zi' array length should be size - 1:
var zi = new double[size - 1];

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

An other issue seems to be the padLength in ZiFilter.ZeroPhase. It should be:
padLength = 3 * Math.Max(_a.Length, _b.Length);

instead of:
padLength = 3 * (Math.Max(_a.Length, _b.Length) - 1);

from nwaves.

ar1st0crat avatar ar1st0crat commented on September 27, 2024

Yes, for some reason, sciPy filtfilt default parameter is not compliant with MATLAB/NWaves filtfilt.

In MATLAB/NWaves default padlen is 3 * (max(len(a), len(b)) - 1). If you need to achieve the same results in sciPy as in MATLAB/NWaves, specify this value explicitly. Example:

double[] num = {1, 0.2, 0.5, -0.6, 0.3};
double[] den = {1, -0.2, 0.4, 0.1};
		
var filter = new ZiFilter(num, den);
		
// ============== lfilter_zi(num, den): ==================
		
double[] zi = filter.Tf.Zi;
		
		
// ============== filtfilt(num, den, np.arange(20), padlen=3*(5-1)): ================== 
		
var input = new DiscreteSignal(1, Enumerable.Range(0, 20));
		
var output = filter.ZeroPhase(input);

In MATLAB:

y = filtfilt([1 0.2 0.5 -0.6 0.3], [1 -0.2 0.4 0.1], 0:19)

In sciPy:

num = [1, 0.2, 0.5, -0.6, 0.3]
den = [1, -0.2, 0.4, 0.1]

x = np.arange(20)

zi = lfilter_zi(num, den)
# [ 0.07692308, -0.33846154, -0.40769231,  0.3]
# in NWaves: { 0.07692308, -0.33846154, -0.40769231,  0.3, 0 }

y = filtfilt(num, den, x, padlen=12)        # 3* (max(len(num), len(den)) - 1)

PS. As for Zi length, it's not an issue. One trailing element is added for convenience (in internal computations). Simply ignore the last element.

from nwaves.

ToGoOrNotToGo avatar ToGoOrNotToGo commented on September 27, 2024

One more comment on this: After many tests and some learning, I think that the inaccuracies arise because the gain (k) is not taken into account in the calculations with zeros and poles (z, p) in TransferFunction or DesignFilter.

Anyway ... NWaves is great for studying signal analysis, implementing math algorithms, OOP, and working with music audio. For general DSP and scientific use cases, further improvements are required (e.g. the z, p, k and the analog to digital filter transformations (lp to hp, bp, bs)).

Thank you for this library and your very good explanations! I hope you continue :-)

from nwaves.

ar1st0crat avatar ar1st0crat commented on September 27, 2024

Thank you for your kind words!

The gain takes part in computations (actually it's always reflected in TF numerator). The reasons for inaccuracies in this particular case is the specifics of your particularly narrow filter (MATLAB, sciPy and NWaves - all of them give different results for this filter parameters due to numeric errors). Just check out other filters (with wider passband) - you'll see NWaves/MATLAB/sciPy will produce equivalent results.

from nwaves.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.