Skip to main content

FermiViewer — electron microscopy image analysis

PyPI

Electron-microscopy image analysis: TEM/STEM image viewing, EELS / EDS / diffraction analysis, measurements, and image processing. Python (FastAPI) backend + React frontend + Tauri desktop shell.

Ground-up port of fermi-viewer (MATLAB), and the long-term home of this project line.

FermiViewer — multi-format image viewer with EELS/EDS/diffraction analysis and a measurement suite

Formats: DM3/DM4/DM5 (Gatan), EMD (Velox/NCEM), BCF (Bruker), SER (TIA), MRC, HDF5 family (.hspy, NeXus .nxs/.nx5, generic .h5/.hdf5), MSA/EMSA spectra, JEOL Analysis Station (.img/.map/.pts), EDAX .spc, Lispix .rpl/.raw, TIFF (pixel size and stage tilt read from Thermo Fisher/FEI SEM-FIB, Zeiss SmartSEM and ImageJ tags), PNG/JPEG/BMP/GIF, headerless RAW, AFM/SPM (Bruker Nanoscope .spm/.000, Gwyddion .gwy/.gsf, Asylum .ibw, JPK .jpk, WSxM .top/.stp, Nanosurf .nid, NT-MDT .mdt, Nanonis .sxm), 4D-STEM (Merlin .mib, 4D HyperSpy). Analysis: EELS (background, maps, quantification ± σ, thickness, Kramers–Kronig, Fourier-log, model-based peak fitting, SVD), EDS (Cliff–Lorimer / ZAF / ζ-factor composition with mass-thickness ± σ, maps, artifacts, peak fitting, composite overlays), 4D-STEM virtual-detector imaging (BF/ABF/ADF/custom apertures), diffraction (camera-length & ellipse calibration, CIF phase import, spot detection, phase indexing, d-spacings), GPA strain, CTF estimation, atom columns, particles, grains (k-means / watershed / paint-to-train classifier), cross-section layer & interface-roughness analysis, FFT filtering, drift alignment, batch recipes with folder watch and macro record/replay, Python scripting API, and a full measurement/annotation suite.


Install

Grab FermiViewer_x64-setup.exe from the latest release and run it. It is fully self-contained (~47 MB) — no Python, no Node, nothing else required. Launch FermiViewer from the Start menu; closing the window shuts everything down.

The installer is currently unsigned, so Windows SmartScreen may warn on first run — choose More info → Run anyway.

Option 2 — standalone server (no installer)

Download fv-server-win64.zip from the same release, unzip anywhere, and run:

fv-server\fv-server.exe

This starts the full app at http://127.0.0.1:8000 and opens your browser. The server exits on its own when the last tab closes (--no-auto-shutdown to keep it running, --no-browser to skip the auto-open).

Option 3 — from source

Requirements: uv (a suitable Python is fetched automatically) and Node 22+ for the frontend (see .nvmrc). Get the code with git clone, or download Source code (zip) from any release and extract it — no git needed.

git clone https://github.com/pquarterman17/fermiviewer
cd fermiviewer

cd frontend && npm ci && npm run build && cd ..   # build the web UI (once)
uv sync                                           # install backend deps
uv run fv                                         # → http://127.0.0.1:8000

That is the whole build — after the first time, uv run fv is all you run. uv run fv --desktop opens a native window instead of the browser (pywebview). Building from source needs internet (PyPI + npm) — for a machine without it, use the ready-made offline bundle in Option 5, which skips Node and PyPI entirely. For the Tauri shell / installer build, see Packaging below.

For one-click local testing, use Run-FermiViewer.bat on Windows or Run-FermiViewer.command on macOS. Both launch from the repository directory and accept the same optional flags as uv run fv when invoked from a terminal, for example Run-FermiViewer.bat --dev.

OneDrive checkouts (Windows): if the repo lives in a synced folder, move the venv out of OneDrive's reach before the first sync:

New-Item -ItemType Junction -Path .venv -Target "$env:LOCALAPPDATA\fermiviewer-venv"

