Current source version: 1.0.8. See CHANGELOG.md for release details.
A desktop GUI app for exploring seasonal patterns in iNaturalist observations within a geographic radius. Search by organism (anything from a genus/species to higher taxa like Agaricales), choose a date range, and plot observation frequency by day, week, or month of the year.
The app supports two search modes:
- Graph with local data (fast, offline-ish): Queries a local
observations.parquetfile using DuckDB (recommended). - Graph with live iNat data (online): Queries the iNaturalist API via
pyinaturalist(slower and rate-limited, but works without local data).
It also includes an interactive map dialog (OpenStreetMap tiles) to set coordinates and radius visually, plus export options for both graphs and data.
- Interactive GUI (PyQt6 + Matplotlib) with a sidebar of search controls and a live plot.
- Local database mode using DuckDB against
observations.parquetfor fast queries. - Taxonomy expansion using
taxonomy.parquetto include all descendant taxa of a selected organism (recursive query). - Taxon cache (
taxon_cache.json) to avoid repeated API lookups and repeated descendant expansion. - Interactive map picker for latitude/longitude + radius:
- OpenStreetMap tile fetching
- RAM LRU cache and disk cache with pruning
- Place search, familiar click/drag navigation, and a draggable radius handle
- Progress widget for long operations:
- Download progress for required Parquet files
- API pagination and rate limiting feedback
- Local query “estimate” + completion messaging
- Theme and appearance controls
- Dark mode by default, with a persistent light/dark mode toggle
- Graph color, graph background color, window background color
- Adjustable app font and graph font sizes (saved via QSettings)
- Export
- Export current plot as JPG/PNG with metadata
- Export observation data to CSV
- Optional splash screen:
splash_screen.jpgin the current working directory (CWD).
The easiest way to get started — no Python required — is to grab the latest build from the Releases page. See the Windows, macOS, and Linux installation guide for the first-launch security steps required by the unsigned Windows and unnotarized macOS builds. The same guide is included automatically on every GitHub release page.
After updating inat_visualizer_version.py and CHANGELOG.md, commit and push
main, then run:
./build-release.shThe script reads the version from the codebase, validates that local main
matches origin/main, and pushes the matching version tag. That tag triggers
the GitHub workflow that builds, smoke-tests, and publishes every platform
artifact. Use ./build-release.sh --dry-run to perform the checks without
creating a tag.
The app is cross-platform (Windows, macOS, Linux) and targets Python 3.12.
- Qt backend: PyQt6
- Matplotlib backend:
QtAgg
On Linux only, Qt runs under XWayland to avoid a Wayland protocol crash
(the app sets QT_QPA_PLATFORM=xcb automatically) and needs a couple of system libs:
sudo apt-get install -y libxcb-cursor0 libxkbcommon-x11-0Windows and macOS need no extra system packages.
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -r requirements.txtconda create -n inat_env python=3.12
conda activate inat_env
pip install -r requirements.txtThe program includes an environment self-check. Missing packages stop startup; version differences are logged as warnings but do not prevent the app from running.
The application uses two Parquet files in its runtime data directory. Sizes depend on the source snapshot; the July 2026 DWCA rebuild produced:
observations.parquet(~1.6 GB)taxonomy.parquet(~6.2 MB)
The older hosted snapshots downloaded by the app are approximately 1.02 GB and 8.7 MB, respectively.
If either is missing at startup, the app offers two choices:
- Download Local Database: uses approximately 1 GB of disk space and enables faster local observation searches. The taxonomy file also expands higher taxa such as orders and families to their descendant species.
- Use iNaturalist API Only: starts immediately without the large download.
Searches require an internet connection and may be slower or rate-limited.
Graph with local data remains disabled until
observations.parquetis installed.
If observations.parquet is already installed and only the much smaller taxonomy
file is missing, Graph with local data remains available; higher-taxon
expansion may be limited until the taxonomy file is downloaded.
Packaged apps store mutable files in these writable per-user locations:
- macOS:
~/Library/Application Support/iNat Seasonal Visualizer/ - Windows:
%LOCALAPPDATA%\iNat Seasonal Visualizer\ - Linux:
$XDG_DATA_HOME/iNat Seasonal Visualizer/, or~/.local/share/iNat Seasonal Visualizer/whenXDG_DATA_HOMEis unset
Runs from source continue to use the current working directory.
The download URLs are:
https://images.mushroomobserver.org/observations.parquethttps://images.mushroomobserver.org/taxonomy.parquet
When the observation database is installed, startup uses a lightweight HEAD
request to compare the hosted observations.parquet Content-Length with its
installed file size. A different size means a coordinated database update is
available; the app then checks the companion taxonomy file, asks before
downloading the changed files, and atomically replaces each installed copy only
after its download completes. A matching observation file does not prompt,
regardless of Last-Modified timestamps or a different older taxonomy snapshot
on the server. API-only installations do not perform this update check.
Run the repository's updater to rebuild both Parquet files from the current iNaturalist Darwin Core archives:
./update_database.pyThe script uses gbif-observations-dwca.zip from the repository directory when
it is already present (including a file downloaded separately with wget). If
it is absent, the script downloads it. It also downloads and rebuilds the
separate iNaturalist taxonomy archive required for higher-taxon searches.
Close the visualizer before running the update. The observation archive is
about 25 GB and its required CSV currently expands to more than 100 GB, so allow
roughly 140 GB of free space. The updater extracts only the two CSV members the
app needs; it does not retain the media or DNA extension data. New databases are
validated before they atomically replace the installed files. Extracted CSVs
and source archives are removed after success. Use --keep-archives to retain
the ZIP files, or --help for paths and other options.
observations.parquet must contain:
eventDatedecimalLatitudedecimalLongitudetaxonID
If these columns are missing, local-data graphing will fail with an explanatory error.
With your environment activated, run:
python visualizer.pypython visualizer.py --lat 37.7749 --lon -122.4194 --radius 25 --scale-factor 1.5 --debugFlags:
--latLatitude (default comes from saved settings)--lonLongitude (default comes from saved settings)--radiusRadius in km (default comes from saved settings)--scale-factorManual UI scale multiplier (useful for 4K/HiDPI)--http-cache-max-mbAPI response cache budget in MB (default:128)--debugEnable timestamped debug logging, including privacy-safe place-search summaries, qualifier/fallback decisions, map lifecycle events, and aggregate tile cache/network statistics
The cache budget can also be set with the
INAT_VISUALIZER_HTTP_CACHE_MAX_MB environment variable. The command-line
option takes precedence.
Logs go to:
- Packaged app:
inat_visualizer.loginside the per-user application data directory listed above - Source run:
inat_visualizer.login the current working directory
-
Set location
- Type latitude/longitude (or paste
"lat, lon"into the latitude field) - Or click Choose Location on Map… to pick a point and radius interactively.
- In the map dialog, enter a country, city, park, or other iNaturalist place and press Enter or Search to jump there. Select a result to fit the map to that place; the current radius remains unchanged.
- Drag the map to pan, click to place the center, scroll to zoom, or drag the white circle handle to resize the radius. Exact values remain editable in the Selected area panel.
- Qualified searches such as
City, Countryfirst try the complete phrase, then use the qualifier to narrow matches when a fallback is needed.
- Type latitude/longitude (or paste
-
Choose an organism
- Examples:
Boletus,Russula brevipes,Agaricales - Leave blank to search all organisms.
- Examples:
-
Optional: exclude a taxon
- Example: exclude
Boletus regineus(also expands descendants)
- Example: exclude
-
Pick a date range
- Default
Date From:2000-01-01 - Default
Date To: today's date. Local searches naturally stop at the end of the installed snapshot, while live searches can include newer data.
- Default
-
Choose view: Daily / Weekly / Monthly
-
Click:
- Graph with local data (fast, when the optional database is installed)
- Graph with live iNat data (online, rate-limited, and available without local data)
To reduce API calls, the app stores cached results in:
taxon_cache.json
This cache includes:
- Name → taxon ID
- Name → list of descendant taxon IDs (
<name>_descendants)
There is also optional support for a manual descendant file:
descendant_taxons.txt
Format:
Agaricales: 117159, 48723, 12345
If present, it can be used as a fallback when descendant expansion via taxonomy.parquet fails.
Live iNaturalist responses are cached in inat_api_cache.db inside the runtime
data directory. Expired responses are removed automatically. The cache has a
default 128 MB disk budget; if valid responses alone exceed that budget, the
app clears the response cache and continues normally.
Older versions used pyinaturalist's shared default cache. If that legacy cache is oversized, the app removes its expired responses and reclaims the unused SQLite space without deleting valid entries.
The map dialog fetches OpenStreetMap tiles and caches them:
- In RAM: LRU cache up to
MAX_CACHE_SIZEtiles - On disk:
tile_cache/inside the runtime data directory, with pruning to ~200 MB
Anonymous API usage can be rate-limited (HTTP 429 / 403). The app uses pagination and backoff, but large queries may still be slow. Local searches are better - they are much faster, and don't hit the API at all.
If you plan to do lots of API queries, you can increase limits by configuring credentials (if supported by your setup). The script references:
INATURALIST_APP_IDINATURALIST_APP_SECRET
(Place them in ~/.bashrc and restart your shell.)
On Linux the app forces Qt onto XWayland (QT_QPA_PLATFORM=xcb) automatically.
If you still have rendering issues, ensure the required libs are installed:
sudo apt-get install -y libxcb-cursor0 libxkbcommon-x11-0You can override the platform plugin by setting QT_QPA_PLATFORM yourself before
launching. (This forcing does not apply on Windows or macOS, which use their
native Qt platform plugins.)
At startup the app verifies that the required Python packages are importable.
A missing package stops startup with an install hint
(pip install -r requirements.txt). Version differences from the tested set are
logged as warnings only and do not block the app.
When you run the program, it may create the following files in its runtime data directory:
inat_visualizer.log(log file)taxon_cache.json(API/taxon cache)inat_api_cache.db(bounded live API response cache)database_stats.json(cached counts for the installed observation snapshot)descendant_taxons.txt(optional manual descendant list)tile_cache/(map tile disk cache)observations.parquet+taxonomy.parquet(large; size varies by snapshot)
The application log rotates at 2 MiB and retains at most two backups, preventing
debug sessions or long-running installations from growing it without bound.
Home-directory paths are abbreviated with ~; place-search text, result names,
geometry, and precise map coordinates are omitted from logs so diagnostics are
safer to share.
MIT