Skip to main content

thunderprint

CI PyPI crates.io License: MIT

Work out where and when an audio recording was made using only the thunder in it.

thunderprint viewer: posterior heatmap and 90% ellipse on a topo map, above modeled vs observed thunder envelopes and a spectrogram

Lightning networks log every strike with millisecond timing and positions accurate to a few hundred metres. Each strike's thunder reaches a listener after a delay set by distance. A recording of a storm therefore carries the listener's position, as the delay pattern across dozens of strikes. thunderprint searches a day of strikes from MET Norway's Frost API for the place and start time whose predicted thunder best matches the recording. It outputs a probability map with a 90% credible region.

The output is a self-contained HTML page, shown above for a real 4-minute recording from northern Norway. The map shows the posterior heatmap and the best candidate with its 90% ellipse. The panel below it plots modeled (red) against observed (blue) thunder over UTC time, above a spectrogram of the clip.

Install

Pick one:

pip install thunderprint     # CLI + Python API, prebuilt for Linux, macOS, Windows
cargo install thunderprint   # CLI from crates.io

or download a prebuilt binary from Releases.

You also need:

  • ffmpeg on PATH to decode audio. Any format ffmpeg reads works.

  • A free Frost client ID from frost.met.no, set as FROST_CLIENT_ID in the environment or in a .env file in the working directory:

    FROST_CLIENT_ID=your-client-id
    

To build from source (Rust stable, edition 2024): git clone https://github.com/Hexay/thunderprint && cargo build --release.

Quickstart

You don't need a real recording to try it. sim synthesises thunder from real strikes at a listener you choose, and you can then check whether locate finds that listener:

thunderprint sim --date 2025-07-19 --lat 65.089 --lon 19.339 --start 14:40:30 \
  --temp=12 --wind-e=4 --wind-n=4 --noise 0.03 --seed 25 --out data/sim/demo
thunderprint locate --audio data/sim/demo/audio.wav --date 2025-07-19 --out out/demo
thunderprint eval --run out/demo --truth data/sim/demo/truth.toml

eval should report an error of about 0.4 km. Open out/demo/index.html to see the map, with modeled and observed thunder and a spectrogram underneath it. Lightning for each UTC day is downloaded once and cached in data/frost/ (override with THUNDERPRINT_CACHE).

Python

pip install thunderprint

The wheel bundles the compiled binary, so Rust isn't needed. ffmpeg still has to be on PATH.

import thunderprint as tp

res = tp.locate("storm.opus", "2025-07-19", frost_client_id="...")  # or set FROST_CLIENT_ID
res.best            # Region(rank=1, lat=65.659, lon=14.117, start=datetime(...), reliable=True, ...)
res.regions         # all candidates, best first
env = res.envelope()        # .times (UTC datetimes), .observed, .modeled
res.spectrogram             # .freqs_hz, .offsets_s, .power[band][frame]
res.viewer                  # path to the interactive HTML map
res.geopackage              # path to result.gpkg for ArcGIS / QGIS (geopandas.read_file(res.geopackage, layer="cells"))

# Your own lightning data: a CSV path, a list of dicts, or a pandas DataFrame
res = tp.locate("storm.opus", "2023-06-02", strikes=df)  # columns: time, lat, lon[, peak_ka, cloud, error_km]

tp.simulate("2025-07-19", 65.089, 19.339, "14:40:30", "sim/", temp=12, seed=25)  # synthetic test clip
tp.load("out/run")          # re-read a previous run

Frost downloads are cached in the user cache directory. To use a different location, set THUNDERPRINT_CACHE.

Usage

Command What it does
locate --audio F --date D [--start HH:MM:SS] Searches every strike on UTC day D for where and when F was recorded. If you know the start time, --start narrows the search to ±0.5 s.
eval --run DIR --truth T Scores a finished run against a truth file (lat/lon TOML).
sim --date D --lat .. --lon .. --start .. Writes a synthetic audio.wav and truth.toml. Run --help for noise, wind, stereo and propagation options.
fetch --date D Downloads and caches one day of Frost lightning ahead of time.
probe ... Diagnostics: scores one point at every pipeline stage.

locate, sim and probe take --strikes file.csv to use lightning data from any network instead of Frost:

Column Required Meaning
time yes UTC, as epoch seconds or ISO-8601 (2025-07-19T16:00:01.25Z)
lat, lon yes Strike position, in degrees
peak_ka no Peak current in kA. Default 10.
cloud no 1/true for in-cloud, otherwise cloud-to-ground
error_km no Location error, as a 50% radius. Default 0.5.

Accuracy depends on the network's timing and position errors. Frost/NORDLIS reaches about 200 m; Blitzortung's public feed is about 3 km.

locate writes five files to --out (default out/run):

  • index.html: the map viewer. Under the map, it plots the modeled and observed thunder envelopes over UTC time, with a 10–1000 Hz spectrogram on the same time axis. Clicking a candidate region switches the modeled curve to that region.
  • result.gpkg: a GeoPackage for ArcGIS Pro or QGIS (drag it in), in WGS 84. It has four layers:
    • cells: posterior grid polygons
    • candidates: points
    • ellipses: 90% regions, linked to candidates by rank
    • onsets: onset-solve fixes
  • heatmap.geojson: the same features in a single GeoJSON file
  • candidates.json: ranked regions, each with its start time, fitted atmosphere, 90% area and a reliable flag
  • trace.json: the envelope curves and spectrogram shown in the viewer

Models and overrides

  • --model improved (the default) is the model described below.
  • --model baseline reimplements the method from the video that inspired this project (see Prior art). It's there for comparison.
  • --overrides file.toml overrides search settings. The fields are declared in src/config.rs and experiments/ has examples.

