123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501 |
- # Author: Travis Oliphant
- # 1999 -- 2002
- from __future__ import division, print_function, absolute_import
- import operator
- import threading
- import sys
- import timeit
- from scipy.spatial import cKDTree
- from . import sigtools, dlti
- from ._upfirdn import upfirdn, _output_len
- from scipy._lib.six import callable
- from scipy._lib._version import NumpyVersion
- from scipy import fftpack, linalg
- from scipy.fftpack.helper import _init_nd_shape_and_axes_sorted
- from numpy import (allclose, angle, arange, argsort, array, asarray,
- atleast_1d, atleast_2d, cast, dot, exp, expand_dims,
- iscomplexobj, mean, ndarray, newaxis, ones, pi,
- poly, polyadd, polyder, polydiv, polymul, polysub, polyval,
- product, r_, ravel, real_if_close, reshape,
- roots, sort, take, transpose, unique, where, zeros,
- zeros_like)
- import numpy as np
- import math
- from scipy.special import factorial
- from .windows import get_window
- from ._arraytools import axis_slice, axis_reverse, odd_ext, even_ext, const_ext
- from .filter_design import cheby1, _validate_sos
- from .fir_filter_design import firwin
- if sys.version_info.major >= 3 and sys.version_info.minor >= 5:
- from math import gcd
- else:
- from fractions import gcd
- __all__ = ['correlate', 'fftconvolve', 'convolve', 'convolve2d', 'correlate2d',
- 'order_filter', 'medfilt', 'medfilt2d', 'wiener', 'lfilter',
- 'lfiltic', 'sosfilt', 'deconvolve', 'hilbert', 'hilbert2',
- 'cmplx_sort', 'unique_roots', 'invres', 'invresz', 'residue',
- 'residuez', 'resample', 'resample_poly', 'detrend',
- 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method',
- 'filtfilt', 'decimate', 'vectorstrength']
- _modedict = {'valid': 0, 'same': 1, 'full': 2}
- _boundarydict = {'fill': 0, 'pad': 0, 'wrap': 2, 'circular': 2, 'symm': 1,
- 'symmetric': 1, 'reflect': 4}
- _rfft_mt_safe = (NumpyVersion(np.__version__) >= '1.9.0.dev-e24486e')
- _rfft_lock = threading.Lock()
- def _valfrommode(mode):
- try:
- return _modedict[mode]
- except KeyError:
- raise ValueError("Acceptable mode flags are 'valid',"
- " 'same', or 'full'.")
- def _bvalfromboundary(boundary):
- try:
- return _boundarydict[boundary] << 2
- except KeyError:
- raise ValueError("Acceptable boundary flags are 'fill', 'circular' "
- "(or 'wrap'), and 'symmetric' (or 'symm').")
- def _inputs_swap_needed(mode, shape1, shape2):
- """
- If in 'valid' mode, returns whether or not the input arrays need to be
- swapped depending on whether `shape1` is at least as large as `shape2` in
- every dimension.
- This is important for some of the correlation and convolution
- implementations in this module, where the larger array input needs to come
- before the smaller array input when operating in this mode.
- Note that if the mode provided is not 'valid', False is immediately
- returned.
- """
- if mode == 'valid':
- ok1, ok2 = True, True
- for d1, d2 in zip(shape1, shape2):
- if not d1 >= d2:
- ok1 = False
- if not d2 >= d1:
- ok2 = False
- if not (ok1 or ok2):
- raise ValueError("For 'valid' mode, one must be at least "
- "as large as the other in every dimension")
- return not ok1
- return False
- def correlate(in1, in2, mode='full', method='auto'):
- r"""
- Cross-correlate two N-dimensional arrays.
- Cross-correlate `in1` and `in2`, with the output size determined by the
- `mode` argument.
- Parameters
- ----------
- in1 : array_like
- First input.
- in2 : array_like
- Second input. Should have the same number of dimensions as `in1`.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear cross-correlation
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding. In 'valid' mode, either `in1` or `in2`
- must be at least as large as the other in every dimension.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- method : str {'auto', 'direct', 'fft'}, optional
- A string indicating which method to use to calculate the correlation.
- ``direct``
- The correlation is determined directly from sums, the definition of
- correlation.
- ``fft``
- The Fast Fourier Transform is used to perform the correlation more
- quickly (only available for numerical arrays.)
- ``auto``
- Automatically chooses direct or Fourier method based on an estimate
- of which is faster (default). See `convolve` Notes for more detail.
- .. versionadded:: 0.19.0
- Returns
- -------
- correlate : array
- An N-dimensional array containing a subset of the discrete linear
- cross-correlation of `in1` with `in2`.
- See Also
- --------
- choose_conv_method : contains more documentation on `method`.
- Notes
- -----
- The correlation z of two d-dimensional arrays x and y is defined as::
- z[...,k,...] = sum[..., i_l, ...] x[..., i_l,...] * conj(y[..., i_l - k,...])
- This way, if x and y are 1-D arrays and ``z = correlate(x, y, 'full')``
- then
- .. math::
- z[k] = (x * y)(k - N + 1)
- = \sum_{l=0}^{||x||-1}x_l y_{l-k+N-1}^{*}
- for :math:`k = 0, 1, ..., ||x|| + ||y|| - 2`
- where :math:`||x||` is the length of ``x``, :math:`N = \max(||x||,||y||)`,
- and :math:`y_m` is 0 when m is outside the range of y.
- ``method='fft'`` only works for numerical arrays as it relies on
- `fftconvolve`. In certain cases (i.e., arrays of objects or when
- rounding integers can lose precision), ``method='direct'`` is always used.
- Examples
- --------
- Implement a matched filter using cross-correlation, to recover a signal
- that has passed through a noisy channel.
- >>> from scipy import signal
- >>> sig = np.repeat([0., 1., 1., 0., 1., 0., 0., 1.], 128)
- >>> sig_noise = sig + np.random.randn(len(sig))
- >>> corr = signal.correlate(sig_noise, np.ones(128), mode='same') / 128
- >>> import matplotlib.pyplot as plt
- >>> clock = np.arange(64, len(sig), 128)
- >>> fig, (ax_orig, ax_noise, ax_corr) = plt.subplots(3, 1, sharex=True)
- >>> ax_orig.plot(sig)
- >>> ax_orig.plot(clock, sig[clock], 'ro')
- >>> ax_orig.set_title('Original signal')
- >>> ax_noise.plot(sig_noise)
- >>> ax_noise.set_title('Signal with noise')
- >>> ax_corr.plot(corr)
- >>> ax_corr.plot(clock, corr[clock], 'ro')
- >>> ax_corr.axhline(0.5, ls=':')
- >>> ax_corr.set_title('Cross-correlated with rectangular pulse')
- >>> ax_orig.margins(0, 0.1)
- >>> fig.tight_layout()
- >>> fig.show()
- """
- in1 = asarray(in1)
- in2 = asarray(in2)
- if in1.ndim == in2.ndim == 0:
- return in1 * in2.conj()
- elif in1.ndim != in2.ndim:
- raise ValueError("in1 and in2 should have the same dimensionality")
- # Don't use _valfrommode, since correlate should not accept numeric modes
- try:
- val = _modedict[mode]
- except KeyError:
- raise ValueError("Acceptable mode flags are 'valid',"
- " 'same', or 'full'.")
- # this either calls fftconvolve or this function with method=='direct'
- if method in ('fft', 'auto'):
- return convolve(in1, _reverse_and_conj(in2), mode, method)
- elif method == 'direct':
- # fastpath to faster numpy.correlate for 1d inputs when possible
- if _np_conv_ok(in1, in2, mode):
- return np.correlate(in1, in2, mode)
- # _correlateND is far slower when in2.size > in1.size, so swap them
- # and then undo the effect afterward if mode == 'full'. Also, it fails
- # with 'valid' mode if in2 is larger than in1, so swap those, too.
- # Don't swap inputs for 'same' mode, since shape of in1 matters.
- swapped_inputs = ((mode == 'full') and (in2.size > in1.size) or
- _inputs_swap_needed(mode, in1.shape, in2.shape))
- if swapped_inputs:
- in1, in2 = in2, in1
- if mode == 'valid':
- ps = [i - j + 1 for i, j in zip(in1.shape, in2.shape)]
- out = np.empty(ps, in1.dtype)
- z = sigtools._correlateND(in1, in2, out, val)
- else:
- ps = [i + j - 1 for i, j in zip(in1.shape, in2.shape)]
- # zero pad input
- in1zpadded = np.zeros(ps, in1.dtype)
- sc = tuple(slice(0, i) for i in in1.shape)
- in1zpadded[sc] = in1.copy()
- if mode == 'full':
- out = np.empty(ps, in1.dtype)
- elif mode == 'same':
- out = np.empty(in1.shape, in1.dtype)
- z = sigtools._correlateND(in1zpadded, in2, out, val)
- if swapped_inputs:
- # Reverse and conjugate to undo the effect of swapping inputs
- z = _reverse_and_conj(z)
- return z
- else:
- raise ValueError("Acceptable method flags are 'auto',"
- " 'direct', or 'fft'.")
- def _centered(arr, newshape):
- # Return the center newshape portion of the array.
- newshape = asarray(newshape)
- currshape = array(arr.shape)
- startind = (currshape - newshape) // 2
- endind = startind + newshape
- myslice = [slice(startind[k], endind[k]) for k in range(len(endind))]
- return arr[tuple(myslice)]
- def fftconvolve(in1, in2, mode="full", axes=None):
- """Convolve two N-dimensional arrays using FFT.
- Convolve `in1` and `in2` using the fast Fourier transform method, with
- the output size determined by the `mode` argument.
- This is generally much faster than `convolve` for large arrays (n > ~500),
- but can be slower when only a few output values are needed, and can only
- output float arrays (int or object array inputs will be cast to float).
- As of v0.19, `convolve` automatically chooses this method or the direct
- method based on an estimation of which is faster.
- Parameters
- ----------
- in1 : array_like
- First input.
- in2 : array_like
- Second input. Should have the same number of dimensions as `in1`.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear convolution
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding. In 'valid' mode, either `in1` or `in2`
- must be at least as large as the other in every dimension.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- axis : tuple, optional
- axes : int or array_like of ints or None, optional
- Axes over which to compute the convolution.
- The default is over all axes.
- Returns
- -------
- out : array
- An N-dimensional array containing a subset of the discrete linear
- convolution of `in1` with `in2`.
- Examples
- --------
- Autocorrelation of white noise is an impulse.
- >>> from scipy import signal
- >>> sig = np.random.randn(1000)
- >>> autocorr = signal.fftconvolve(sig, sig[::-1], mode='full')
- >>> import matplotlib.pyplot as plt
- >>> fig, (ax_orig, ax_mag) = plt.subplots(2, 1)
- >>> ax_orig.plot(sig)
- >>> ax_orig.set_title('White noise')
- >>> ax_mag.plot(np.arange(-len(sig)+1,len(sig)), autocorr)
- >>> ax_mag.set_title('Autocorrelation')
- >>> fig.tight_layout()
- >>> fig.show()
- Gaussian blur implemented using FFT convolution. Notice the dark borders
- around the image, due to the zero-padding beyond its boundaries.
- The `convolve2d` function allows for other types of image boundaries,
- but is far slower.
- >>> from scipy import misc
- >>> face = misc.face(gray=True)
- >>> kernel = np.outer(signal.gaussian(70, 8), signal.gaussian(70, 8))
- >>> blurred = signal.fftconvolve(face, kernel, mode='same')
- >>> fig, (ax_orig, ax_kernel, ax_blurred) = plt.subplots(3, 1,
- ... figsize=(6, 15))
- >>> ax_orig.imshow(face, cmap='gray')
- >>> ax_orig.set_title('Original')
- >>> ax_orig.set_axis_off()
- >>> ax_kernel.imshow(kernel, cmap='gray')
- >>> ax_kernel.set_title('Gaussian kernel')
- >>> ax_kernel.set_axis_off()
- >>> ax_blurred.imshow(blurred, cmap='gray')
- >>> ax_blurred.set_title('Blurred')
- >>> ax_blurred.set_axis_off()
- >>> fig.show()
- """
- in1 = asarray(in1)
- in2 = asarray(in2)
- noaxes = axes is None
- if in1.ndim == in2.ndim == 0: # scalar inputs
- return in1 * in2
- elif in1.ndim != in2.ndim:
- raise ValueError("in1 and in2 should have the same dimensionality")
- elif in1.size == 0 or in2.size == 0: # empty arrays
- return array([])
- _, axes = _init_nd_shape_and_axes_sorted(in1, shape=None, axes=axes)
- if not noaxes and not axes.size:
- raise ValueError("when provided, axes cannot be empty")
- if noaxes:
- other_axes = array([], dtype=np.intc)
- else:
- other_axes = np.setdiff1d(np.arange(in1.ndim), axes)
- s1 = array(in1.shape)
- s2 = array(in2.shape)
- if not np.all((s1[other_axes] == s2[other_axes])
- | (s1[other_axes] == 1) | (s2[other_axes] == 1)):
- raise ValueError("incompatible shapes for in1 and in2:"
- " {0} and {1}".format(in1.shape, in2.shape))
- complex_result = (np.issubdtype(in1.dtype, np.complexfloating)
- or np.issubdtype(in2.dtype, np.complexfloating))
- shape = np.maximum(s1, s2)
- shape[axes] = s1[axes] + s2[axes] - 1
- # Check that input sizes are compatible with 'valid' mode
- if _inputs_swap_needed(mode, s1, s2):
- # Convolution is commutative; order doesn't have any effect on output
- in1, s1, in2, s2 = in2, s2, in1, s1
- # Speed up FFT by padding to optimal size for FFTPACK
- fshape = [fftpack.helper.next_fast_len(d) for d in shape[axes]]
- fslice = tuple([slice(sz) for sz in shape])
- # Pre-1.9 NumPy FFT routines are not threadsafe. For older NumPys, make
- # sure we only call rfftn/irfftn from one thread at a time.
- if not complex_result and (_rfft_mt_safe or _rfft_lock.acquire(False)):
- try:
- sp1 = np.fft.rfftn(in1, fshape, axes=axes)
- sp2 = np.fft.rfftn(in2, fshape, axes=axes)
- ret = np.fft.irfftn(sp1 * sp2, fshape, axes=axes)[fslice].copy()
- finally:
- if not _rfft_mt_safe:
- _rfft_lock.release()
- else:
- # If we're here, it's either because we need a complex result, or we
- # failed to acquire _rfft_lock (meaning rfftn isn't threadsafe and
- # is already in use by another thread). In either case, use the
- # (threadsafe but slower) SciPy complex-FFT routines instead.
- sp1 = fftpack.fftn(in1, fshape, axes=axes)
- sp2 = fftpack.fftn(in2, fshape, axes=axes)
- ret = fftpack.ifftn(sp1 * sp2, axes=axes)[fslice].copy()
- if not complex_result:
- ret = ret.real
- if mode == "full":
- return ret
- elif mode == "same":
- return _centered(ret, s1)
- elif mode == "valid":
- shape_valid = shape.copy()
- shape_valid[axes] = s1[axes] - s2[axes] + 1
- return _centered(ret, shape_valid)
- else:
- raise ValueError("acceptable mode flags are 'valid',"
- " 'same', or 'full'")
- def _numeric_arrays(arrays, kinds='buifc'):
- """
- See if a list of arrays are all numeric.
- Parameters
- ----------
- ndarrays : array or list of arrays
- arrays to check if numeric.
- numeric_kinds : string-like
- The dtypes of the arrays to be checked. If the dtype.kind of
- the ndarrays are not in this string the function returns False and
- otherwise returns True.
- """
- if type(arrays) == ndarray:
- return arrays.dtype.kind in kinds
- for array_ in arrays:
- if array_.dtype.kind not in kinds:
- return False
- return True
- def _prod(iterable):
- """
- Product of a list of numbers.
- Faster than np.prod for short lists like array shapes.
- """
- product = 1
- for x in iterable:
- product *= x
- return product
- def _fftconv_faster(x, h, mode):
- """
- See if using `fftconvolve` or `_correlateND` is faster. The boolean value
- returned depends on the sizes and shapes of the input values.
- The big O ratios were found to hold across different machines, which makes
- sense as it's the ratio that matters (the effective speed of the computer
- is found in both big O constants). Regardless, this had been tuned on an
- early 2015 MacBook Pro with 8GB RAM and an Intel i5 processor.
- """
- if mode == 'full':
- out_shape = [n + k - 1 for n, k in zip(x.shape, h.shape)]
- big_O_constant = 10963.92823819 if x.ndim == 1 else 8899.1104874
- elif mode == 'same':
- out_shape = x.shape
- if x.ndim == 1:
- if h.size <= x.size:
- big_O_constant = 7183.41306773
- else:
- big_O_constant = 856.78174111
- else:
- big_O_constant = 34519.21021589
- elif mode == 'valid':
- out_shape = [n - k + 1 for n, k in zip(x.shape, h.shape)]
- big_O_constant = 41954.28006344 if x.ndim == 1 else 66453.24316434
- else:
- raise ValueError("Acceptable mode flags are 'valid',"
- " 'same', or 'full'.")
- # see whether the Fourier transform convolution method or the direct
- # convolution method is faster (discussed in scikit-image PR #1792)
- direct_time = (x.size * h.size * _prod(out_shape))
- fft_time = sum(n * math.log(n) for n in (x.shape + h.shape +
- tuple(out_shape)))
- return big_O_constant * fft_time < direct_time
- def _reverse_and_conj(x):
- """
- Reverse array `x` in all dimensions and perform the complex conjugate
- """
- reverse = (slice(None, None, -1),) * x.ndim
- return x[reverse].conj()
- def _np_conv_ok(volume, kernel, mode):
- """
- See if numpy supports convolution of `volume` and `kernel` (i.e. both are
- 1D ndarrays and of the appropriate shape). Numpy's 'same' mode uses the
- size of the larger input, while Scipy's uses the size of the first input.
- Invalid mode strings will return False and be caught by the calling func.
- """
- if volume.ndim == kernel.ndim == 1:
- if mode in ('full', 'valid'):
- return True
- elif mode == 'same':
- return volume.size >= kernel.size
- else:
- return False
- def _timeit_fast(stmt="pass", setup="pass", repeat=3):
- """
- Returns the time the statement/function took, in seconds.
- Faster, less precise version of IPython's timeit. `stmt` can be a statement
- written as a string or a callable.
- Will do only 1 loop (like IPython's timeit) with no repetitions
- (unlike IPython) for very slow functions. For fast functions, only does
- enough loops to take 5 ms, which seems to produce similar results (on
- Windows at least), and avoids doing an extraneous cycle that isn't
- measured.
- """
- timer = timeit.Timer(stmt, setup)
- # determine number of calls per rep so total time for 1 rep >= 5 ms
- x = 0
- for p in range(0, 10):
- number = 10**p
- x = timer.timeit(number) # seconds
- if x >= 5e-3 / 10: # 5 ms for final test, 1/10th that for this one
- break
- if x > 1: # second
- # If it's macroscopic, don't bother with repetitions
- best = x
- else:
- number *= 10
- r = timer.repeat(repeat, number)
- best = min(r)
- sec = best / number
- return sec
- def choose_conv_method(in1, in2, mode='full', measure=False):
- """
- Find the fastest convolution/correlation method.
- This primarily exists to be called during the ``method='auto'`` option in
- `convolve` and `correlate`, but can also be used when performing many
- convolutions of the same input shapes and dtypes, determining
- which method to use for all of them, either to avoid the overhead of the
- 'auto' option or to use accurate real-world measurements.
- Parameters
- ----------
- in1 : array_like
- The first argument passed into the convolution function.
- in2 : array_like
- The second argument passed into the convolution function.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear convolution
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- measure : bool, optional
- If True, run and time the convolution of `in1` and `in2` with both
- methods and return the fastest. If False (default), predict the fastest
- method using precomputed values.
- Returns
- -------
- method : str
- A string indicating which convolution method is fastest, either
- 'direct' or 'fft'
- times : dict, optional
- A dictionary containing the times (in seconds) needed for each method.
- This value is only returned if ``measure=True``.
- See Also
- --------
- convolve
- correlate
- Notes
- -----
- For large n, ``measure=False`` is accurate and can quickly determine the
- fastest method to perform the convolution. However, this is not as
- accurate for small n (when any dimension in the input or output is small).
- In practice, we found that this function estimates the faster method up to
- a multiplicative factor of 5 (i.e., the estimated method is *at most* 5
- times slower than the fastest method). The estimation values were tuned on
- an early 2015 MacBook Pro with 8GB RAM but we found that the prediction
- held *fairly* accurately across different machines.
- If ``measure=True``, time the convolutions. Because this function uses
- `fftconvolve`, an error will be thrown if it does not support the inputs.
- There are cases when `fftconvolve` supports the inputs but this function
- returns `direct` (e.g., to protect against floating point integer
- precision).
- .. versionadded:: 0.19
- Examples
- --------
- Estimate the fastest method for a given input:
- >>> from scipy import signal
- >>> a = np.random.randn(1000)
- >>> b = np.random.randn(1000000)
- >>> method = signal.choose_conv_method(a, b, mode='same')
- >>> method
- 'fft'
- This can then be applied to other arrays of the same dtype and shape:
- >>> c = np.random.randn(1000)
- >>> d = np.random.randn(1000000)
- >>> # `method` works with correlate and convolve
- >>> corr1 = signal.correlate(a, b, mode='same', method=method)
- >>> corr2 = signal.correlate(c, d, mode='same', method=method)
- >>> conv1 = signal.convolve(a, b, mode='same', method=method)
- >>> conv2 = signal.convolve(c, d, mode='same', method=method)
- """
- volume = asarray(in1)
- kernel = asarray(in2)
- if measure:
- times = {}
- for method in ['fft', 'direct']:
- times[method] = _timeit_fast(lambda: convolve(volume, kernel,
- mode=mode, method=method))
- chosen_method = 'fft' if times['fft'] < times['direct'] else 'direct'
- return chosen_method, times
- # fftconvolve doesn't support complex256
- fftconv_unsup = "complex256" if sys.maxsize > 2**32 else "complex192"
- if hasattr(np, fftconv_unsup):
- if volume.dtype == fftconv_unsup or kernel.dtype == fftconv_unsup:
- return 'direct'
- # for integer input,
- # catch when more precision required than float provides (representing an
- # integer as float can lose precision in fftconvolve if larger than 2**52)
- if any([_numeric_arrays([x], kinds='ui') for x in [volume, kernel]]):
- max_value = int(np.abs(volume).max()) * int(np.abs(kernel).max())
- max_value *= int(min(volume.size, kernel.size))
- if max_value > 2**np.finfo('float').nmant - 1:
- return 'direct'
- if _numeric_arrays([volume, kernel], kinds='b'):
- return 'direct'
- if _numeric_arrays([volume, kernel]):
- if _fftconv_faster(volume, kernel, mode):
- return 'fft'
- return 'direct'
- def convolve(in1, in2, mode='full', method='auto'):
- """
- Convolve two N-dimensional arrays.
- Convolve `in1` and `in2`, with the output size determined by the
- `mode` argument.
- Parameters
- ----------
- in1 : array_like
- First input.
- in2 : array_like
- Second input. Should have the same number of dimensions as `in1`.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear convolution
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding. In 'valid' mode, either `in1` or `in2`
- must be at least as large as the other in every dimension.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- method : str {'auto', 'direct', 'fft'}, optional
- A string indicating which method to use to calculate the convolution.
- ``direct``
- The convolution is determined directly from sums, the definition of
- convolution.
- ``fft``
- The Fourier Transform is used to perform the convolution by calling
- `fftconvolve`.
- ``auto``
- Automatically chooses direct or Fourier method based on an estimate
- of which is faster (default). See Notes for more detail.
- .. versionadded:: 0.19.0
- Returns
- -------
- convolve : array
- An N-dimensional array containing a subset of the discrete linear
- convolution of `in1` with `in2`.
- See Also
- --------
- numpy.polymul : performs polynomial multiplication (same operation, but
- also accepts poly1d objects)
- choose_conv_method : chooses the fastest appropriate convolution method
- fftconvolve
- Notes
- -----
- By default, `convolve` and `correlate` use ``method='auto'``, which calls
- `choose_conv_method` to choose the fastest method using pre-computed
- values (`choose_conv_method` can also measure real-world timing with a
- keyword argument). Because `fftconvolve` relies on floating point numbers,
- there are certain constraints that may force `method=direct` (more detail
- in `choose_conv_method` docstring).
- Examples
- --------
- Smooth a square pulse using a Hann window:
- >>> from scipy import signal
- >>> sig = np.repeat([0., 1., 0.], 100)
- >>> win = signal.hann(50)
- >>> filtered = signal.convolve(sig, win, mode='same') / sum(win)
- >>> import matplotlib.pyplot as plt
- >>> fig, (ax_orig, ax_win, ax_filt) = plt.subplots(3, 1, sharex=True)
- >>> ax_orig.plot(sig)
- >>> ax_orig.set_title('Original pulse')
- >>> ax_orig.margins(0, 0.1)
- >>> ax_win.plot(win)
- >>> ax_win.set_title('Filter impulse response')
- >>> ax_win.margins(0, 0.1)
- >>> ax_filt.plot(filtered)
- >>> ax_filt.set_title('Filtered signal')
- >>> ax_filt.margins(0, 0.1)
- >>> fig.tight_layout()
- >>> fig.show()
- """
- volume = asarray(in1)
- kernel = asarray(in2)
- if volume.ndim == kernel.ndim == 0:
- return volume * kernel
- elif volume.ndim != kernel.ndim:
- raise ValueError("volume and kernel should have the same "
- "dimensionality")
- if _inputs_swap_needed(mode, volume.shape, kernel.shape):
- # Convolution is commutative; order doesn't have any effect on output
- volume, kernel = kernel, volume
- if method == 'auto':
- method = choose_conv_method(volume, kernel, mode=mode)
- if method == 'fft':
- out = fftconvolve(volume, kernel, mode=mode)
- result_type = np.result_type(volume, kernel)
- if result_type.kind in {'u', 'i'}:
- out = np.around(out)
- return out.astype(result_type)
- elif method == 'direct':
- # fastpath to faster numpy.convolve for 1d inputs when possible
- if _np_conv_ok(volume, kernel, mode):
- return np.convolve(volume, kernel, mode)
- return correlate(volume, _reverse_and_conj(kernel), mode, 'direct')
- else:
- raise ValueError("Acceptable method flags are 'auto',"
- " 'direct', or 'fft'.")
- def order_filter(a, domain, rank):
- """
- Perform an order filter on an N-dimensional array.
- Perform an order filter on the array in. The domain argument acts as a
- mask centered over each pixel. The non-zero elements of domain are
- used to select elements surrounding each input pixel which are placed
- in a list. The list is sorted, and the output for that pixel is the
- element corresponding to rank in the sorted list.
- Parameters
- ----------
- a : ndarray
- The N-dimensional input array.
- domain : array_like
- A mask array with the same number of dimensions as `a`.
- Each dimension should have an odd number of elements.
- rank : int
- A non-negative integer which selects the element from the
- sorted list (0 corresponds to the smallest element, 1 is the
- next smallest element, etc.).
- Returns
- -------
- out : ndarray
- The results of the order filter in an array with the same
- shape as `a`.
- Examples
- --------
- >>> from scipy import signal
- >>> x = np.arange(25).reshape(5, 5)
- >>> domain = np.identity(3)
- >>> x
- array([[ 0, 1, 2, 3, 4],
- [ 5, 6, 7, 8, 9],
- [10, 11, 12, 13, 14],
- [15, 16, 17, 18, 19],
- [20, 21, 22, 23, 24]])
- >>> signal.order_filter(x, domain, 0)
- array([[ 0., 0., 0., 0., 0.],
- [ 0., 0., 1., 2., 0.],
- [ 0., 5., 6., 7., 0.],
- [ 0., 10., 11., 12., 0.],
- [ 0., 0., 0., 0., 0.]])
- >>> signal.order_filter(x, domain, 2)
- array([[ 6., 7., 8., 9., 4.],
- [ 11., 12., 13., 14., 9.],
- [ 16., 17., 18., 19., 14.],
- [ 21., 22., 23., 24., 19.],
- [ 20., 21., 22., 23., 24.]])
- """
- domain = asarray(domain)
- size = domain.shape
- for k in range(len(size)):
- if (size[k] % 2) != 1:
- raise ValueError("Each dimension of domain argument "
- " should have an odd number of elements.")
- return sigtools._order_filterND(a, domain, rank)
- def medfilt(volume, kernel_size=None):
- """
- Perform a median filter on an N-dimensional array.
- Apply a median filter to the input array using a local window-size
- given by `kernel_size`.
- Parameters
- ----------
- volume : array_like
- An N-dimensional input array.
- kernel_size : array_like, optional
- A scalar or an N-length list giving the size of the median filter
- window in each dimension. Elements of `kernel_size` should be odd.
- If `kernel_size` is a scalar, then this scalar is used as the size in
- each dimension. Default size is 3 for each dimension.
- Returns
- -------
- out : ndarray
- An array the same size as input containing the median filtered
- result.
- """
- volume = atleast_1d(volume)
- if kernel_size is None:
- kernel_size = [3] * volume.ndim
- kernel_size = asarray(kernel_size)
- if kernel_size.shape == ():
- kernel_size = np.repeat(kernel_size.item(), volume.ndim)
- for k in range(volume.ndim):
- if (kernel_size[k] % 2) != 1:
- raise ValueError("Each element of kernel_size should be odd.")
- domain = ones(kernel_size)
- numels = product(kernel_size, axis=0)
- order = numels // 2
- return sigtools._order_filterND(volume, domain, order)
- def wiener(im, mysize=None, noise=None):
- """
- Perform a Wiener filter on an N-dimensional array.
- Apply a Wiener filter to the N-dimensional array `im`.
- Parameters
- ----------
- im : ndarray
- An N-dimensional array.
- mysize : int or array_like, optional
- A scalar or an N-length list giving the size of the Wiener filter
- window in each dimension. Elements of mysize should be odd.
- If mysize is a scalar, then this scalar is used as the size
- in each dimension.
- noise : float, optional
- The noise-power to use. If None, then noise is estimated as the
- average of the local variance of the input.
- Returns
- -------
- out : ndarray
- Wiener filtered result with the same shape as `im`.
- """
- im = asarray(im)
- if mysize is None:
- mysize = [3] * im.ndim
- mysize = asarray(mysize)
- if mysize.shape == ():
- mysize = np.repeat(mysize.item(), im.ndim)
- # Estimate the local mean
- lMean = correlate(im, ones(mysize), 'same') / product(mysize, axis=0)
- # Estimate the local variance
- lVar = (correlate(im ** 2, ones(mysize), 'same') /
- product(mysize, axis=0) - lMean ** 2)
- # Estimate the noise power if needed.
- if noise is None:
- noise = mean(ravel(lVar), axis=0)
- res = (im - lMean)
- res *= (1 - noise / lVar)
- res += lMean
- out = where(lVar < noise, lMean, res)
- return out
- def convolve2d(in1, in2, mode='full', boundary='fill', fillvalue=0):
- """
- Convolve two 2-dimensional arrays.
- Convolve `in1` and `in2` with output size determined by `mode`, and
- boundary conditions determined by `boundary` and `fillvalue`.
- Parameters
- ----------
- in1 : array_like
- First input.
- in2 : array_like
- Second input. Should have the same number of dimensions as `in1`.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear convolution
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding. In 'valid' mode, either `in1` or `in2`
- must be at least as large as the other in every dimension.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- boundary : str {'fill', 'wrap', 'symm'}, optional
- A flag indicating how to handle boundaries:
- ``fill``
- pad input arrays with fillvalue. (default)
- ``wrap``
- circular boundary conditions.
- ``symm``
- symmetrical boundary conditions.
- fillvalue : scalar, optional
- Value to fill pad input arrays with. Default is 0.
- Returns
- -------
- out : ndarray
- A 2-dimensional array containing a subset of the discrete linear
- convolution of `in1` with `in2`.
- Examples
- --------
- Compute the gradient of an image by 2D convolution with a complex Scharr
- operator. (Horizontal operator is real, vertical is imaginary.) Use
- symmetric boundary condition to avoid creating edges at the image
- boundaries.
- >>> from scipy import signal
- >>> from scipy import misc
- >>> ascent = misc.ascent()
- >>> scharr = np.array([[ -3-3j, 0-10j, +3 -3j],
- ... [-10+0j, 0+ 0j, +10 +0j],
- ... [ -3+3j, 0+10j, +3 +3j]]) # Gx + j*Gy
- >>> grad = signal.convolve2d(ascent, scharr, boundary='symm', mode='same')
- >>> import matplotlib.pyplot as plt
- >>> fig, (ax_orig, ax_mag, ax_ang) = plt.subplots(3, 1, figsize=(6, 15))
- >>> ax_orig.imshow(ascent, cmap='gray')
- >>> ax_orig.set_title('Original')
- >>> ax_orig.set_axis_off()
- >>> ax_mag.imshow(np.absolute(grad), cmap='gray')
- >>> ax_mag.set_title('Gradient magnitude')
- >>> ax_mag.set_axis_off()
- >>> ax_ang.imshow(np.angle(grad), cmap='hsv') # hsv is cyclic, like angles
- >>> ax_ang.set_title('Gradient orientation')
- >>> ax_ang.set_axis_off()
- >>> fig.show()
- """
- in1 = asarray(in1)
- in2 = asarray(in2)
- if not in1.ndim == in2.ndim == 2:
- raise ValueError('convolve2d inputs must both be 2D arrays')
- if _inputs_swap_needed(mode, in1.shape, in2.shape):
- in1, in2 = in2, in1
- val = _valfrommode(mode)
- bval = _bvalfromboundary(boundary)
- out = sigtools._convolve2d(in1, in2, 1, val, bval, fillvalue)
- return out
- def correlate2d(in1, in2, mode='full', boundary='fill', fillvalue=0):
- """
- Cross-correlate two 2-dimensional arrays.
- Cross correlate `in1` and `in2` with output size determined by `mode`, and
- boundary conditions determined by `boundary` and `fillvalue`.
- Parameters
- ----------
- in1 : array_like
- First input.
- in2 : array_like
- Second input. Should have the same number of dimensions as `in1`.
- mode : str {'full', 'valid', 'same'}, optional
- A string indicating the size of the output:
- ``full``
- The output is the full discrete linear cross-correlation
- of the inputs. (Default)
- ``valid``
- The output consists only of those elements that do not
- rely on the zero-padding. In 'valid' mode, either `in1` or `in2`
- must be at least as large as the other in every dimension.
- ``same``
- The output is the same size as `in1`, centered
- with respect to the 'full' output.
- boundary : str {'fill', 'wrap', 'symm'}, optional
- A flag indicating how to handle boundaries:
- ``fill``
- pad input arrays with fillvalue. (default)
- ``wrap``
- circular boundary conditions.
- ``symm``
- symmetrical boundary conditions.
- fillvalue : scalar, optional
- Value to fill pad input arrays with. Default is 0.
- Returns
- -------
- correlate2d : ndarray
- A 2-dimensional array containing a subset of the discrete linear
- cross-correlation of `in1` with `in2`.
- Examples
- --------
- Use 2D cross-correlation to find the location of a template in a noisy
- image:
- >>> from scipy import signal
- >>> from scipy import misc
- >>> face = misc.face(gray=True) - misc.face(gray=True).mean()
- >>> template = np.copy(face[300:365, 670:750]) # right eye
- >>> template -= template.mean()
- >>> face = face + np.random.randn(*face.shape) * 50 # add noise
- >>> corr = signal.correlate2d(face, template, boundary='symm', mode='same')
- >>> y, x = np.unravel_index(np.argmax(corr), corr.shape) # find the match
- >>> import matplotlib.pyplot as plt
- >>> fig, (ax_orig, ax_template, ax_corr) = plt.subplots(3, 1,
- ... figsize=(6, 15))
- >>> ax_orig.imshow(face, cmap='gray')
- >>> ax_orig.set_title('Original')
- >>> ax_orig.set_axis_off()
- >>> ax_template.imshow(template, cmap='gray')
- >>> ax_template.set_title('Template')
- >>> ax_template.set_axis_off()
- >>> ax_corr.imshow(corr, cmap='gray')
- >>> ax_corr.set_title('Cross-correlation')
- >>> ax_corr.set_axis_off()
- >>> ax_orig.plot(x, y, 'ro')
- >>> fig.show()
- """
- in1 = asarray(in1)
- in2 = asarray(in2)
- if not in1.ndim == in2.ndim == 2:
- raise ValueError('correlate2d inputs must both be 2D arrays')
- swapped_inputs = _inputs_swap_needed(mode, in1.shape, in2.shape)
- if swapped_inputs:
- in1, in2 = in2, in1
- val = _valfrommode(mode)
- bval = _bvalfromboundary(boundary)
- out = sigtools._convolve2d(in1, in2.conj(), 0, val, bval, fillvalue)
- if swapped_inputs:
- out = out[::-1, ::-1]
- return out
- def medfilt2d(input, kernel_size=3):
- """
- Median filter a 2-dimensional array.
- Apply a median filter to the `input` array using a local window-size
- given by `kernel_size` (must be odd).
- Parameters
- ----------
- input : array_like
- A 2-dimensional input array.
- kernel_size : array_like, optional
- A scalar or a list of length 2, giving the size of the
- median filter window in each dimension. Elements of
- `kernel_size` should be odd. If `kernel_size` is a scalar,
- then this scalar is used as the size in each dimension.
- Default is a kernel of size (3, 3).
- Returns
- -------
- out : ndarray
- An array the same size as input containing the median filtered
- result.
- """
- image = asarray(input)
- if kernel_size is None:
- kernel_size = [3] * 2
- kernel_size = asarray(kernel_size)
- if kernel_size.shape == ():
- kernel_size = np.repeat(kernel_size.item(), 2)
- for size in kernel_size:
- if (size % 2) != 1:
- raise ValueError("Each element of kernel_size should be odd.")
- return sigtools._medfilt2d(image, kernel_size)
- def lfilter(b, a, x, axis=-1, zi=None):
- """
- Filter data along one-dimension with an IIR or FIR filter.
- Filter a data sequence, `x`, using a digital filter. This works for many
- fundamental data types (including Object type). The filter is a direct
- form II transposed implementation of the standard difference equation
- (see Notes).
- Parameters
- ----------
- b : array_like
- The numerator coefficient vector in a 1-D sequence.
- a : array_like
- The denominator coefficient vector in a 1-D sequence. If ``a[0]``
- is not 1, then both `a` and `b` are normalized by ``a[0]``.
- x : array_like
- An N-dimensional input array.
- axis : int, optional
- The axis of the input data array along which to apply the
- linear filter. The filter is applied to each subarray along
- this axis. Default is -1.
- zi : array_like, optional
- Initial conditions for the filter delays. It is a vector
- (or array of vectors for an N-dimensional input) of length
- ``max(len(a), len(b)) - 1``. If `zi` is None or is not given then
- initial rest is assumed. See `lfiltic` for more information.
- Returns
- -------
- y : array
- The output of the digital filter.
- zf : array, optional
- If `zi` is None, this is not returned, otherwise, `zf` holds the
- final filter delay values.
- See Also
- --------
- lfiltic : Construct initial conditions for `lfilter`.
- lfilter_zi : Compute initial state (steady state of step response) for
- `lfilter`.
- filtfilt : A forward-backward filter, to obtain a filter with linear phase.
- savgol_filter : A Savitzky-Golay filter.
- sosfilt: Filter data using cascaded second-order sections.
- sosfiltfilt: A forward-backward filter using second-order sections.
- Notes
- -----
- The filter function is implemented as a direct II transposed structure.
- This means that the filter implements::
- a[0]*y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M]
- - a[1]*y[n-1] - ... - a[N]*y[n-N]
- where `M` is the degree of the numerator, `N` is the degree of the
- denominator, and `n` is the sample number. It is implemented using
- the following difference equations (assuming M = N)::
- a[0]*y[n] = b[0] * x[n] + d[0][n-1]
- d[0][n] = b[1] * x[n] - a[1] * y[n] + d[1][n-1]
- d[1][n] = b[2] * x[n] - a[2] * y[n] + d[2][n-1]
- ...
- d[N-2][n] = b[N-1]*x[n] - a[N-1]*y[n] + d[N-1][n-1]
- d[N-1][n] = b[N] * x[n] - a[N] * y[n]
- where `d` are the state variables.
- The rational transfer function describing this filter in the
- z-transform domain is::
- -1 -M
- b[0] + b[1]z + ... + b[M] z
- Y(z) = -------------------------------- X(z)
- -1 -N
- a[0] + a[1]z + ... + a[N] z
- Examples
- --------
- Generate a noisy signal to be filtered:
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> t = np.linspace(-1, 1, 201)
- >>> x = (np.sin(2*np.pi*0.75*t*(1-t) + 2.1) +
- ... 0.1*np.sin(2*np.pi*1.25*t + 1) +
- ... 0.18*np.cos(2*np.pi*3.85*t))
- >>> xn = x + np.random.randn(len(t)) * 0.08
- Create an order 3 lowpass butterworth filter:
- >>> b, a = signal.butter(3, 0.05)
- Apply the filter to xn. Use lfilter_zi to choose the initial condition of
- the filter:
- >>> zi = signal.lfilter_zi(b, a)
- >>> z, _ = signal.lfilter(b, a, xn, zi=zi*xn[0])
- Apply the filter again, to have a result filtered at an order the same as
- filtfilt:
- >>> z2, _ = signal.lfilter(b, a, z, zi=zi*z[0])
- Use filtfilt to apply the filter:
- >>> y = signal.filtfilt(b, a, xn)
- Plot the original signal and the various filtered versions:
- >>> plt.figure
- >>> plt.plot(t, xn, 'b', alpha=0.75)
- >>> plt.plot(t, z, 'r--', t, z2, 'r', t, y, 'k')
- >>> plt.legend(('noisy signal', 'lfilter, once', 'lfilter, twice',
- ... 'filtfilt'), loc='best')
- >>> plt.grid(True)
- >>> plt.show()
- """
- a = np.atleast_1d(a)
- if len(a) == 1:
- # This path only supports types fdgFDGO to mirror _linear_filter below.
- # Any of b, a, x, or zi can set the dtype, but there is no default
- # casting of other types; instead a NotImplementedError is raised.
- b = np.asarray(b)
- a = np.asarray(a)
- if b.ndim != 1 and a.ndim != 1:
- raise ValueError('object of too small depth for desired array')
- x = np.asarray(x)
- inputs = [b, a, x]
- if zi is not None:
- # _linear_filter does not broadcast zi, but does do expansion of
- # singleton dims.
- zi = np.asarray(zi)
- if zi.ndim != x.ndim:
- raise ValueError('object of too small depth for desired array')
- expected_shape = list(x.shape)
- expected_shape[axis] = b.shape[0] - 1
- expected_shape = tuple(expected_shape)
- # check the trivial case where zi is the right shape first
- if zi.shape != expected_shape:
- strides = zi.ndim * [None]
- if axis < 0:
- axis += zi.ndim
- for k in range(zi.ndim):
- if k == axis and zi.shape[k] == expected_shape[k]:
- strides[k] = zi.strides[k]
- elif k != axis and zi.shape[k] == expected_shape[k]:
- strides[k] = zi.strides[k]
- elif k != axis and zi.shape[k] == 1:
- strides[k] = 0
- else:
- raise ValueError('Unexpected shape for zi: expected '
- '%s, found %s.' %
- (expected_shape, zi.shape))
- zi = np.lib.stride_tricks.as_strided(zi, expected_shape,
- strides)
- inputs.append(zi)
- dtype = np.result_type(*inputs)
- if dtype.char not in 'fdgFDGO':
- raise NotImplementedError("input type '%s' not supported" % dtype)
- b = np.array(b, dtype=dtype)
- a = np.array(a, dtype=dtype, copy=False)
- b /= a[0]
- x = np.array(x, dtype=dtype, copy=False)
- out_full = np.apply_along_axis(lambda y: np.convolve(b, y), axis, x)
- ind = out_full.ndim * [slice(None)]
- if zi is not None:
- ind[axis] = slice(zi.shape[axis])
- out_full[tuple(ind)] += zi
- ind[axis] = slice(out_full.shape[axis] - len(b) + 1)
- out = out_full[tuple(ind)]
- if zi is None:
- return out
- else:
- ind[axis] = slice(out_full.shape[axis] - len(b) + 1, None)
- zf = out_full[tuple(ind)]
- return out, zf
- else:
- if zi is None:
- return sigtools._linear_filter(b, a, x, axis)
- else:
- return sigtools._linear_filter(b, a, x, axis, zi)
- def lfiltic(b, a, y, x=None):
- """
- Construct initial conditions for lfilter given input and output vectors.
- Given a linear filter (b, a) and initial conditions on the output `y`
- and the input `x`, return the initial conditions on the state vector zi
- which is used by `lfilter` to generate the output given the input.
- Parameters
- ----------
- b : array_like
- Linear filter term.
- a : array_like
- Linear filter term.
- y : array_like
- Initial conditions.
- If ``N = len(a) - 1``, then ``y = {y[-1], y[-2], ..., y[-N]}``.
- If `y` is too short, it is padded with zeros.
- x : array_like, optional
- Initial conditions.
- If ``M = len(b) - 1``, then ``x = {x[-1], x[-2], ..., x[-M]}``.
- If `x` is not given, its initial conditions are assumed zero.
- If `x` is too short, it is padded with zeros.
- Returns
- -------
- zi : ndarray
- The state vector ``zi = {z_0[-1], z_1[-1], ..., z_K-1[-1]}``,
- where ``K = max(M, N)``.
- See Also
- --------
- lfilter, lfilter_zi
- """
- N = np.size(a) - 1
- M = np.size(b) - 1
- K = max(M, N)
- y = asarray(y)
- if y.dtype.kind in 'bui':
- # ensure calculations are floating point
- y = y.astype(np.float64)
- zi = zeros(K, y.dtype)
- if x is None:
- x = zeros(M, y.dtype)
- else:
- x = asarray(x)
- L = np.size(x)
- if L < M:
- x = r_[x, zeros(M - L)]
- L = np.size(y)
- if L < N:
- y = r_[y, zeros(N - L)]
- for m in range(M):
- zi[m] = np.sum(b[m + 1:] * x[:M - m], axis=0)
- for m in range(N):
- zi[m] -= np.sum(a[m + 1:] * y[:N - m], axis=0)
- return zi
- def deconvolve(signal, divisor):
- """Deconvolves ``divisor`` out of ``signal`` using inverse filtering.
- Returns the quotient and remainder such that
- ``signal = convolve(divisor, quotient) + remainder``
- Parameters
- ----------
- signal : array_like
- Signal data, typically a recorded signal
- divisor : array_like
- Divisor data, typically an impulse response or filter that was
- applied to the original signal
- Returns
- -------
- quotient : ndarray
- Quotient, typically the recovered original signal
- remainder : ndarray
- Remainder
- Examples
- --------
- Deconvolve a signal that's been filtered:
- >>> from scipy import signal
- >>> original = [0, 1, 0, 0, 1, 1, 0, 0]
- >>> impulse_response = [2, 1]
- >>> recorded = signal.convolve(impulse_response, original)
- >>> recorded
- array([0, 2, 1, 0, 2, 3, 1, 0, 0])
- >>> recovered, remainder = signal.deconvolve(recorded, impulse_response)
- >>> recovered
- array([ 0., 1., 0., 0., 1., 1., 0., 0.])
- See Also
- --------
- numpy.polydiv : performs polynomial division (same operation, but
- also accepts poly1d objects)
- """
- num = atleast_1d(signal)
- den = atleast_1d(divisor)
- N = len(num)
- D = len(den)
- if D > N:
- quot = []
- rem = num
- else:
- input = zeros(N - D + 1, float)
- input[0] = 1
- quot = lfilter(num, den, input)
- rem = num - convolve(den, quot, mode='full')
- return quot, rem
- def hilbert(x, N=None, axis=-1):
- """
- Compute the analytic signal, using the Hilbert transform.
- The transformation is done along the last axis by default.
- Parameters
- ----------
- x : array_like
- Signal data. Must be real.
- N : int, optional
- Number of Fourier components. Default: ``x.shape[axis]``
- axis : int, optional
- Axis along which to do the transformation. Default: -1.
- Returns
- -------
- xa : ndarray
- Analytic signal of `x`, of each 1-D array along `axis`
- See Also
- --------
- scipy.fftpack.hilbert : Return Hilbert transform of a periodic sequence x.
- Notes
- -----
- The analytic signal ``x_a(t)`` of signal ``x(t)`` is:
- .. math:: x_a = F^{-1}(F(x) 2U) = x + i y
- where `F` is the Fourier transform, `U` the unit step function,
- and `y` the Hilbert transform of `x`. [1]_
- In other words, the negative half of the frequency spectrum is zeroed
- out, turning the real-valued signal into a complex signal. The Hilbert
- transformed signal can be obtained from ``np.imag(hilbert(x))``, and the
- original signal from ``np.real(hilbert(x))``.
- Examples
- ---------
- In this example we use the Hilbert transform to determine the amplitude
- envelope and instantaneous frequency of an amplitude-modulated signal.
- >>> import numpy as np
- >>> import matplotlib.pyplot as plt
- >>> from scipy.signal import hilbert, chirp
- >>> duration = 1.0
- >>> fs = 400.0
- >>> samples = int(fs*duration)
- >>> t = np.arange(samples) / fs
- We create a chirp of which the frequency increases from 20 Hz to 100 Hz and
- apply an amplitude modulation.
- >>> signal = chirp(t, 20.0, t[-1], 100.0)
- >>> signal *= (1.0 + 0.5 * np.sin(2.0*np.pi*3.0*t) )
- The amplitude envelope is given by magnitude of the analytic signal. The
- instantaneous frequency can be obtained by differentiating the
- instantaneous phase in respect to time. The instantaneous phase corresponds
- to the phase angle of the analytic signal.
- >>> analytic_signal = hilbert(signal)
- >>> amplitude_envelope = np.abs(analytic_signal)
- >>> instantaneous_phase = np.unwrap(np.angle(analytic_signal))
- >>> instantaneous_frequency = (np.diff(instantaneous_phase) /
- ... (2.0*np.pi) * fs)
- >>> fig = plt.figure()
- >>> ax0 = fig.add_subplot(211)
- >>> ax0.plot(t, signal, label='signal')
- >>> ax0.plot(t, amplitude_envelope, label='envelope')
- >>> ax0.set_xlabel("time in seconds")
- >>> ax0.legend()
- >>> ax1 = fig.add_subplot(212)
- >>> ax1.plot(t[1:], instantaneous_frequency)
- >>> ax1.set_xlabel("time in seconds")
- >>> ax1.set_ylim(0.0, 120.0)
- References
- ----------
- .. [1] Wikipedia, "Analytic signal".
- https://en.wikipedia.org/wiki/Analytic_signal
- .. [2] Leon Cohen, "Time-Frequency Analysis", 1995. Chapter 2.
- .. [3] Alan V. Oppenheim, Ronald W. Schafer. Discrete-Time Signal
- Processing, Third Edition, 2009. Chapter 12.
- ISBN 13: 978-1292-02572-8
- """
- x = asarray(x)
- if iscomplexobj(x):
- raise ValueError("x must be real.")
- if N is None:
- N = x.shape[axis]
- if N <= 0:
- raise ValueError("N must be positive.")
- Xf = fftpack.fft(x, N, axis=axis)
- h = zeros(N)
- if N % 2 == 0:
- h[0] = h[N // 2] = 1
- h[1:N // 2] = 2
- else:
- h[0] = 1
- h[1:(N + 1) // 2] = 2
- if x.ndim > 1:
- ind = [newaxis] * x.ndim
- ind[axis] = slice(None)
- h = h[tuple(ind)]
- x = fftpack.ifft(Xf * h, axis=axis)
- return x
- def hilbert2(x, N=None):
- """
- Compute the '2-D' analytic signal of `x`
- Parameters
- ----------
- x : array_like
- 2-D signal data.
- N : int or tuple of two ints, optional
- Number of Fourier components. Default is ``x.shape``
- Returns
- -------
- xa : ndarray
- Analytic signal of `x` taken along axes (0,1).
- References
- ----------
- .. [1] Wikipedia, "Analytic signal",
- https://en.wikipedia.org/wiki/Analytic_signal
- """
- x = atleast_2d(x)
- if x.ndim > 2:
- raise ValueError("x must be 2-D.")
- if iscomplexobj(x):
- raise ValueError("x must be real.")
- if N is None:
- N = x.shape
- elif isinstance(N, int):
- if N <= 0:
- raise ValueError("N must be positive.")
- N = (N, N)
- elif len(N) != 2 or np.any(np.asarray(N) <= 0):
- raise ValueError("When given as a tuple, N must hold exactly "
- "two positive integers")
- Xf = fftpack.fft2(x, N, axes=(0, 1))
- h1 = zeros(N[0], 'd')
- h2 = zeros(N[1], 'd')
- for p in range(2):
- h = eval("h%d" % (p + 1))
- N1 = N[p]
- if N1 % 2 == 0:
- h[0] = h[N1 // 2] = 1
- h[1:N1 // 2] = 2
- else:
- h[0] = 1
- h[1:(N1 + 1) // 2] = 2
- exec("h%d = h" % (p + 1), globals(), locals())
- h = h1[:, newaxis] * h2[newaxis, :]
- k = x.ndim
- while k > 2:
- h = h[:, newaxis]
- k -= 1
- x = fftpack.ifft2(Xf * h, axes=(0, 1))
- return x
- def cmplx_sort(p):
- """Sort roots based on magnitude.
- Parameters
- ----------
- p : array_like
- The roots to sort, as a 1-D array.
- Returns
- -------
- p_sorted : ndarray
- Sorted roots.
- indx : ndarray
- Array of indices needed to sort the input `p`.
- Examples
- --------
- >>> from scipy import signal
- >>> vals = [1, 4, 1+1.j, 3]
- >>> p_sorted, indx = signal.cmplx_sort(vals)
- >>> p_sorted
- array([1.+0.j, 1.+1.j, 3.+0.j, 4.+0.j])
- >>> indx
- array([0, 2, 3, 1])
- """
- p = asarray(p)
- if iscomplexobj(p):
- indx = argsort(abs(p))
- else:
- indx = argsort(p)
- return take(p, indx, 0), indx
- def unique_roots(p, tol=1e-3, rtype='min'):
- """Determine unique roots and their multiplicities from a list of roots.
- Parameters
- ----------
- p : array_like
- The list of roots.
- tol : float, optional
- The tolerance for two roots to be considered equal in terms of
- the distance between them. Default is 1e-3. Refer to Notes about
- the details on roots grouping.
- rtype : {'max', 'maximum', 'min', 'minimum', 'avg', 'mean'}, optional
- How to determine the returned root if multiple roots are within
- `tol` of each other.
- - 'max', 'maximum': pick the maximum of those roots
- - 'min', 'minimum': pick the minimum of those roots
- - 'avg', 'mean': take the average of those roots
- When finding minimum or maximum among complex roots they are compared
- first by the real part and then by the imaginary part.
- Returns
- -------
- unique : ndarray
- The list of unique roots.
- multiplicity : ndarray
- The multiplicity of each root.
- Notes
- -----
- If we have 3 roots ``a``, ``b`` and ``c``, such that ``a`` is close to
- ``b`` and ``b`` is close to ``c`` (distance is less than `tol`), then it
- doesn't necessarily mean that ``a`` is close to ``c``. It means that roots
- grouping is not unique. In this function we use "greedy" grouping going
- through the roots in the order they are given in the input `p`.
- This utility function is not specific to roots but can be used for any
- sequence of values for which uniqueness and multiplicity has to be
- determined. For a more general routine, see `numpy.unique`.
- Examples
- --------
- >>> from scipy import signal
- >>> vals = [0, 1.3, 1.31, 2.8, 1.25, 2.2, 10.3]
- >>> uniq, mult = signal.unique_roots(vals, tol=2e-2, rtype='avg')
- Check which roots have multiplicity larger than 1:
- >>> uniq[mult > 1]
- array([ 1.305])
- """
- if rtype in ['max', 'maximum']:
- reduce = np.max
- elif rtype in ['min', 'minimum']:
- reduce = np.min
- elif rtype in ['avg', 'mean']:
- reduce = np.mean
- else:
- raise ValueError("`rtype` must be one of "
- "{'max', 'maximum', 'min', 'minimum', 'avg', 'mean'}")
- p = np.asarray(p)
- points = np.empty((len(p), 2))
- points[:, 0] = np.real(p)
- points[:, 1] = np.imag(p)
- tree = cKDTree(points)
- p_unique = []
- p_multiplicity = []
- used = np.zeros(len(p), dtype=bool)
- for i in range(len(p)):
- if used[i]:
- continue
- group = tree.query_ball_point(points[i], tol)
- group = [x for x in group if not used[x]]
- p_unique.append(reduce(p[group]))
- p_multiplicity.append(len(group))
- used[group] = True
- return np.asarray(p_unique), np.asarray(p_multiplicity)
- def invres(r, p, k, tol=1e-3, rtype='avg'):
- """
- Compute b(s) and a(s) from partial fraction expansion.
- If `M` is the degree of numerator `b` and `N` the degree of denominator
- `a`::
- b(s) b[0] s**(M) + b[1] s**(M-1) + ... + b[M]
- H(s) = ------ = ------------------------------------------
- a(s) a[0] s**(N) + a[1] s**(N-1) + ... + a[N]
- then the partial-fraction expansion H(s) is defined as::
- r[0] r[1] r[-1]
- = -------- + -------- + ... + --------- + k(s)
- (s-p[0]) (s-p[1]) (s-p[-1])
- If there are any repeated roots (closer together than `tol`), then H(s)
- has terms like::
- r[i] r[i+1] r[i+n-1]
- -------- + ----------- + ... + -----------
- (s-p[i]) (s-p[i])**2 (s-p[i])**n
- This function is used for polynomials in positive powers of s or z,
- such as analog filters or digital filters in controls engineering. For
- negative powers of z (typical for digital filters in DSP), use `invresz`.
- Parameters
- ----------
- r : array_like
- Residues.
- p : array_like
- Poles.
- k : array_like
- Coefficients of the direct polynomial term.
- tol : float, optional
- The tolerance for two roots to be considered equal. Default is 1e-3.
- rtype : {'max', 'min, 'avg'}, optional
- How to determine the returned root if multiple roots are within
- `tol` of each other.
- - 'max': pick the maximum of those roots.
- - 'min': pick the minimum of those roots.
- - 'avg': take the average of those roots.
- Returns
- -------
- b : ndarray
- Numerator polynomial coefficients.
- a : ndarray
- Denominator polynomial coefficients.
- See Also
- --------
- residue, invresz, unique_roots
- """
- extra = k
- p, indx = cmplx_sort(p)
- r = take(r, indx, 0)
- pout, mult = unique_roots(p, tol=tol, rtype=rtype)
- p = []
- for k in range(len(pout)):
- p.extend([pout[k]] * mult[k])
- a = atleast_1d(poly(p))
- if len(extra) > 0:
- b = polymul(extra, a)
- else:
- b = [0]
- indx = 0
- for k in range(len(pout)):
- temp = []
- for l in range(len(pout)):
- if l != k:
- temp.extend([pout[l]] * mult[l])
- for m in range(mult[k]):
- t2 = temp[:]
- t2.extend([pout[k]] * (mult[k] - m - 1))
- b = polyadd(b, r[indx] * atleast_1d(poly(t2)))
- indx += 1
- b = real_if_close(b)
- while allclose(b[0], 0, rtol=1e-14) and (b.shape[-1] > 1):
- b = b[1:]
- return b, a
- def residue(b, a, tol=1e-3, rtype='avg'):
- """
- Compute partial-fraction expansion of b(s) / a(s).
- If `M` is the degree of numerator `b` and `N` the degree of denominator
- `a`::
- b(s) b[0] s**(M) + b[1] s**(M-1) + ... + b[M]
- H(s) = ------ = ------------------------------------------
- a(s) a[0] s**(N) + a[1] s**(N-1) + ... + a[N]
- then the partial-fraction expansion H(s) is defined as::
- r[0] r[1] r[-1]
- = -------- + -------- + ... + --------- + k(s)
- (s-p[0]) (s-p[1]) (s-p[-1])
- If there are any repeated roots (closer together than `tol`), then H(s)
- has terms like::
- r[i] r[i+1] r[i+n-1]
- -------- + ----------- + ... + -----------
- (s-p[i]) (s-p[i])**2 (s-p[i])**n
- This function is used for polynomials in positive powers of s or z,
- such as analog filters or digital filters in controls engineering. For
- negative powers of z (typical for digital filters in DSP), use `residuez`.
- Parameters
- ----------
- b : array_like
- Numerator polynomial coefficients.
- a : array_like
- Denominator polynomial coefficients.
- Returns
- -------
- r : ndarray
- Residues.
- p : ndarray
- Poles.
- k : ndarray
- Coefficients of the direct polynomial term.
- See Also
- --------
- invres, residuez, numpy.poly, unique_roots
- """
- b, a = map(asarray, (b, a))
- rscale = a[0]
- k, b = polydiv(b, a)
- p = roots(a)
- r = p * 0.0
- pout, mult = unique_roots(p, tol=tol, rtype=rtype)
- p = []
- for n in range(len(pout)):
- p.extend([pout[n]] * mult[n])
- p = asarray(p)
- # Compute the residue from the general formula
- indx = 0
- for n in range(len(pout)):
- bn = b.copy()
- pn = []
- for l in range(len(pout)):
- if l != n:
- pn.extend([pout[l]] * mult[l])
- an = atleast_1d(poly(pn))
- # bn(s) / an(s) is (s-po[n])**Nn * b(s) / a(s) where Nn is
- # multiplicity of pole at po[n]
- sig = mult[n]
- for m in range(sig, 0, -1):
- if sig > m:
- # compute next derivative of bn(s) / an(s)
- term1 = polymul(polyder(bn, 1), an)
- term2 = polymul(bn, polyder(an, 1))
- bn = polysub(term1, term2)
- an = polymul(an, an)
- r[indx + m - 1] = (polyval(bn, pout[n]) / polyval(an, pout[n]) /
- factorial(sig - m))
- indx += sig
- return r / rscale, p, k
- def residuez(b, a, tol=1e-3, rtype='avg'):
- """
- Compute partial-fraction expansion of b(z) / a(z).
- If `M` is the degree of numerator `b` and `N` the degree of denominator
- `a`::
- b(z) b[0] + b[1] z**(-1) + ... + b[M] z**(-M)
- H(z) = ------ = ------------------------------------------
- a(z) a[0] + a[1] z**(-1) + ... + a[N] z**(-N)
- then the partial-fraction expansion H(z) is defined as::
- r[0] r[-1]
- = --------------- + ... + ---------------- + k[0] + k[1]z**(-1) ...
- (1-p[0]z**(-1)) (1-p[-1]z**(-1))
- If there are any repeated roots (closer than `tol`), then the partial
- fraction expansion has terms like::
- r[i] r[i+1] r[i+n-1]
- -------------- + ------------------ + ... + ------------------
- (1-p[i]z**(-1)) (1-p[i]z**(-1))**2 (1-p[i]z**(-1))**n
- This function is used for polynomials in negative powers of z,
- such as digital filters in DSP. For positive powers, use `residue`.
- Parameters
- ----------
- b : array_like
- Numerator polynomial coefficients.
- a : array_like
- Denominator polynomial coefficients.
- Returns
- -------
- r : ndarray
- Residues.
- p : ndarray
- Poles.
- k : ndarray
- Coefficients of the direct polynomial term.
- See Also
- --------
- invresz, residue, unique_roots
- """
- b, a = map(asarray, (b, a))
- gain = a[0]
- brev, arev = b[::-1], a[::-1]
- krev, brev = polydiv(brev, arev)
- if krev == []:
- k = []
- else:
- k = krev[::-1]
- b = brev[::-1]
- p = roots(a)
- r = p * 0.0
- pout, mult = unique_roots(p, tol=tol, rtype=rtype)
- p = []
- for n in range(len(pout)):
- p.extend([pout[n]] * mult[n])
- p = asarray(p)
- # Compute the residue from the general formula (for discrete-time)
- # the polynomial is in z**(-1) and the multiplication is by terms
- # like this (1-p[i] z**(-1))**mult[i]. After differentiation,
- # we must divide by (-p[i])**(m-k) as well as (m-k)!
- indx = 0
- for n in range(len(pout)):
- bn = brev.copy()
- pn = []
- for l in range(len(pout)):
- if l != n:
- pn.extend([pout[l]] * mult[l])
- an = atleast_1d(poly(pn))[::-1]
- # bn(z) / an(z) is (1-po[n] z**(-1))**Nn * b(z) / a(z) where Nn is
- # multiplicity of pole at po[n] and b(z) and a(z) are polynomials.
- sig = mult[n]
- for m in range(sig, 0, -1):
- if sig > m:
- # compute next derivative of bn(s) / an(s)
- term1 = polymul(polyder(bn, 1), an)
- term2 = polymul(bn, polyder(an, 1))
- bn = polysub(term1, term2)
- an = polymul(an, an)
- r[indx + m - 1] = (polyval(bn, 1.0 / pout[n]) /
- polyval(an, 1.0 / pout[n]) /
- factorial(sig - m) / (-pout[n]) ** (sig - m))
- indx += sig
- return r / gain, p, k
- def invresz(r, p, k, tol=1e-3, rtype='avg'):
- """
- Compute b(z) and a(z) from partial fraction expansion.
- If `M` is the degree of numerator `b` and `N` the degree of denominator
- `a`::
- b(z) b[0] + b[1] z**(-1) + ... + b[M] z**(-M)
- H(z) = ------ = ------------------------------------------
- a(z) a[0] + a[1] z**(-1) + ... + a[N] z**(-N)
- then the partial-fraction expansion H(z) is defined as::
- r[0] r[-1]
- = --------------- + ... + ---------------- + k[0] + k[1]z**(-1) ...
- (1-p[0]z**(-1)) (1-p[-1]z**(-1))
- If there are any repeated roots (closer than `tol`), then the partial
- fraction expansion has terms like::
- r[i] r[i+1] r[i+n-1]
- -------------- + ------------------ + ... + ------------------
- (1-p[i]z**(-1)) (1-p[i]z**(-1))**2 (1-p[i]z**(-1))**n
- This function is used for polynomials in negative powers of z,
- such as digital filters in DSP. For positive powers, use `invres`.
- Parameters
- ----------
- r : array_like
- Residues.
- p : array_like
- Poles.
- k : array_like
- Coefficients of the direct polynomial term.
- tol : float, optional
- The tolerance for two roots to be considered equal. Default is 1e-3.
- rtype : {'max', 'min, 'avg'}, optional
- How to determine the returned root if multiple roots are within
- `tol` of each other.
- - 'max': pick the maximum of those roots.
- - 'min': pick the minimum of those roots.
- - 'avg': take the average of those roots.
- Returns
- -------
- b : ndarray
- Numerator polynomial coefficients.
- a : ndarray
- Denominator polynomial coefficients.
- See Also
- --------
- residuez, unique_roots, invres
- """
- extra = asarray(k)
- p, indx = cmplx_sort(p)
- r = take(r, indx, 0)
- pout, mult = unique_roots(p, tol=tol, rtype=rtype)
- p = []
- for k in range(len(pout)):
- p.extend([pout[k]] * mult[k])
- a = atleast_1d(poly(p))
- if len(extra) > 0:
- b = polymul(extra, a)
- else:
- b = [0]
- indx = 0
- brev = asarray(b)[::-1]
- for k in range(len(pout)):
- temp = []
- # Construct polynomial which does not include any of this root
- for l in range(len(pout)):
- if l != k:
- temp.extend([pout[l]] * mult[l])
- for m in range(mult[k]):
- t2 = temp[:]
- t2.extend([pout[k]] * (mult[k] - m - 1))
- brev = polyadd(brev, (r[indx] * atleast_1d(poly(t2)))[::-1])
- indx += 1
- b = real_if_close(brev[::-1])
- return b, a
- def resample(x, num, t=None, axis=0, window=None):
- """
- Resample `x` to `num` samples using Fourier method along the given axis.
- The resampled signal starts at the same value as `x` but is sampled
- with a spacing of ``len(x) / num * (spacing of x)``. Because a
- Fourier method is used, the signal is assumed to be periodic.
- Parameters
- ----------
- x : array_like
- The data to be resampled.
- num : int
- The number of samples in the resampled signal.
- t : array_like, optional
- If `t` is given, it is assumed to be the sample positions
- associated with the signal data in `x`.
- axis : int, optional
- The axis of `x` that is resampled. Default is 0.
- window : array_like, callable, string, float, or tuple, optional
- Specifies the window applied to the signal in the Fourier
- domain. See below for details.
- Returns
- -------
- resampled_x or (resampled_x, resampled_t)
- Either the resampled array, or, if `t` was given, a tuple
- containing the resampled array and the corresponding resampled
- positions.
- See Also
- --------
- decimate : Downsample the signal after applying an FIR or IIR filter.
- resample_poly : Resample using polyphase filtering and an FIR filter.
- Notes
- -----
- The argument `window` controls a Fourier-domain window that tapers
- the Fourier spectrum before zero-padding to alleviate ringing in
- the resampled values for sampled signals you didn't intend to be
- interpreted as band-limited.
- If `window` is a function, then it is called with a vector of inputs
- indicating the frequency bins (i.e. fftfreq(x.shape[axis]) ).
- If `window` is an array of the same length as `x.shape[axis]` it is
- assumed to be the window to be applied directly in the Fourier
- domain (with dc and low-frequency first).
- For any other type of `window`, the function `scipy.signal.get_window`
- is called to generate the window.
- The first sample of the returned vector is the same as the first
- sample of the input vector. The spacing between samples is changed
- from ``dx`` to ``dx * len(x) / num``.
- If `t` is not None, then it represents the old sample positions,
- and the new sample positions will be returned as well as the new
- samples.
- As noted, `resample` uses FFT transformations, which can be very
- slow if the number of input or output samples is large and prime;
- see `scipy.fftpack.fft`.
- Examples
- --------
- Note that the end of the resampled data rises to meet the first
- sample of the next cycle:
- >>> from scipy import signal
- >>> x = np.linspace(0, 10, 20, endpoint=False)
- >>> y = np.cos(-x**2/6.0)
- >>> f = signal.resample(y, 100)
- >>> xnew = np.linspace(0, 10, 100, endpoint=False)
- >>> import matplotlib.pyplot as plt
- >>> plt.plot(x, y, 'go-', xnew, f, '.-', 10, y[0], 'ro')
- >>> plt.legend(['data', 'resampled'], loc='best')
- >>> plt.show()
- """
- x = asarray(x)
- X = fftpack.fft(x, axis=axis)
- Nx = x.shape[axis]
- if window is not None:
- if callable(window):
- W = window(fftpack.fftfreq(Nx))
- elif isinstance(window, ndarray):
- if window.shape != (Nx,):
- raise ValueError('window must have the same length as data')
- W = window
- else:
- W = fftpack.ifftshift(get_window(window, Nx))
- newshape = [1] * x.ndim
- newshape[axis] = len(W)
- W.shape = newshape
- X = X * W
- W.shape = (Nx,)
- sl = [slice(None)] * x.ndim
- newshape = list(x.shape)
- newshape[axis] = num
- N = int(np.minimum(num, Nx))
- Y = zeros(newshape, 'D')
- sl[axis] = slice(0, (N + 1) // 2)
- Y[tuple(sl)] = X[tuple(sl)]
- sl[axis] = slice(-(N - 1) // 2, None)
- Y[tuple(sl)] = X[tuple(sl)]
- if N % 2 == 0: # special treatment if low number of points is even. So far we have set Y[-N/2]=X[-N/2]
- if N < Nx: # if downsampling
- sl[axis] = slice(N//2,N//2+1,None) # select the component at frequency N/2
- Y[tuple(sl)] += X[tuple(sl)] # add the component of X at N/2
- elif N < num: # if upsampling
- sl[axis] = slice(num-N//2,num-N//2+1,None) # select the component at frequency -N/2
- Y[tuple(sl)] /= 2 # halve the component at -N/2
- temp = Y[tuple(sl)]
- sl[axis] = slice(N//2,N//2+1,None) # select the component at +N/2
- Y[tuple(sl)] = temp # set that equal to the component at -N/2
- y = fftpack.ifft(Y, axis=axis) * (float(num) / float(Nx))
- if x.dtype.char not in ['F', 'D']:
- y = y.real
- if t is None:
- return y
- else:
- new_t = arange(0, num) * (t[1] - t[0]) * Nx / float(num) + t[0]
- return y, new_t
- def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0)):
- """
- Resample `x` along the given axis using polyphase filtering.
- The signal `x` is upsampled by the factor `up`, a zero-phase low-pass
- FIR filter is applied, and then it is downsampled by the factor `down`.
- The resulting sample rate is ``up / down`` times the original sample
- rate. Values beyond the boundary of the signal are assumed to be zero
- during the filtering step.
- Parameters
- ----------
- x : array_like
- The data to be resampled.
- up : int
- The upsampling factor.
- down : int
- The downsampling factor.
- axis : int, optional
- The axis of `x` that is resampled. Default is 0.
- window : string, tuple, or array_like, optional
- Desired window to use to design the low-pass filter, or the FIR filter
- coefficients to employ. See below for details.
- Returns
- -------
- resampled_x : array
- The resampled array.
- See Also
- --------
- decimate : Downsample the signal after applying an FIR or IIR filter.
- resample : Resample up or down using the FFT method.
- Notes
- -----
- This polyphase method will likely be faster than the Fourier method
- in `scipy.signal.resample` when the number of samples is large and
- prime, or when the number of samples is large and `up` and `down`
- share a large greatest common denominator. The length of the FIR
- filter used will depend on ``max(up, down) // gcd(up, down)``, and
- the number of operations during polyphase filtering will depend on
- the filter length and `down` (see `scipy.signal.upfirdn` for details).
- The argument `window` specifies the FIR low-pass filter design.
- If `window` is an array_like it is assumed to be the FIR filter
- coefficients. Note that the FIR filter is applied after the upsampling
- step, so it should be designed to operate on a signal at a sampling
- frequency higher than the original by a factor of `up//gcd(up, down)`.
- This function's output will be centered with respect to this array, so it
- is best to pass a symmetric filter with an odd number of samples if, as
- is usually the case, a zero-phase filter is desired.
- For any other type of `window`, the functions `scipy.signal.get_window`
- and `scipy.signal.firwin` are called to generate the appropriate filter
- coefficients.
- The first sample of the returned vector is the same as the first
- sample of the input vector. The spacing between samples is changed
- from ``dx`` to ``dx * down / float(up)``.
- Examples
- --------
- Note that the end of the resampled data rises to meet the first
- sample of the next cycle for the FFT method, and gets closer to zero
- for the polyphase method:
- >>> from scipy import signal
- >>> x = np.linspace(0, 10, 20, endpoint=False)
- >>> y = np.cos(-x**2/6.0)
- >>> f_fft = signal.resample(y, 100)
- >>> f_poly = signal.resample_poly(y, 100, 20)
- >>> xnew = np.linspace(0, 10, 100, endpoint=False)
- >>> import matplotlib.pyplot as plt
- >>> plt.plot(xnew, f_fft, 'b.-', xnew, f_poly, 'r.-')
- >>> plt.plot(x, y, 'ko-')
- >>> plt.plot(10, y[0], 'bo', 10, 0., 'ro') # boundaries
- >>> plt.legend(['resample', 'resamp_poly', 'data'], loc='best')
- >>> plt.show()
- """
- x = asarray(x)
- if up != int(up):
- raise ValueError("up must be an integer")
- if down != int(down):
- raise ValueError("down must be an integer")
- up = int(up)
- down = int(down)
- if up < 1 or down < 1:
- raise ValueError('up and down must be >= 1')
- # Determine our up and down factors
- # Use a rational approximation to save computation time on really long
- # signals
- g_ = gcd(up, down)
- up //= g_
- down //= g_
- if up == down == 1:
- return x.copy()
- n_out = x.shape[axis] * up
- n_out = n_out // down + bool(n_out % down)
- if isinstance(window, (list, np.ndarray)):
- window = array(window) # use array to force a copy (we modify it)
- if window.ndim > 1:
- raise ValueError('window must be 1-D')
- half_len = (window.size - 1) // 2
- h = window
- else:
- # Design a linear-phase low-pass FIR filter
- max_rate = max(up, down)
- f_c = 1. / max_rate # cutoff of FIR filter (rel. to Nyquist)
- half_len = 10 * max_rate # reasonable cutoff for our sinc-like function
- h = firwin(2 * half_len + 1, f_c, window=window)
- h *= up
- # Zero-pad our filter to put the output samples at the center
- n_pre_pad = (down - half_len % down)
- n_post_pad = 0
- n_pre_remove = (half_len + n_pre_pad) // down
- # We should rarely need to do this given our filter lengths...
- while _output_len(len(h) + n_pre_pad + n_post_pad, x.shape[axis],
- up, down) < n_out + n_pre_remove:
- n_post_pad += 1
- h = np.concatenate((np.zeros(n_pre_pad, dtype=h.dtype), h,
- np.zeros(n_post_pad, dtype=h.dtype)))
- n_pre_remove_end = n_pre_remove + n_out
- # filter then remove excess
- y = upfirdn(h, x, up, down, axis=axis)
- keep = [slice(None), ]*x.ndim
- keep[axis] = slice(n_pre_remove, n_pre_remove_end)
- return y[tuple(keep)]
- def vectorstrength(events, period):
- '''
- Determine the vector strength of the events corresponding to the given
- period.
- The vector strength is a measure of phase synchrony, how well the
- timing of the events is synchronized to a single period of a periodic
- signal.
- If multiple periods are used, calculate the vector strength of each.
- This is called the "resonating vector strength".
- Parameters
- ----------
- events : 1D array_like
- An array of time points containing the timing of the events.
- period : float or array_like
- The period of the signal that the events should synchronize to.
- The period is in the same units as `events`. It can also be an array
- of periods, in which case the outputs are arrays of the same length.
- Returns
- -------
- strength : float or 1D array
- The strength of the synchronization. 1.0 is perfect synchronization
- and 0.0 is no synchronization. If `period` is an array, this is also
- an array with each element containing the vector strength at the
- corresponding period.
- phase : float or array
- The phase that the events are most strongly synchronized to in radians.
- If `period` is an array, this is also an array with each element
- containing the phase for the corresponding period.
- References
- ----------
- van Hemmen, JL, Longtin, A, and Vollmayr, AN. Testing resonating vector
- strength: Auditory system, electric fish, and noise.
- Chaos 21, 047508 (2011);
- :doi:`10.1063/1.3670512`.
- van Hemmen, JL. Vector strength after Goldberg, Brown, and von Mises:
- biological and mathematical perspectives. Biol Cybern.
- 2013 Aug;107(4):385-96. :doi:`10.1007/s00422-013-0561-7`.
- van Hemmen, JL and Vollmayr, AN. Resonating vector strength: what happens
- when we vary the "probing" frequency while keeping the spike times
- fixed. Biol Cybern. 2013 Aug;107(4):491-94.
- :doi:`10.1007/s00422-013-0560-8`.
- '''
- events = asarray(events)
- period = asarray(period)
- if events.ndim > 1:
- raise ValueError('events cannot have dimensions more than 1')
- if period.ndim > 1:
- raise ValueError('period cannot have dimensions more than 1')
- # we need to know later if period was originally a scalar
- scalarperiod = not period.ndim
- events = atleast_2d(events)
- period = atleast_2d(period)
- if (period <= 0).any():
- raise ValueError('periods must be positive')
- # this converts the times to vectors
- vectors = exp(dot(2j*pi/period.T, events))
- # the vector strength is just the magnitude of the mean of the vectors
- # the vector phase is the angle of the mean of the vectors
- vectormean = mean(vectors, axis=1)
- strength = abs(vectormean)
- phase = angle(vectormean)
- # if the original period was a scalar, return scalars
- if scalarperiod:
- strength = strength[0]
- phase = phase[0]
- return strength, phase
- def detrend(data, axis=-1, type='linear', bp=0):
- """
- Remove linear trend along axis from data.
- Parameters
- ----------
- data : array_like
- The input data.
- axis : int, optional
- The axis along which to detrend the data. By default this is the
- last axis (-1).
- type : {'linear', 'constant'}, optional
- The type of detrending. If ``type == 'linear'`` (default),
- the result of a linear least-squares fit to `data` is subtracted
- from `data`.
- If ``type == 'constant'``, only the mean of `data` is subtracted.
- bp : array_like of ints, optional
- A sequence of break points. If given, an individual linear fit is
- performed for each part of `data` between two break points.
- Break points are specified as indices into `data`.
- Returns
- -------
- ret : ndarray
- The detrended input data.
- Examples
- --------
- >>> from scipy import signal
- >>> randgen = np.random.RandomState(9)
- >>> npoints = 1000
- >>> noise = randgen.randn(npoints)
- >>> x = 3 + 2*np.linspace(0, 1, npoints) + noise
- >>> (signal.detrend(x) - noise).max() < 0.01
- True
- """
- if type not in ['linear', 'l', 'constant', 'c']:
- raise ValueError("Trend type must be 'linear' or 'constant'.")
- data = asarray(data)
- dtype = data.dtype.char
- if dtype not in 'dfDF':
- dtype = 'd'
- if type in ['constant', 'c']:
- ret = data - expand_dims(mean(data, axis), axis)
- return ret
- else:
- dshape = data.shape
- N = dshape[axis]
- bp = sort(unique(r_[0, bp, N]))
- if np.any(bp > N):
- raise ValueError("Breakpoints must be less than length "
- "of data along given axis.")
- Nreg = len(bp) - 1
- # Restructure data so that axis is along first dimension and
- # all other dimensions are collapsed into second dimension
- rnk = len(dshape)
- if axis < 0:
- axis = axis + rnk
- newdims = r_[axis, 0:axis, axis + 1:rnk]
- newdata = reshape(transpose(data, tuple(newdims)),
- (N, _prod(dshape) // N))
- newdata = newdata.copy() # make sure we have a copy
- if newdata.dtype.char not in 'dfDF':
- newdata = newdata.astype(dtype)
- # Find leastsq fit and remove it for each piece
- for m in range(Nreg):
- Npts = bp[m + 1] - bp[m]
- A = ones((Npts, 2), dtype)
- A[:, 0] = cast[dtype](arange(1, Npts + 1) * 1.0 / Npts)
- sl = slice(bp[m], bp[m + 1])
- coef, resids, rank, s = linalg.lstsq(A, newdata[sl])
- newdata[sl] = newdata[sl] - dot(A, coef)
- # Put data back in original shape.
- tdshape = take(dshape, newdims, 0)
- ret = reshape(newdata, tuple(tdshape))
- vals = list(range(1, rnk))
- olddims = vals[:axis] + [0] + vals[axis:]
- ret = transpose(ret, tuple(olddims))
- return ret
- def lfilter_zi(b, a):
- """
- Construct initial conditions for lfilter for step response steady-state.
- Compute an initial state `zi` for the `lfilter` function that corresponds
- to the steady state of the step response.
- A typical use of this function is to set the initial state so that the
- output of the filter starts at the same value as the first element of
- the signal to be filtered.
- Parameters
- ----------
- b, a : array_like (1-D)
- The IIR filter coefficients. See `lfilter` for more
- information.
- Returns
- -------
- zi : 1-D ndarray
- The initial state for the filter.
- See Also
- --------
- lfilter, lfiltic, filtfilt
- Notes
- -----
- A linear filter with order m has a state space representation (A, B, C, D),
- for which the output y of the filter can be expressed as::
- z(n+1) = A*z(n) + B*x(n)
- y(n) = C*z(n) + D*x(n)
- where z(n) is a vector of length m, A has shape (m, m), B has shape
- (m, 1), C has shape (1, m) and D has shape (1, 1) (assuming x(n) is
- a scalar). lfilter_zi solves::
- zi = A*zi + B
- In other words, it finds the initial condition for which the response
- to an input of all ones is a constant.
- Given the filter coefficients `a` and `b`, the state space matrices
- for the transposed direct form II implementation of the linear filter,
- which is the implementation used by scipy.signal.lfilter, are::
- A = scipy.linalg.companion(a).T
- B = b[1:] - a[1:]*b[0]
- assuming `a[0]` is 1.0; if `a[0]` is not 1, `a` and `b` are first
- divided by a[0].
- Examples
- --------
- The following code creates a lowpass Butterworth filter. Then it
- applies that filter to an array whose values are all 1.0; the
- output is also all 1.0, as expected for a lowpass filter. If the
- `zi` argument of `lfilter` had not been given, the output would have
- shown the transient signal.
- >>> from numpy import array, ones
- >>> from scipy.signal import lfilter, lfilter_zi, butter
- >>> b, a = butter(5, 0.25)
- >>> zi = lfilter_zi(b, a)
- >>> y, zo = lfilter(b, a, ones(10), zi=zi)
- >>> y
- array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
- Another example:
- >>> x = array([0.5, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0])
- >>> y, zf = lfilter(b, a, x, zi=zi*x[0])
- >>> y
- array([ 0.5 , 0.5 , 0.5 , 0.49836039, 0.48610528,
- 0.44399389, 0.35505241])
- Note that the `zi` argument to `lfilter` was computed using
- `lfilter_zi` and scaled by `x[0]`. Then the output `y` has no
- transient until the input drops from 0.5 to 0.0.
- """
- # FIXME: Can this function be replaced with an appropriate
- # use of lfiltic? For example, when b,a = butter(N,Wn),
- # lfiltic(b, a, y=numpy.ones_like(a), x=numpy.ones_like(b)).
- #
- # We could use scipy.signal.normalize, but it uses warnings in
- # cases where a ValueError is more appropriate, and it allows
- # b to be 2D.
- b = np.atleast_1d(b)
- if b.ndim != 1:
- raise ValueError("Numerator b must be 1-D.")
- a = np.atleast_1d(a)
- if a.ndim != 1:
- raise ValueError("Denominator a must be 1-D.")
- while len(a) > 1 and a[0] == 0.0:
- a = a[1:]
- if a.size < 1:
- raise ValueError("There must be at least one nonzero `a` coefficient.")
- if a[0] != 1.0:
- # Normalize the coefficients so a[0] == 1.
- b = b / a[0]
- a = a / a[0]
- n = max(len(a), len(b))
- # Pad a or b with zeros so they are the same length.
- if len(a) < n:
- a = np.r_[a, np.zeros(n - len(a))]
- elif len(b) < n:
- b = np.r_[b, np.zeros(n - len(b))]
- IminusA = np.eye(n - 1) - linalg.companion(a).T
- B = b[1:] - a[1:] * b[0]
- # Solve zi = A*zi + B
- zi = np.linalg.solve(IminusA, B)
- # For future reference: we could also use the following
- # explicit formulas to solve the linear system:
- #
- # zi = np.zeros(n - 1)
- # zi[0] = B.sum() / IminusA[:,0].sum()
- # asum = 1.0
- # csum = 0.0
- # for k in range(1,n-1):
- # asum += a[k]
- # csum += b[k] - a[k]*b[0]
- # zi[k] = asum*zi[0] - csum
- return zi
- def sosfilt_zi(sos):
- """
- Construct initial conditions for sosfilt for step response steady-state.
- Compute an initial state `zi` for the `sosfilt` function that corresponds
- to the steady state of the step response.
- A typical use of this function is to set the initial state so that the
- output of the filter starts at the same value as the first element of
- the signal to be filtered.
- Parameters
- ----------
- sos : array_like
- Array of second-order filter coefficients, must have shape
- ``(n_sections, 6)``. See `sosfilt` for the SOS filter format
- specification.
- Returns
- -------
- zi : ndarray
- Initial conditions suitable for use with ``sosfilt``, shape
- ``(n_sections, 2)``.
- See Also
- --------
- sosfilt, zpk2sos
- Notes
- -----
- .. versionadded:: 0.16.0
- Examples
- --------
- Filter a rectangular pulse that begins at time 0, with and without
- the use of the `zi` argument of `scipy.signal.sosfilt`.
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> sos = signal.butter(9, 0.125, output='sos')
- >>> zi = signal.sosfilt_zi(sos)
- >>> x = (np.arange(250) < 100).astype(int)
- >>> f1 = signal.sosfilt(sos, x)
- >>> f2, zo = signal.sosfilt(sos, x, zi=zi)
- >>> plt.plot(x, 'k--', label='x')
- >>> plt.plot(f1, 'b', alpha=0.5, linewidth=2, label='filtered')
- >>> plt.plot(f2, 'g', alpha=0.25, linewidth=4, label='filtered with zi')
- >>> plt.legend(loc='best')
- >>> plt.show()
- """
- sos = np.asarray(sos)
- if sos.ndim != 2 or sos.shape[1] != 6:
- raise ValueError('sos must be shape (n_sections, 6)')
- n_sections = sos.shape[0]
- zi = np.empty((n_sections, 2))
- scale = 1.0
- for section in range(n_sections):
- b = sos[section, :3]
- a = sos[section, 3:]
- zi[section] = scale * lfilter_zi(b, a)
- # If H(z) = B(z)/A(z) is this section's transfer function, then
- # b.sum()/a.sum() is H(1), the gain at omega=0. That's the steady
- # state value of this section's step response.
- scale *= b.sum() / a.sum()
- return zi
- def _filtfilt_gust(b, a, x, axis=-1, irlen=None):
- """Forward-backward IIR filter that uses Gustafsson's method.
- Apply the IIR filter defined by `(b,a)` to `x` twice, first forward
- then backward, using Gustafsson's initial conditions [1]_.
- Let ``y_fb`` be the result of filtering first forward and then backward,
- and let ``y_bf`` be the result of filtering first backward then forward.
- Gustafsson's method is to compute initial conditions for the forward
- pass and the backward pass such that ``y_fb == y_bf``.
- Parameters
- ----------
- b : scalar or 1-D ndarray
- Numerator coefficients of the filter.
- a : scalar or 1-D ndarray
- Denominator coefficients of the filter.
- x : ndarray
- Data to be filtered.
- axis : int, optional
- Axis of `x` to be filtered. Default is -1.
- irlen : int or None, optional
- The length of the nonnegligible part of the impulse response.
- If `irlen` is None, or if the length of the signal is less than
- ``2 * irlen``, then no part of the impulse response is ignored.
- Returns
- -------
- y : ndarray
- The filtered data.
- x0 : ndarray
- Initial condition for the forward filter.
- x1 : ndarray
- Initial condition for the backward filter.
- Notes
- -----
- Typically the return values `x0` and `x1` are not needed by the
- caller. The intended use of these return values is in unit tests.
- References
- ----------
- .. [1] F. Gustaffson. Determining the initial states in forward-backward
- filtering. Transactions on Signal Processing, 46(4):988-992, 1996.
- """
- # In the comments, "Gustafsson's paper" and [1] refer to the
- # paper referenced in the docstring.
- b = np.atleast_1d(b)
- a = np.atleast_1d(a)
- order = max(len(b), len(a)) - 1
- if order == 0:
- # The filter is just scalar multiplication, with no state.
- scale = (b[0] / a[0])**2
- y = scale * x
- return y, np.array([]), np.array([])
- if axis != -1 or axis != x.ndim - 1:
- # Move the axis containing the data to the end.
- x = np.swapaxes(x, axis, x.ndim - 1)
- # n is the number of samples in the data to be filtered.
- n = x.shape[-1]
- if irlen is None or n <= 2*irlen:
- m = n
- else:
- m = irlen
- # Create Obs, the observability matrix (called O in the paper).
- # This matrix can be interpreted as the operator that propagates
- # an arbitrary initial state to the output, assuming the input is
- # zero.
- # In Gustafsson's paper, the forward and backward filters are not
- # necessarily the same, so he has both O_f and O_b. We use the same
- # filter in both directions, so we only need O. The same comment
- # applies to S below.
- Obs = np.zeros((m, order))
- zi = np.zeros(order)
- zi[0] = 1
- Obs[:, 0] = lfilter(b, a, np.zeros(m), zi=zi)[0]
- for k in range(1, order):
- Obs[k:, k] = Obs[:-k, 0]
- # Obsr is O^R (Gustafsson's notation for row-reversed O)
- Obsr = Obs[::-1]
- # Create S. S is the matrix that applies the filter to the reversed
- # propagated initial conditions. That is,
- # out = S.dot(zi)
- # is the same as
- # tmp, _ = lfilter(b, a, zeros(), zi=zi) # Propagate ICs.
- # out = lfilter(b, a, tmp[::-1]) # Reverse and filter.
- # Equations (5) & (6) of [1]
- S = lfilter(b, a, Obs[::-1], axis=0)
- # Sr is S^R (row-reversed S)
- Sr = S[::-1]
- # M is [(S^R - O), (O^R - S)]
- if m == n:
- M = np.hstack((Sr - Obs, Obsr - S))
- else:
- # Matrix described in section IV of [1].
- M = np.zeros((2*m, 2*order))
- M[:m, :order] = Sr - Obs
- M[m:, order:] = Obsr - S
- # Naive forward-backward and backward-forward filters.
- # These have large transients because the filters use zero initial
- # conditions.
- y_f = lfilter(b, a, x)
- y_fb = lfilter(b, a, y_f[..., ::-1])[..., ::-1]
- y_b = lfilter(b, a, x[..., ::-1])[..., ::-1]
- y_bf = lfilter(b, a, y_b)
- delta_y_bf_fb = y_bf - y_fb
- if m == n:
- delta = delta_y_bf_fb
- else:
- start_m = delta_y_bf_fb[..., :m]
- end_m = delta_y_bf_fb[..., -m:]
- delta = np.concatenate((start_m, end_m), axis=-1)
- # ic_opt holds the "optimal" initial conditions.
- # The following code computes the result shown in the formula
- # of the paper between equations (6) and (7).
- if delta.ndim == 1:
- ic_opt = linalg.lstsq(M, delta)[0]
- else:
- # Reshape delta so it can be used as an array of multiple
- # right-hand-sides in linalg.lstsq.
- delta2d = delta.reshape(-1, delta.shape[-1]).T
- ic_opt0 = linalg.lstsq(M, delta2d)[0].T
- ic_opt = ic_opt0.reshape(delta.shape[:-1] + (M.shape[-1],))
- # Now compute the filtered signal using equation (7) of [1].
- # First, form [S^R, O^R] and call it W.
- if m == n:
- W = np.hstack((Sr, Obsr))
- else:
- W = np.zeros((2*m, 2*order))
- W[:m, :order] = Sr
- W[m:, order:] = Obsr
- # Equation (7) of [1] says
- # Y_fb^opt = Y_fb^0 + W * [x_0^opt; x_{N-1}^opt]
- # `wic` is (almost) the product on the right.
- # W has shape (m, 2*order), and ic_opt has shape (..., 2*order),
- # so we can't use W.dot(ic_opt). Instead, we dot ic_opt with W.T,
- # so wic has shape (..., m).
- wic = ic_opt.dot(W.T)
- # `wic` is "almost" the product of W and the optimal ICs in equation
- # (7)--if we're using a truncated impulse response (m < n), `wic`
- # contains only the adjustments required for the ends of the signal.
- # Here we form y_opt, taking this into account if necessary.
- y_opt = y_fb
- if m == n:
- y_opt += wic
- else:
- y_opt[..., :m] += wic[..., :m]
- y_opt[..., -m:] += wic[..., -m:]
- x0 = ic_opt[..., :order]
- x1 = ic_opt[..., -order:]
- if axis != -1 or axis != x.ndim - 1:
- # Restore the data axis to its original position.
- x0 = np.swapaxes(x0, axis, x.ndim - 1)
- x1 = np.swapaxes(x1, axis, x.ndim - 1)
- y_opt = np.swapaxes(y_opt, axis, x.ndim - 1)
- return y_opt, x0, x1
- def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad',
- irlen=None):
- """
- Apply a digital filter forward and backward to a signal.
- This function applies a linear digital filter twice, once forward and
- once backwards. The combined filter has zero phase and a filter order
- twice that of the original.
- The function provides options for handling the edges of the signal.
- Parameters
- ----------
- b : (N,) array_like
- The numerator coefficient vector of the filter.
- a : (N,) array_like
- The denominator coefficient vector of the filter. If ``a[0]``
- is not 1, then both `a` and `b` are normalized by ``a[0]``.
- x : array_like
- The array of data to be filtered.
- axis : int, optional
- The axis of `x` to which the filter is applied.
- Default is -1.
- padtype : str or None, optional
- Must be 'odd', 'even', 'constant', or None. This determines the
- type of extension to use for the padded signal to which the filter
- is applied. If `padtype` is None, no padding is used. The default
- is 'odd'.
- padlen : int or None, optional
- The number of elements by which to extend `x` at both ends of
- `axis` before applying the filter. This value must be less than
- ``x.shape[axis] - 1``. ``padlen=0`` implies no padding.
- The default value is ``3 * max(len(a), len(b))``.
- method : str, optional
- Determines the method for handling the edges of the signal, either
- "pad" or "gust". When `method` is "pad", the signal is padded; the
- type of padding is determined by `padtype` and `padlen`, and `irlen`
- is ignored. When `method` is "gust", Gustafsson's method is used,
- and `padtype` and `padlen` are ignored.
- irlen : int or None, optional
- When `method` is "gust", `irlen` specifies the length of the
- impulse response of the filter. If `irlen` is None, no part
- of the impulse response is ignored. For a long signal, specifying
- `irlen` can significantly improve the performance of the filter.
- Returns
- -------
- y : ndarray
- The filtered output with the same shape as `x`.
- See Also
- --------
- sosfiltfilt, lfilter_zi, lfilter, lfiltic, savgol_filter, sosfilt
- Notes
- -----
- When `method` is "pad", the function pads the data along the given axis
- in one of three ways: odd, even or constant. The odd and even extensions
- have the corresponding symmetry about the end point of the data. The
- constant extension extends the data with the values at the end points. On
- both the forward and backward passes, the initial condition of the
- filter is found by using `lfilter_zi` and scaling it by the end point of
- the extended data.
- When `method` is "gust", Gustafsson's method [1]_ is used. Initial
- conditions are chosen for the forward and backward passes so that the
- forward-backward filter gives the same result as the backward-forward
- filter.
- The option to use Gustaffson's method was added in scipy version 0.16.0.
- References
- ----------
- .. [1] F. Gustaffson, "Determining the initial states in forward-backward
- filtering", Transactions on Signal Processing, Vol. 46, pp. 988-992,
- 1996.
- Examples
- --------
- The examples will use several functions from `scipy.signal`.
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- First we create a one second signal that is the sum of two pure sine
- waves, with frequencies 5 Hz and 250 Hz, sampled at 2000 Hz.
- >>> t = np.linspace(0, 1.0, 2001)
- >>> xlow = np.sin(2 * np.pi * 5 * t)
- >>> xhigh = np.sin(2 * np.pi * 250 * t)
- >>> x = xlow + xhigh
- Now create a lowpass Butterworth filter with a cutoff of 0.125 times
- the Nyquist frequency, or 125 Hz, and apply it to ``x`` with `filtfilt`.
- The result should be approximately ``xlow``, with no phase shift.
- >>> b, a = signal.butter(8, 0.125)
- >>> y = signal.filtfilt(b, a, x, padlen=150)
- >>> np.abs(y - xlow).max()
- 9.1086182074789912e-06
- We get a fairly clean result for this artificial example because
- the odd extension is exact, and with the moderately long padding,
- the filter's transients have dissipated by the time the actual data
- is reached. In general, transient effects at the edges are
- unavoidable.
- The following example demonstrates the option ``method="gust"``.
- First, create a filter.
- >>> b, a = signal.ellip(4, 0.01, 120, 0.125) # Filter to be applied.
- >>> np.random.seed(123456)
- `sig` is a random input signal to be filtered.
- >>> n = 60
- >>> sig = np.random.randn(n)**3 + 3*np.random.randn(n).cumsum()
- Apply `filtfilt` to `sig`, once using the Gustafsson method, and
- once using padding, and plot the results for comparison.
- >>> fgust = signal.filtfilt(b, a, sig, method="gust")
- >>> fpad = signal.filtfilt(b, a, sig, padlen=50)
- >>> plt.plot(sig, 'k-', label='input')
- >>> plt.plot(fgust, 'b-', linewidth=4, label='gust')
- >>> plt.plot(fpad, 'c-', linewidth=1.5, label='pad')
- >>> plt.legend(loc='best')
- >>> plt.show()
- The `irlen` argument can be used to improve the performance
- of Gustafsson's method.
- Estimate the impulse response length of the filter.
- >>> z, p, k = signal.tf2zpk(b, a)
- >>> eps = 1e-9
- >>> r = np.max(np.abs(p))
- >>> approx_impulse_len = int(np.ceil(np.log(eps) / np.log(r)))
- >>> approx_impulse_len
- 137
- Apply the filter to a longer signal, with and without the `irlen`
- argument. The difference between `y1` and `y2` is small. For long
- signals, using `irlen` gives a significant performance improvement.
- >>> x = np.random.randn(5000)
- >>> y1 = signal.filtfilt(b, a, x, method='gust')
- >>> y2 = signal.filtfilt(b, a, x, method='gust', irlen=approx_impulse_len)
- >>> print(np.max(np.abs(y1 - y2)))
- 1.80056858312e-10
- """
- b = np.atleast_1d(b)
- a = np.atleast_1d(a)
- x = np.asarray(x)
- if method not in ["pad", "gust"]:
- raise ValueError("method must be 'pad' or 'gust'.")
- if method == "gust":
- y, z1, z2 = _filtfilt_gust(b, a, x, axis=axis, irlen=irlen)
- return y
- # method == "pad"
- edge, ext = _validate_pad(padtype, padlen, x, axis,
- ntaps=max(len(a), len(b)))
- # Get the steady state of the filter's step response.
- zi = lfilter_zi(b, a)
- # Reshape zi and create x0 so that zi*x0 broadcasts
- # to the correct value for the 'zi' keyword argument
- # to lfilter.
- zi_shape = [1] * x.ndim
- zi_shape[axis] = zi.size
- zi = np.reshape(zi, zi_shape)
- x0 = axis_slice(ext, stop=1, axis=axis)
- # Forward filter.
- (y, zf) = lfilter(b, a, ext, axis=axis, zi=zi * x0)
- # Backward filter.
- # Create y0 so zi*y0 broadcasts appropriately.
- y0 = axis_slice(y, start=-1, axis=axis)
- (y, zf) = lfilter(b, a, axis_reverse(y, axis=axis), axis=axis, zi=zi * y0)
- # Reverse y.
- y = axis_reverse(y, axis=axis)
- if edge > 0:
- # Slice the actual signal from the extended signal.
- y = axis_slice(y, start=edge, stop=-edge, axis=axis)
- return y
- def _validate_pad(padtype, padlen, x, axis, ntaps):
- """Helper to validate padding for filtfilt"""
- if padtype not in ['even', 'odd', 'constant', None]:
- raise ValueError(("Unknown value '%s' given to padtype. padtype "
- "must be 'even', 'odd', 'constant', or None.") %
- padtype)
- if padtype is None:
- padlen = 0
- if padlen is None:
- # Original padding; preserved for backwards compatibility.
- edge = ntaps * 3
- else:
- edge = padlen
- # x's 'axis' dimension must be bigger than edge.
- if x.shape[axis] <= edge:
- raise ValueError("The length of the input vector x must be at least "
- "padlen, which is %d." % edge)
- if padtype is not None and edge > 0:
- # Make an extension of length `edge` at each
- # end of the input array.
- if padtype == 'even':
- ext = even_ext(x, edge, axis=axis)
- elif padtype == 'odd':
- ext = odd_ext(x, edge, axis=axis)
- else:
- ext = const_ext(x, edge, axis=axis)
- else:
- ext = x
- return edge, ext
- def sosfilt(sos, x, axis=-1, zi=None):
- """
- Filter data along one dimension using cascaded second-order sections.
- Filter a data sequence, `x`, using a digital IIR filter defined by
- `sos`. This is implemented by performing `lfilter` for each
- second-order section. See `lfilter` for details.
- Parameters
- ----------
- sos : array_like
- Array of second-order filter coefficients, must have shape
- ``(n_sections, 6)``. Each row corresponds to a second-order
- section, with the first three columns providing the numerator
- coefficients and the last three providing the denominator
- coefficients.
- x : array_like
- An N-dimensional input array.
- axis : int, optional
- The axis of the input data array along which to apply the
- linear filter. The filter is applied to each subarray along
- this axis. Default is -1.
- zi : array_like, optional
- Initial conditions for the cascaded filter delays. It is a (at
- least 2D) vector of shape ``(n_sections, ..., 2, ...)``, where
- ``..., 2, ...`` denotes the shape of `x`, but with ``x.shape[axis]``
- replaced by 2. If `zi` is None or is not given then initial rest
- (i.e. all zeros) is assumed.
- Note that these initial conditions are *not* the same as the initial
- conditions given by `lfiltic` or `lfilter_zi`.
- Returns
- -------
- y : ndarray
- The output of the digital filter.
- zf : ndarray, optional
- If `zi` is None, this is not returned, otherwise, `zf` holds the
- final filter delay values.
- See Also
- --------
- zpk2sos, sos2zpk, sosfilt_zi, sosfiltfilt, sosfreqz
- Notes
- -----
- The filter function is implemented as a series of second-order filters
- with direct-form II transposed structure. It is designed to minimize
- numerical precision errors for high-order filters.
- .. versionadded:: 0.16.0
- Examples
- --------
- Plot a 13th-order filter's impulse response using both `lfilter` and
- `sosfilt`, showing the instability that results from trying to do a
- 13th-order filter in a single stage (the numerical error pushes some poles
- outside of the unit circle):
- >>> import matplotlib.pyplot as plt
- >>> from scipy import signal
- >>> b, a = signal.ellip(13, 0.009, 80, 0.05, output='ba')
- >>> sos = signal.ellip(13, 0.009, 80, 0.05, output='sos')
- >>> x = signal.unit_impulse(700)
- >>> y_tf = signal.lfilter(b, a, x)
- >>> y_sos = signal.sosfilt(sos, x)
- >>> plt.plot(y_tf, 'r', label='TF')
- >>> plt.plot(y_sos, 'k', label='SOS')
- >>> plt.legend(loc='best')
- >>> plt.show()
- """
- x = np.asarray(x)
- sos, n_sections = _validate_sos(sos)
- use_zi = zi is not None
- if use_zi:
- zi = np.asarray(zi)
- x_zi_shape = list(x.shape)
- x_zi_shape[axis] = 2
- x_zi_shape = tuple([n_sections] + x_zi_shape)
- if zi.shape != x_zi_shape:
- raise ValueError('Invalid zi shape. With axis=%r, an input with '
- 'shape %r, and an sos array with %d sections, zi '
- 'must have shape %r, got %r.' %
- (axis, x.shape, n_sections, x_zi_shape, zi.shape))
- zf = zeros_like(zi)
- for section in range(n_sections):
- if use_zi:
- x, zf[section] = lfilter(sos[section, :3], sos[section, 3:],
- x, axis, zi=zi[section])
- else:
- x = lfilter(sos[section, :3], sos[section, 3:], x, axis)
- out = (x, zf) if use_zi else x
- return out
- def sosfiltfilt(sos, x, axis=-1, padtype='odd', padlen=None):
- """
- A forward-backward digital filter using cascaded second-order sections.
- See `filtfilt` for more complete information about this method.
- Parameters
- ----------
- sos : array_like
- Array of second-order filter coefficients, must have shape
- ``(n_sections, 6)``. Each row corresponds to a second-order
- section, with the first three columns providing the numerator
- coefficients and the last three providing the denominator
- coefficients.
- x : array_like
- The array of data to be filtered.
- axis : int, optional
- The axis of `x` to which the filter is applied.
- Default is -1.
- padtype : str or None, optional
- Must be 'odd', 'even', 'constant', or None. This determines the
- type of extension to use for the padded signal to which the filter
- is applied. If `padtype` is None, no padding is used. The default
- is 'odd'.
- padlen : int or None, optional
- The number of elements by which to extend `x` at both ends of
- `axis` before applying the filter. This value must be less than
- ``x.shape[axis] - 1``. ``padlen=0`` implies no padding.
- The default value is::
- 3 * (2 * len(sos) + 1 - min((sos[:, 2] == 0).sum(),
- (sos[:, 5] == 0).sum()))
- The extra subtraction at the end attempts to compensate for poles
- and zeros at the origin (e.g. for odd-order filters) to yield
- equivalent estimates of `padlen` to those of `filtfilt` for
- second-order section filters built with `scipy.signal` functions.
- Returns
- -------
- y : ndarray
- The filtered output with the same shape as `x`.
- See Also
- --------
- filtfilt, sosfilt, sosfilt_zi, sosfreqz
- Notes
- -----
- .. versionadded:: 0.18.0
- Examples
- --------
- >>> from scipy.signal import sosfiltfilt, butter
- >>> import matplotlib.pyplot as plt
- Create an interesting signal to filter.
- >>> n = 201
- >>> t = np.linspace(0, 1, n)
- >>> np.random.seed(123)
- >>> x = 1 + (t < 0.5) - 0.25*t**2 + 0.05*np.random.randn(n)
- Create a lowpass Butterworth filter, and use it to filter `x`.
- >>> sos = butter(4, 0.125, output='sos')
- >>> y = sosfiltfilt(sos, x)
- For comparison, apply an 8th order filter using `sosfilt`. The filter
- is initialized using the mean of the first four values of `x`.
- >>> from scipy.signal import sosfilt, sosfilt_zi
- >>> sos8 = butter(8, 0.125, output='sos')
- >>> zi = x[:4].mean() * sosfilt_zi(sos8)
- >>> y2, zo = sosfilt(sos8, x, zi=zi)
- Plot the results. Note that the phase of `y` matches the input, while
- `y2` has a significant phase delay.
- >>> plt.plot(t, x, alpha=0.5, label='x(t)')
- >>> plt.plot(t, y, label='y(t)')
- >>> plt.plot(t, y2, label='y2(t)')
- >>> plt.legend(framealpha=1, shadow=True)
- >>> plt.grid(alpha=0.25)
- >>> plt.xlabel('t')
- >>> plt.show()
- """
- sos, n_sections = _validate_sos(sos)
- # `method` is "pad"...
- ntaps = 2 * n_sections + 1
- ntaps -= min((sos[:, 2] == 0).sum(), (sos[:, 5] == 0).sum())
- edge, ext = _validate_pad(padtype, padlen, x, axis,
- ntaps=ntaps)
- # These steps follow the same form as filtfilt with modifications
- zi = sosfilt_zi(sos) # shape (n_sections, 2) --> (n_sections, ..., 2, ...)
- zi_shape = [1] * x.ndim
- zi_shape[axis] = 2
- zi.shape = [n_sections] + zi_shape
- x_0 = axis_slice(ext, stop=1, axis=axis)
- (y, zf) = sosfilt(sos, ext, axis=axis, zi=zi * x_0)
- y_0 = axis_slice(y, start=-1, axis=axis)
- (y, zf) = sosfilt(sos, axis_reverse(y, axis=axis), axis=axis, zi=zi * y_0)
- y = axis_reverse(y, axis=axis)
- if edge > 0:
- y = axis_slice(y, start=edge, stop=-edge, axis=axis)
- return y
- def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True):
- """
- Downsample the signal after applying an anti-aliasing filter.
- By default, an order 8 Chebyshev type I filter is used. A 30 point FIR
- filter with Hamming window is used if `ftype` is 'fir'.
- Parameters
- ----------
- x : array_like
- The signal to be downsampled, as an N-dimensional array.
- q : int
- The downsampling factor. When using IIR downsampling, it is recommended
- to call `decimate` multiple times for downsampling factors higher than
- 13.
- n : int, optional
- The order of the filter (1 less than the length for 'fir'). Defaults to
- 8 for 'iir' and 20 times the downsampling factor for 'fir'.
- ftype : str {'iir', 'fir'} or ``dlti`` instance, optional
- If 'iir' or 'fir', specifies the type of lowpass filter. If an instance
- of an `dlti` object, uses that object to filter before downsampling.
- axis : int, optional
- The axis along which to decimate.
- zero_phase : bool, optional
- Prevent phase shift by filtering with `filtfilt` instead of `lfilter`
- when using an IIR filter, and shifting the outputs back by the filter's
- group delay when using an FIR filter. The default value of ``True`` is
- recommended, since a phase shift is generally not desired.
- .. versionadded:: 0.18.0
- Returns
- -------
- y : ndarray
- The down-sampled signal.
- See Also
- --------
- resample : Resample up or down using the FFT method.
- resample_poly : Resample using polyphase filtering and an FIR filter.
- Notes
- -----
- The ``zero_phase`` keyword was added in 0.18.0.
- The possibility to use instances of ``dlti`` as ``ftype`` was added in
- 0.18.0.
- """
- x = asarray(x)
- q = operator.index(q)
- if n is not None:
- n = operator.index(n)
- if ftype == 'fir':
- if n is None:
- half_len = 10 * q # reasonable cutoff for our sinc-like function
- n = 2 * half_len
- b, a = firwin(n+1, 1. / q, window='hamming'), 1.
- elif ftype == 'iir':
- if n is None:
- n = 8
- system = dlti(*cheby1(n, 0.05, 0.8 / q))
- b, a = system.num, system.den
- elif isinstance(ftype, dlti):
- system = ftype._as_tf() # Avoids copying if already in TF form
- b, a = system.num, system.den
- else:
- raise ValueError('invalid ftype')
- sl = [slice(None)] * x.ndim
- a = np.asarray(a)
- if a.size == 1: # FIR case
- b = b / a
- if zero_phase:
- y = resample_poly(x, 1, q, axis=axis, window=b)
- else:
- # upfirdn is generally faster than lfilter by a factor equal to the
- # downsampling factor, since it only calculates the needed outputs
- n_out = x.shape[axis] // q + bool(x.shape[axis] % q)
- y = upfirdn(b, x, up=1, down=q, axis=axis)
- sl[axis] = slice(None, n_out, None)
- else: # IIR case
- if zero_phase:
- y = filtfilt(b, a, x, axis=axis)
- else:
- y = lfilter(b, a, x, axis=axis)
- sl[axis] = slice(None, None, q)
- return y[tuple(sl)]
|