IIR filter relationship between number of poles, order and second order sections I have ported Iowa Hills Code Kit to a mcu to calculate IIR filter coefficients. They are used to initialize the CMSIS Biquad Cascade IIR Filters Using a Direct Form II Transposed Structure.
Trying to compare the output of my mcu program with a similar python script written using SciPy I realized that there is a discrepancy between the two, as explained below.
SciPy
Calculating a 4th order bandpass IIR filter using the following in SciPy,
sos = scipy.signal.iirfilter(4,
                            [1500, 2500],
                            btype='bandpass', 
                            analog=False, 
                            ftype='bessel',
                            output='sos',
                            fs=50000)

results in 8 zeros and 8 poles which is then converted to 4 Second-Order-Sections. This gives

My understanding however, is that the code above is producing an 8th order filter and that a 4th order filter should be represented as 2 second order sections and should have 4 poles and zeros. I might be missing something very obvious, but I'm not sure why signal.iirfilter produces this result!
IowaHills
To calculate a 4th order bandpass IIR fitler using IowaHills however, I would have to input numPoles=2 to get 2 second order sections as shown below,
TIIRCoeff coeffs = {0};              // filter coefficients struct (IowaHills)
TIIRFilterParams fparams;            // filter parameters struct (IowaHills)
fparams.IIRPassType = iirBPF;        // bandpass filter
fparams.ProtoType = BESSEL;          // proto bessel
fparams.BW = 0.10;                   // filter width
fparams.dBGain = 1.0;                // gain
fparams.Gamma = 0.0;                 // transition bandwith
// NumPoles = [order / 2] for BandPass and BandStop
// NumPoles = [order]     for LowPass and HighPass
fparams.NumPoles = 2;                // 4th order
fparams.OmegaC = 0.41;               // centre frequency for bandpass
fparams.Ripple = 0.0;                // only for chebyshev/elliptical
fparams.StopBanddB = 0.0;            // only for chebyshev/elliptical
coeffs = CalcIIRFilterCoeff(fparams);

this gives

Can anyone explain why the relationship between number of poles and filter order is different in the two programs? I'm assuming both of them are correct so what is the right way to interpret the outputs?
The filter order equals the number of poles, that's a fact. How people call and use parameters in software is a totally different question. Many routines that design standard IIR filters use frequency transformations to design bandpass or bandstop filters from a prototype lowpass filter. Often, the specified filter order is the order of the prototype lowpass filter, but the transformation doubles that order such that the resulting bandpass or bandstop filter has twice the order that was specified in the call of the routine. It works that way in Matlab/Octave. Maybe that's also the reason why you get twice the specified filter order with the SciPy routine and with the Iowa Hills software. Note that in both cases you obtain twice the specified filter order (or number of poles).