How it works

  1. Observed envelope. The audio is band-passed to 20–250 Hz, where thunder carries far and through walls. It's then reduced to a smoothed, compressed loudness envelope.
  2. Predicted envelope. For a candidate listener position, every strike is rendered as a thunder pulse.
    • Cloud-to-ground strikes are modelled as extended vertical channels, intra-cloud strikes as horizontal sources aloft.
    • Arrival time is slant distance divided by the speed of sound, corrected for wind.
    • Loudness scales with peak current and falls off with range.
    • Each pulse has a rumble tail whose length grows with the channel's spread of arrival times.
  3. Match. The predicted envelope is correlated with the observed one at every possible start time in the day, using FFT cross-correlation.
  4. Search, coarse to fine. A coarse grid covers the whole strike region, then mid and fine grids refine the best peaks.
    • The fine stage marginalises over nuisance atmospheres: temperature (speed of sound), wind, refraction lapse rate and the effective height thunder onsets are timed from.
  5. Posterior. Scores are tempered into a posterior, from which the tool reports ranked regions and 90% credible ellipses. A region is marked unreliable when fewer than 30 strikes would have been audible from it.

Two physical findings drove the model. Both are written up in research/:

Accuracy and limitations

Case Error
Synthetic clips, listener near active storms 0.4–1.0 km
Synthetic twin of the real clip 1.4 km
Real 4-minute recording, northern Norway 1.3 km, versus 2.2 km for the baseline method. Not blind: the truth was known during development.

Limitations:

  • Coverage. Frost lightning covers the Nordic detection network (Norway and neighbouring countries) and nothing else. For anywhere else, use --strikes with data from another network.
  • Recording length. You need several minutes of unedited audio recorded inside an active storm. Short or edited clips don't hear enough strikes to fix a position.
  • Geometry. If every strike comes from one direction, the result is a stretched ellipse. Strikes from several directions give a tight fix.
  • Video clocks. In a video, the visible flashes give the frame clock. The audio track can be seconds away from that, so --start taken from flashes needs care.
  • Real clips fit worse than simulations. The model is still incomplete, and the credible regions on real audio should be read as optimistic.

Prior art

Data and attribution

  • Lightning data: from MET Norway via the Frost API, licensed under NLOD 2.0 / CC BY 4.0. Raw strike files are not redistributed here; each user downloads them with their own client ID.
  • Map tiles in the viewer: © Esri, HERE, Garmin and © OpenStreetMap contributors.

License

MIT

Metadata

Release files for thunderprint 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for thunderprint 0.1.1
File Size Uploaded Provenance Actions
thunderprint-0.1.1.tar.gz 78.3 kB Without provenance Details for thunderprint-0.1.1.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for thunderprint 0.1.1
File Actions
thunderprint-0.1.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for thunderprint-0.1.1-py3-none-win_amd64.whl
thunderprint-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for thunderprint-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
thunderprint-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for thunderprint-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
thunderprint-0.1.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for thunderprint-0.1.1-py3-none-macosx_11_0_arm64.whl
thunderprint-0.1.1-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details for thunderprint-0.1.1-py3-none-macosx_10_12_x86_64.whl

Total release size: 17.0 MB

Release files / thunderprint-0.1.1.tar.gz

Download URL thunderprint-0.1.1.tar.gz
Size 78.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3647bb14d8963ed63a6030773b82bd6b4666a44bb9c1ecc5aad7cf73accc09f7
BLAKE2b-256 checksum
How to use checksums
eabd64e9cf9b7f31f8fdfb864d7d0ca697edb4eee4a5b2406510702f9c324f15
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thunderprint-0.1.1-py3-none-win_amd64.whl

Download URL thunderprint-0.1.1-py3-none-win_amd64.whl
Size 3.5 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
915ed02d5e7e0aa66757bb9769285ea431c34e3fdfe66cfa840d92d36c3c6f47
BLAKE2b-256 checksum
How to use checksums
fa2d16fd929ee7b4e25bad9047c1e48da49c70f69b2d73d66ee66e052ebd391c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thunderprint-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL thunderprint-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.7 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
e160c1a0e32c702bfc3d890343cb37ecc1d85f5e846d793d34c10e55360b2775
BLAKE2b-256 checksum
How to use checksums
ce559f19251117ae63eea26d1797b775f94e4a01505e1a6575a00bd7d95ac31b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thunderprint-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL thunderprint-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.2 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
18119f6d77789740e8f2772813c261d6b06666754989e4ca168d34939f461e67
BLAKE2b-256 checksum
How to use checksums
77e90a4df73f9169567ba85f11db324bd22824528eddc0f515aa389d9160de73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thunderprint-0.1.1-py3-none-macosx_11_0_arm64.whl

Download URL thunderprint-0.1.1-py3-none-macosx_11_0_arm64.whl
Size 3.0 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
c60fec27b985b29e9b980a8b38af481d1e2d7b62c9e0be11821a9c00da7af2a1
BLAKE2b-256 checksum
How to use checksums
304355587d58b3d66423befd6f3885b2978bd6dd2f0c0a1507d480b89d3b0cce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thunderprint-0.1.1-py3-none-macosx_10_12_x86_64.whl

Download URL thunderprint-0.1.1-py3-none-macosx_10_12_x86_64.whl
Size 3.4 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
d57a9dd70718f085abc7749c2e0dbbe3c46fa7edef0b4a6acc8e3658cb911470
BLAKE2b-256 checksum
How to use checksums
6d0e66c50e61c993fbf859feab1942e41c421eafdedc7cc23021b8367c0ca528
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for thunderprint 0.1.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

6 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page