([tool.uv] link-mode = "copy" is already set in pyproject.toml; the junction prevents sync-lock races during installs.)

Windows Store Python recovery: a .venv created from the Microsoft Store interpreter can stop building wheels after the Store updates Python; the old executable is a stale reparse point, so uv fails while copying it into an isolated build environment (os error 1920). Recreate the environment with a uv-managed interpreter instead:

uv python install 3.13
uv venv --managed-python --python 3.13 --clear .venv
uv sync --group dev --managed-python --python 3.13
uv run pytest -q tests\test_offline_install.py

The final command verifies the wheel and baked-SPA path that exposes the stale-interpreter problem. It should report four passing tests. This is safe with the OneDrive junction above: --clear empties the junction's target without replacing the link, so .venv stays out of OneDrive.

Option 4 — install from PyPI (pip / uv)

FermiViewer is on PyPI with the web UI already baked into the wheel — no Node, no checkout, no release download; any 64-bit Python 3.10+ works. Install once, then launch from any terminal — including from a folder of images:

uv tool install fermiviewer              # exposes `fermiviewer` on PATH
fermiviewer                              # launch from anywhere
fermiviewer C:\data\session-42           # …or point it at a folder

pip install fermiviewer works too (same wheel; use pip if you don't have uv). From a source checkout, the equivalent is uv tool install --from . fermiviewer.

fermiviewer opens a browser tab once the server is confirmed up (no more racing a cold start), and the in-app Open dialog defaults to the folder you launched from. If a copy is already running it just opens a new tab; if port 8000 is taken by another app it steps to the next free port. fv remains as a short alias for the same entry point.

Option 5 — air-gapped / offline machine (source install, no exe)

For machines with no internet access — or where IT policy rules out running downloaded executables — each release ships per-OS fv-offline-*.zip bundles: the FermiViewer wheel (web UI already baked in) plus every dependency as pre-built wheels and a standard-library-only installer. The only requirement on the target is a 64-bit Python 3.10+.

  1. Download fv-offline-win64.zip (or -macos-arm64 / -linux-x64) from the latest release and carry it over.
  2. Extract anywhere writable and run py install.py (macOS/Linux: python3 install.py).
  3. Launch with the generated FermiViewer.bat / ./fermiviewer.

Everything lives in that one folder (nothing downloaded, no admin rights); deleting the folder uninstalls it. Full details, including how to review the exact pinned dependency versions, are in the bundle's README-OFFLINE.md. To build a bundle yourself from a checkout (on a connected machine of the same OS):

uv run python tools/offline/make_bundle.py

Usage

Command What it does
fermiviewer / fv API + SPA on :8000, opens the browser once healthy, exits when the last tab closes
fermiviewer <dir> …and defaults the in-app Open dialog to <dir>
fermiviewer --desktop Native window (pywebview), exits on close
fermiviewer --dev Vite HMR (:5173) + auto-reloading backend, one terminal
fermiviewer --no-browser --no-auto-shutdown Plain server, stays up

Open files via File → Open… (the launch-folder list when started from one, otherwise the native picker), drag-and-drop, or File → Open by Path… for large files already on the server's disk. Press ? in the app for the full keyboard map, ⌘K for the command palette.

Keyboard & accessibility

The whole interface works without a mouse. Tab moves between the major regions — menu bar, library, stage, inspector — and each region is a single tab stop, so the arrow keys move within it:

Where Keys
Menu bar ← → between menus · ↓ / Enter to open · ↑ ↓ · Home/End within · → / ← into and out of submenus · Esc to close
Image library ↑ ↓ ← → and Home/End to move · Enter/Space to select · Ctrl/⌘+Enter/Space to add · Shift+Enter/Space for a range · Shift+F10 for the context menu
Dialogs Tab / Shift+Tab cycle inside the dialog · Esc closes · focus returns to whatever opened it
Command palette ⌘K / Ctrl-K to open · ↑ ↓ to move · Enter to run

Unavailable menu items are skipped while arrowing, and app-wide shortcuts are suspended while a dialog is open. Controls carry the ARIA roles screen readers expect — menu bar, multi-selectable listbox, modal dialog, combobox — icon-only buttons carry text labels, and toggles report their pressed state. Dark, light, and system themes, five Color scheme accents, and three interface densities live in Preferences → Appearance.

Full reference: Keyboard & Accessibility.


Documentation

Feature walkthroughs, screenshots, and how-tos live in the project wiki:

Cross-section layer stack Trained grain classifier
Cross-section Layers workshop — layer-stack band diagram with calibrated thickness table Trained grains — painted class examples and a pixel-classification preview
Calibrated color scale (AFM height) EELS analysis
AFM height map with calibrated color scale EELS analysis workshop

Scripting

Every registered operation is also a headless Python surface — fermiviewer.api — the same engine the server and a notebook both use, with no FastAPI/browser dependency. Registered-op coverage is 75 of 83 analysis endpoints; the eight remaining endpoints are 4D-STEM/tomography work parked behind roadmap items 8 and 9. The generated audit of exactly what is reachable headlessly is docs/operation-coverage.md:

import fermiviewer.api as fv

img = fv.open("scan.dm4")                                   # any registered format
denoised = img.gaussian(sigma=2).image.median(window_size=3).image
stats = denoised.image_stats()
print(stats.value)              # {'mean': ..., 'std': ..., 'min': ..., 'max': ..., 'shape': ...}
csv_bytes = stats.to_csv()      # a one-row CSV, ready to write or upload
print(denoised.methods())       # "scan.dm4 was processed with fermiviewer X.Y.Z:
                                 #  gaussian (sigma=2.0); median (window_size=3)."
Surface What it gives you
fv.open(path) / fv.Session() Load any registered format into an Image; a Session tracks opened + derived images and their lineage
Image.<op>(**params) Every registered op as a method — .gaussian(), .eds_quantify(), .roughness(), ... — returns a Result
Image.pipeline([{"op": ..., "params": {...}, "inputs": {...}}], reference=other) Run a whole recipe, including portable named auxiliary inputs (the same shape a saved batch preset uses)
Result.value / .image / .params A plain value (scalar/table), a derived Image, and the resolved parameters
Result.to_csv() / .to_json() Export a dict-shaped result as a table
Image.methods() / .provenance_json() A reproducible methods paragraph / full JSON ancestry for that image's pipeline
op categories filter, geometry, analysis, spectral, eels, eds, diffraction — see fv.ops() or docs/api-reference.md for the full, generated list with every parameter

Full generated reference (every class, method, and op parameter, regenerated from source): docs/api-reference.md. Complete worked scripts (filters + roughness, EDS quantification, and the same recipe against real corpus data): examples/.

For a no-code, headless batch run over a folder of files, fv --script replays a saved recipe (the exact .fvbatch.json the GUI's batch dialog exports, or a bare JSON recipe) with no server and no browser:

fv --script recipe.json scan1.dm4 scan2.dm4 --out results/
{
  "steps": [
    { "op": "gaussian", "params": { "sigma": 1.5 } },
    { "op": "median", "params": { "window_size": 3 } },
    { "op": "image_stats", "params": {} }
  ]
}

Each input gets its derived image (TIFF), every value-producing step's result (CSV + JSON), and a provenance JSON written into --out — see fv --script --help for directory/glob input expansion and defaults.


Development

uv sync --group dev                      # + ruff, mypy, pytest
uv run pytest                            # golden-verified; realdata tests
                                         #   auto-skip if the corpus is absent
uv run pytest -m "eels and golden"       # marker-scoped
uv run ruff check src tests
uv run mypy src

cd frontend
npm run dev                              # Vite on :5173, /api proxied
npx tsc --noEmit && npm run build

Hard rules (enforced by tests/test_repo_integrity.py):

  • io/ and calc/ are pure libraries — they never import FastAPI/Pydantic; routes/ are thin adapters.
  • 500-line ceiling per source module.
  • No GPL runtime dependencies (rosettasciio lives only in the oracle test group; PyInstaller only in the bundle build group).
  • Physics constants port verbatim from the MATLAB reference — annotated do-not-"fix" items are calibrated/intentional.

Packaging

A tagged push builds and publishes everything automatically (.github/workflows/release.yml):

git tag v0.2.0 && git push origin v0.2.0
# → Release with the per-OS installers (.exe / .dmg / .deb), the
#   fv-server-* standalone archives, the fv-offline-* air-gapped
#   bundles, and the auto-updater's latest.json
# → PyPI wheel (SPA baked in), published via trusted publishing after
#   a clean-venv install + boot smoke test

Local equivalents (Windows; needs Rust + VS Build Tools for the shell):

cd frontend && npm run build && cd ..
uv sync --group bundle
uv run pyinstaller tools/bundle/fv-server.spec --noconfirm --distpath dist-sidecar
cd src-tauri
npx @tauri-apps/cli@^2 build --config \
  '{"bundle":{"resources":{"../dist-sidecar/fv-server":"fv-server"}}}'

The desktop shell shows a loading splash and only navigates to the app once /api/health answers, so a slow first start no longer lands on a "can't reach this page" error. This lives in the Rust shell — installed copies must be rebuilt and reinstalled to pick it up (rebuild the SPA first so frontend/dist/loading.html is bundled).


Project docs

Doc Purpose
docs/parity_report.md Three-way parity vs the MATLAB reference + design prototype
docs/w3_imaging_audit.md Per-algorithm port decisions (map / port / hybrid)
docs/api-reference.md Generated reference for the fermiviewer.api scripting surface — every class/method/op, regenerate with tools/gen_api_reference.py
docs/operation-coverage.md Generated GUI/headless parity audit — every analysis endpoint, its GUI surface, registered-op reach, ADR 0004 result kinds, and wave; regenerate with tools/gen_coverage_table.py
examples/ Worked fermiviewer.api scripts (filters + roughness, EDS quantification, real-corpus recipe)
tests/golden/ Frozen MATLAB reference values (see tools/matlab/)
plans/ (local-only) Per-machine working plans (gitignored, fermi-viewer convention)

License

Apache-2.0. Bundled JetBrains Mono is under the SIL OFL (frontend/public/fonts/OFL.txt).

Metadata

Release files for fermiviewer 0.10.0

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

Built distribution (wheel)

Table of built distributions (wheels) for fermiviewer 0.10.0
File Interpreter ABI Platform
fermiviewer-0.10.0-py3-none-any.whl Python 3 none any Details

Release files / fermiviewer-0.10.0-py3-none-any.whl

Download URL fermiviewer-0.10.0-py3-none-any.whl
Size 1.8 MB
Tags Python 3
SHA-256 checksum
How to use checksums
f2c85b3512b8de91d390dc5caa7558f40138fe5f6a931955ba6fe2ce3e2de0fb
BLAKE2b-256 checksum
How to use checksums
e5a06f74054eb10ffc6f14c5b512aea70173ed93bc49ec60da408c3abf2f2d3d
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 Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.0 This release

1 release file

0.9.0

1 release file

0.8.1

1 release file

0.8.0

1 release file

0.7.1

1 release file

0.7.0

1 release file

0.6.1

1 release file

0.6.0

1 release file

0.5.0

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.32

1 release file

0.1.31

1 release file

0.1.30

1 release file

0.1.29

1 release file

0.1.28

1 release file

0.1.27

1 release file

0.1.26

1 release file

0.1.25

1 release file

0.1.24

1 release file

0.1.23

1 release file

0.1.22

1 release file

0.1.21

1 release file

0.1.20

1 release file

0.1.19

1 release file

0.1.18

1 release file

0.1.17

1 release file

0.1.16

1 release file

0.1.15

1 release file

0.1.14

1 release file

0.1.13

1 release file

0.1.12

1 release file

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