Package: pfilter
pfilterBasic Python particle filter. Plain SIR filtering, with various resampling algorithms. Written to be simple and clear; not necessarily most efficient or most flexible implementation. Depends onNumPyonly.UsesThis repo is useful for understanding how a particle filter works, or a quick way to develop a custom filter of your own from a relatively simple codebase.AlternativesThere are more mature and sophisticated packages for probabilistic filtering in Python (especially for Kalman filtering) if you want an off-the-shelf solution:Particle filteringparticlesExtensive particle filtering, including smoothing and quasi-SMC algorithmsFilterPyProvides extensive Kalman filtering and basic particle filtering.pyfilterprovides Unscented Kalman Filtering, Sequential Importance Resampling and Auxiliary Particle Filter models, and has a number of advanced algorithms implemented, with PyTorch backend.Kalman filteringpykalmanEasy to use Kalman Filter, Extended Kalman Filter and Unscented Kalman Filter implementationssimdkalmanFast implmentations of plain Kalman filter banks.torch-kalmanPyTorch implementation of Kalman filters, including Pandas dataframe support.InstallationAvailable via PyPI:pip install pfilterOr install the git version:pip install git+https://github.com/johnhw/pfilter.gitUsageCreate aParticleFilterobject, then callupdate(observation)with an observation array to update the state of the particle filter.Callingupdate()without an observation will update the model without any data, i.e. perform a prediction step only.ModelInternal state space ofddimensionsObservation space ofhdimensionsnparticles estimating state in each time stepParticles are represented as an(n,d)matrix of states, one state per row. Observations are generated from this matrix into an(n,h)matrix of hypothesized observations via the observation function.FunctionsYou need to specify at the minimum:anobservation functionobserve_fn(state (n,d)) => observation matrix (n,h)which will return a predicted observation for an internal state.a function that samples from aninitial distributionsprior_fn => state matrix (n,d)for all of the internal state variables. These are usually distributions fromscipy.stats. The utility functionindependent_samplemakes it easy to concatenate sampling functions to sample the whole state vector.aweight functionweight_fn(hyp_observed (n,h), real_observed (h,)) => weight vector (n,)which specifies how well each of thehyp_observedarrays match the real observationreal_observed. This must produce a strictly positive weight value for each hypothesized observation, where larger means more similar. This is often an RBF kernel or similar.Typically, you would also specify:dynamicsa functiondynamics_fn(state (n,d)) => predicted_state (n,d)to update the state based on internal (forward prediction) dynamics, and adiffusiona functionnoise_fn(predicted_state (n,d)) => noisy_state (n,d)to add diffusion into the sampling process (though you could also merge into the dynamics).You might also specify:Internal weightinga functioninternal_weight_fn(state (n,d)) => weight vector (n,)which provides a weighting to apply on top of the weight function based oninternalstate. This is useful to impose penalties or to include learned inverse models in the inference.Post-processing transform functiona functiontransform_fn(state (n,d), weights (n,)) => states(n, k)which can apply a post-processing transform and store the result intransformed_particlesMissing observationsIf you want to be able to deal with partial missing values in the observations, the weight function should support masked arrays. Thesquared_error(a,b)function inpfilter.pydoes this, for example.Passing values to functionsSometimes it is useful to pass inputs to callback functions likedynamics_fn(x)at each time step. You can do this by giving keyword arguments toupdate().If you callpf.update(y, t=5)allof the functionsdynamics_fn, weight_fn, noise_fn, internal_weight_fn, observe_fnwill receive the keyword argumentt=5. ALlkwargsare forwarded to these calls. You can just ignore them if not used (e.g. definedynamics_fn = lambda x, **kwargs: real_dynamics(x)) but this can be useful for propagating inputs that are neither internal states nor observed states to the filter. If nokwargsare given toupdate, then no extra arguments are passed to any of callbacks.AttributesTheParticleFilterobject will have the following useful attributes after updating:original_particlesthe(n,d)collection of particles in the last update stepmean_statethe(d,)expectation of the statemean_hypothesizedthe(h,)expectation of the hypothesized observationscov_statethe(d,d)covariance matrix of the statemap_statethe(d,)most likely statemap_hypothesizedthe(h,)most likely hypothesized observationweightsthe(n,)normalised weights of each particleIn equationsExampleFor example, assuming we observe 32x32 images and want to track a moving circle. Assume the internal state we are estimating is the 4D vector (x, y, dx, dy), with 200 particlesfrompfilterimportParticleFilter,gaussian_noise,squared_error,independent_samplecolumns=["x","y","radius","dx","dy"]fromscipy.statsimportnorm,gamma,uniform# prior sampling function for each variable# (assumes x and y are coordinates in the range 0-32)prior_fn=independent_sample([uniform(loc=0,scale=32).rvs,uniform(loc=0,scale=32).rvs,gamma(a=2,loc=0,scale=10).rvs,norm(loc=0,scale=0.5).rvs,norm(loc=0,scale=0.5).rvs])# very simple linear dynamics: x += dxdefvelocity(x):xp=np.array(x)xp[0:2]+=xp[3:5]returnxp# create the filterpf=pfilter.ParticleFilter(prior_fn=prior_fn,observe_fn=blob,n_particles=200,dynamics_fn=velocity,noise_fn=lambdax:gaussian_noise(x,sigmas=[0.2,0.2,0.1,0.05,0.05]),weight_fn=lambdax,y:squared_error(x,y,sigma=2),resample_proportion=0.1,column_names=columns)# assuming image of the same dimensions/type as blob will producepf.update(image)blob(200, 4) -> (200, 1024) which draws a blob on an image of size 32x32 (1024 pixels) for each internal state, our observation functionvelocity(200, 4) -> (200, 4), our dynamics function, which just applies a single Euler step integrating the velocityprior_fnwhich generates a (200,4) initial random stategaussian_noise(200, 4) -> (200,4) which adds noise to the internal statesquared_error((200,1024), (1024,)) -> (200,) the similarity measurementSee the notebookexamples/example_filter.pyfor a working example usingskimageandOpenCVwhich tracks a moving white circle.