FluxPrint
FluxPrint is an open-source Python package implementing flux footprint models
for eddy covariance data analysis. It provides footprint-model implementations
behind a single, consistent interface so researchers can compare spatially
resolved fluxes with field measurements, and add new models by following a small
convention. It is designed for interoperability with ecosystem flux datasets
(e.g. FLUXNET). See Figure 1 for the conceptual scheme.
Figure 1. Conceptual scheme for FluxPrint.
Why FluxPrint?
Most eddy-covariance users already have Kljun's official FFP scripts or EddyPro's built-in footprint estimates. FluxPrint adds, on top of the same science:
- Verified fidelity: the Kljun et al. (2015) implementation is pinned against the original FFP code (vendored in this repository) by regression tests asserting bitwise-identical fields — a dedicated CI job fails if any registered model ever diverges from its reference implementation.
- A batch workflow: one call takes a half-hourly table (FLUXNET/ICOS
style), validates and estimates missing micrometeorology per record, and
returns a typed
FootprintSeriesready for climatologies. - A typed object API: georeferencing, source-area contours, plots, NetCDF/GeoTIFF/Shapefile export, and provenance metadata on every result.
Features
- Footprint models behind one interface, selected by name: Kljun et al.
(2015) and Hsieh et al. (2000, with the Detto et al. 2006 crosswind
expansion); Kormann & Meixner (2001) is
planned. A new model is only its
physics — a per-record kernel registered with
@footprint_modelinherits the grid, record loop, validation, smoothing and provenance. - A typed footprint object (
Footprint): a 2-D source-area field on a fixed grid centred on the tower, plusFootprintSeriesfor time-ordered stacks. - Two coordinate frames: a local, tower-centred metric grid, and a
georeferenced projected grid (
georeference()); lon/lat is display-only. - Source-area contours:
contours()for the 50/80% isopleths,plot()for a quick look,to_shapefile()for GIS. - Serialization: NetCDF is the native format (CF grid mapping included), with GeoTIFF conversion; outputs carry provenance (package/model version, citation, settings).
- Aggregation: collapse a
FootprintSeriesinto a climatology.
Installation
pip install fluxprint
Or from source for development:
pip install git+https://github.com/pedrohenriquecoimbra/fluxprint
import fluxprint is light: the geo/plotting stack (xarray/netcdf4,
rasterio, pyproj, fiona/shapely, matplotlib) is imported lazily by
the operations that need it. Those libraries are still installed as
dependencies today; a future release
will move them into the already-declared extras (fluxprint[netcdf],
[tiff], [crs], [shapefile]; [viz] adds folium for interactive
maps, [all] installs everything).
Quickstart
From a met table to a climatology (the batch path)
import pandas as pd
import fluxprint
data = pd.read_csv("halfhours.csv", na_values=[-9999]) # FLUXNET-style table
climatology = fluxprint.wrapper(
data=data,
zm=20.0, # aerodynamic height: z - d, see note below
pblh=1500.0, # boundary-layer height [m] - not in FLUXNET tables, so
# supply it (or a PBLH/BLH column, or opt into the crude
# 1000 m constant with fill_all=True)
tower=(4321000.0, 3210000.0), # tower position, for georeferencing
tower_crs="EPSG:3035",
dst="climatology.nc", # optional: write the result
)
Columns are matched case-insensitively with common aliases (USTAR/u*,
WD/wind_direction, WS/wind_speed, OL/L, SIGMA_V/v_sd, ...;
H, the sensible heat flux, is matched exactly so FFP's lowercase h — the
boundary-layer height — is never mistaken for it). Missing variables are
estimated when physically possible — e.g. the Obukhov length from
USTAR/H/TA/PA — and every estimated variable is recorded in the
result's attrs["estimated_inputs"]. Crude constant fallbacks (including the
pblh=1000 constant) require an explicit fill_all=True and warn loudly.
Use fluxprint.calculate_footprint(data, by="timestamp_column", ...) for one
footprint per group (a FootprintSeries) instead of a single climatology.
zm is the aerodynamic height. All models define
zm = z - d(measurement height above the zero-plane displacement), not the instrument height. Over vegetation the difference is substantial. If you passmeasurement_heighttogether withdisplacement(orcanopy_height, using d ≈ 0.67·h), FluxPrint deriveszmfor you.
Compute a single footprint directly
from fluxprint.model import get_model
kljun = get_model("kljun2015")
fp = kljun(
zm=2.0, # measurement height above displacement [m]
z0=0.01, # roughness length [m] (or pass umean instead)
ustar=0.5, # friction velocity [m s-1]
pblh=1000.0, # boundary-layer height [m]
mo_length=-50.0, # Obukhov length [m]
v_sigma=0.5, # std. dev. of lateral velocity [m s-1]
wind_dir=180.0, # wind direction [deg] (orients the grid north-up)
dx=2.0, # grid spacing [m]
tower=(4321000.0, 3210000.0),
tower_crs="EPSG:3035",
)
fp.total() # fraction of the flux captured by the grid (also in attrs)
fp.peak_xy() # (x, y) of the footprint peak, metres from the tower
fp.contours([0.5, 0.8]) # 50%/80% source-area isopleths
fp.plot(rs=[0.5, 0.8]) # quick-look plot (matplotlib)
Inputs may be scalars (one record) or equal-length sequences (composited into a
single footprint, fp.n records). Records failing the model's validity checks
(including NaN gaps) are rejected per record.
Georeference and export
geo = fp.georeference("EPSG:3035") # local metres -> projected coords (needs pyproj)
geo.to_netcdf("footprint.nc") # needs xarray/netcdf4; CF grid mapping included
geo.to_tiff("footprint.tif") # needs rasterio
geo.to_shapefile("contours.shp", rs=[0.5, 0.8]) # needs fiona
Adding a model
A model is a callable mapping micrometeorological inputs to one 2-D Footprint
in the local frame. Register it by name and it becomes selectable everywhere:
from fluxprint.model import register_model
from fluxprint.footprint import Footprint
@register_model("my_model", description="My footprint parameterisation")
def calc(*, zm, ustar, pblh, mo_length, v_sigma, wind_dir, z0=None, umean=None,
domain=None, dx=None, dy=None, tower=None, tower_crs=None, time=None,
**kwargs) -> Footprint:
f = ... # 2-D field on a regular grid centred on the tower
return Footprint.from_grid(f, dx=dx, tower=tower, tower_crs=tower_crs, time=time)
from fluxprint.model import available_models, get_model
available_models() # ['kljun2015', 'my_model']
get_model("my_model")(...)
The registry is backed by regorator. New models are expected to come with a
reference oracle in tests/test_reference_regression.py pinning them against
their original implementation — CI enforces this.
API reference
Top level (import fluxprint)
calculate_footprint(data, by=None, model="kljun2015", on_error="skip", ...)— footprint each group of a table; returns aFootprintSeries.on_errordecides what a failed group costs: drop it ("skip"), abort ("raise"), or keep an all-NaN member with the reason inattrs["error"]("nan"). The series is eager — right for grouped climatologies (tens to hundreds of members); for one footprint per record over long records, usemap_footprintsbelow instead.map_footprints(data, model="kljun2015", ...)— the xarray/dask-native compute path: maps the model's per-record kernel over arrays of any dimensionality and returns aDataArraywith dims(*input_dims, y, x). Lazy over dask-backed inputs; a bad record costs a NaN plane, never the job; rejections are summarised once per block.wrapper(...)—calculate_footprint+ aggregation + optional file output.empty_footprint(model, domain=..., dx=...)— a NaN template on the model's grid. This is the template API: use it to learn the exact output grid/shape (~2 ms) instead of running the model on placeholder meteorology.ALIASES— the table of column aliasesprocess_footprint_inputsrecognizes (model inputs and estimator drivers).read_handler/read_from_url/read_from_file— table/NetCDF/TIFF readers.- Legacy
write_to_*helpers are deprecated: use theFootprint.to_*methods. fluxprint.commons.start_logging(path)— opt-in file logging for applications: attaches a handler to thefluxprintlogger (the package itself stays silent by default via aNullHandler).
fluxprint.model
get_model(name)— return the registered model callable.available_models()— list registered model names.register_model(name, description="", **attrs)— decorator to register a model.FootprintModel— the callable protocol models conform to.
fluxprint.footprint.Footprint
Footprint.from_grid(f, dx, dy=None, **meta)— build a local, tower-centred footprint.georeference(target_crs)/to_lonlat()— local → projected; display lon/lat.total(),peak_xy(),normalized(),contours(rs)— analysis helpers.captured_fraction— fraction of the flux the domain captures (=total()).level_for(r)— the source-area field level for fractionr(fractions are of the full unit-integral footprint, not of the captured total).smoothed()— the standard FFP 3×3 double-convolution smoothing, applicable to any footprint (fluxprint.footprint.smooth_fieldis the array primitive).weighted_mean(field, min_coverage=None)— footprint-weighted mean of a gridded quantity; owns the NaN mask and the coverage threshold, and returns a uniform field unchanged whatever the normalization or truncation.replace(**changes)— variant constructor (fp.replace(time=None)); copiesattrs, shares the arrays unless replaced.plot(rs=None)— matplotlib quick look (never callsplt.show()).to_netcdf/from_netcdf,to_tiff/from_tiff,to_xarray/from_xarray,to_shapefile.
fluxprint.footprint.FootprintSeries
aggregate(smooth=True)— collapse the stack to a 2-D climatology.georeference(target_crs),to_netcdf/from_netcdf,to_xarray/from_xarray.
See CHANGELOG.md for release notes and deprecations.
Examples
See the sample/ directory for usage examples.
Citing FluxPrint
If FluxPrint contributes to your research, please cite the software (see CITATION.cff) alongside the footprint-model paper you used (e.g. Kljun et al., 2015 — the models' own citations and DOIs are stamped into every output's metadata).
Roadmap
docs/ROADMAP.md records where the library is going
after 0.4.0 — the planned 0.5.0 integration API, the 0.6.0 cleanup and
third model, what 1.0 will guarantee — and, just as usefully, what has
been deliberately declined and why.
Contributing
Contributions are welcome — see CONTRIBUTING.md for the development setup, and open a pull request from a feature branch.
License
Licensed under the European Union Public Licence v. 1.2 (EUPL-1.2). See the
LICENSE file for details. The vendored Kljun et al. (2015) FFP
reference code (fluxprint/model/Kljun_et_al_2015_original/) is distributed
under its own permissive licence (see the license.txt alongside it),
copyright (C) 2015–2023 Natascha Kljun.
Acknowledgments
- Kljun, N., Calanca, P., Rotach, M. W., Schmid, H. P. (2015): The simple two-dimensional parameterisation for Flux Footprint Predictions (FFP), Geosci. Model Dev. 8, 3695–3713, doi:10.5194/gmd-8-3695-2015.
- Hsieh, C.-I., Katul, G., Chi, T. (2000): An approximate analytical model for footprint estimation of scalar fluxes in thermally stratified atmospheric flows, Adv. Water Resour. 23, 765–772, doi:10.1016/S0309-1708(99)00042-1.
- Kormann, R., Meixner, F. X. (2001): An analytical footprint model for non-neutral stratification, Boundary-Layer Meteorol. 99, 207–224, doi:10.1023/A:1018991015119.
Contact
Metadata
Release files for fluxprint 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fluxprint-0.4.0.tar.gz | 128.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fluxprint-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 231.9 kB
Release files / fluxprint-0.4.0.tar.gz
| Download URL | fluxprint-0.4.0.tar.gz |
|---|---|
| Size | 128.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d983a8f00f9a0db97fb6d30364666fd38c3a5b611e7fbd231ac9cf05ea03b2e
|
|
BLAKE2b-256 checksum How to use checksums |
48d60d57e1b72fc3146805744e1dde55483b618a56e798f3c7fdbc84e57741ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / fluxprint-0.4.0-py3-none-any.whl
| Download URL | fluxprint-0.4.0-py3-none-any.whl |
|---|---|
| Size | 103.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8c50da407231ea93c13b9fe28f8af7d79e03dee3be6e0d98f93346000b6ebd52
|
|
BLAKE2b-256 checksum How to use checksums |
1cc7afee358daed2f03d484acd931076347648ebc3bd1c9a4bc0914061c91275
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency log