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.0

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.0
File Size Uploaded Provenance Actions
thunderprint-0.1.0.tar.gz 78.3 kB Without provenance Details for thunderprint-0.1.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for thunderprint 0.1.0
File Actions
thunderprint-0.1.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for thunderprint-0.1.0-py3-none-win_amd64.whl
thunderprint-0.1.0-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.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
thunderprint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for thunderprint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
thunderprint-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for thunderprint-0.1.0-py3-none-macosx_11_0_arm64.whl
thunderprint-0.1.0-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details for thunderprint-0.1.0-py3-none-macosx_10_12_x86_64.whl

Total release size: 17.0 MB

Release files / thunderprint-0.1.0.tar.gz

Download URL thunderprint-0.1.0.tar.gz
Size 78.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3b17a9850bd29f1618c4d88408014fbe36e8e1220c53a9e3c3f5fbde7a2f6bf6
BLAKE2b-256 checksum
How to use checksums
54d308cda22df4362c00db832bead31d4d99b3f5e73e0af8787232893c409ead
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.0-py3-none-win_amd64.whl

Download URL thunderprint-0.1.0-py3-none-win_amd64.whl
Size 3.5 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
009a06b1ea992ab1a57ca8324b335a8bc47a0d9fd7dce3d057d1d7a351137135
BLAKE2b-256 checksum
How to use checksums
3d389d4394aebfd9a26fb276b34262c7ffa1da35fe800017f67b5197b10da743
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.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL thunderprint-0.1.0-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
4443a2bc709144f7cce4c079b3370e14aa361e480bfa9c6bfe62f09ea0c58aa4
BLAKE2b-256 checksum
How to use checksums
6342c94ae5c6c349d4b6eeab7592a39ad05190ea21ee13b2421278d372b4974f
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.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL thunderprint-0.1.0-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
292dda55e90174e59731d644e7fc379908a50e6c02cc60e040bdd71d536af220
BLAKE2b-256 checksum
How to use checksums
65fe0782d26b108a70188c0f2c5ebc5064c5b6db3a5341fdd21b11ba6aa34ec6
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.0-py3-none-macosx_11_0_arm64.whl

Download URL thunderprint-0.1.0-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
720cfd0a776576da057d89fd63a1e6c4bd457a59946a95ccf33dfde9e0939359
BLAKE2b-256 checksum
How to use checksums
a1698d3f09e643e0f258d6552bdd7b24ea9b19a7aacd3d60a7318e75a8338ce8
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.0-py3-none-macosx_10_12_x86_64.whl

Download URL thunderprint-0.1.0-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
7ea593bdbb65a9bd3a991382190be7d7eaa4337079519a5eb0f5ef5f9b3534c0
BLAKE2b-256 checksum
How to use checksums
a430c29fb68da6cc55026c2b982a38f9b64187b7b78e256d262fef03dd092a1c
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.0

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.0 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