Skip to main content

cellpy simple gui

A small desktop app for exploring battery cell data with cellpy (≥ 2.1).

It runs a local FastAPI backend inside a native window (via pywebview).

Cycle summary

Cell explorer — cellpy cycle curves Manage cells modal


Features

  • Zero-setup demo — one click loads three bundled example cells (no files needed).
  • Load your own .cellpy / legacy .h5 files.
  • Import raw instrument files — Arbin .res, Maccor (text), Neware, PEC and more are processed into cellpy cells with a metadata step (mass / area / nominal capacity / cycle mode). One-click bundled raw demos too.
  • Save & reopen projects — explicit Save (not autosave) writes the loaded set plus grouping / labels / selection into a portable project folder; reopen later. The project tag shows when you have unsaved edits (name*), and Close clears the current session after confirmation. Re-saving only rewrites cells whose data changed — renaming or regrouping is roughly 10× faster than a full write, because those live in the manifest, not in the .cellpy files.
  • Cycle summary across many cells — built with cellpy's own collect_summaries + plotting, with a plot-type selector (capacity + CE, capacity, coulombic efficiency, cumulated CE, end voltages, internal resistance, C-rate, capacity loss), a gravimetric / areal / absolute basis, optional group averaging with a mean ± std spread band, and independent or shared y-scales.
  • Cell explorer — cellpy's collect_cycles voltage–capacity curves for any set of cycles (gravimetric / areal / absolute, method), with per-cell metric tiles. Switch the same cycles to dQ/dV (incremental capacity) or dV/dQ (differential voltage), charge / discharge / both.
  • Cycles collector — the same three curve types across every selected cell, laid out per cycle or per cell, or as a film (density) plot.
  • Load data lots of ways: bundled demo cells, .cellpy / .h5 files, native cellpy batch journals (.json), or your own project folders — with glob patterns (*si*.h5, capped at a configurable max) and, in the desktop app, native file pickers. Journal load failures surface as toasts instead of a stuck spinner.
  • Editable cell list (the "journal"): rename, group, select/deselect, remove — plus a Manage cells modal (filter/sort, select-by-group, remove all).
  • Instruments discovered from cellpy at runtime (not hard-coded), with each loader's sub-models.
  • Clear feedback: toast notifications for loads, saves, opens, exports, and errors (including corrupt journals).
  • Background loading with live progress (SSE) — the UI never freezes.
  • Export collected data to CSV / Excel / Parquet / JSON, and charts as PNG / SVG / PDF from Export ▾ (server-side via kaleido — install with uv sync --extra export); the chart toolbar camera still saves a quick PNG.
  • Light & dark themes.
  • Colorized terminal logging via loguru (CSG_LOG_LEVEL, default INFO).

Install

Requires Python ≥ 3.13. With uv:

uv tool install "cellpy-simple-gui[desktop]"
cellpy-simple-gui

Or run it once without installing anything:

uvx --from "cellpy-simple-gui[desktop]" cellpy-simple-gui

pipx works too: pipx install "cellpy-simple-gui[desktop]".

The [desktop] extra is what gives you the native window. Leave it off and you get a fully working app that opens in your browser instead — which is what a server wants, and why it is an extra rather than a dependency.

Two heavier routes, if you would rather not have Python in the picture:


Quick start (from a clone)

Requires Python ≥ 3.13 and uv.

uv sync --extra desktop

The native window lives in the desktop extra so a served instance need not install GUI libraries. Plain uv sync gives you a fully working app that opens in your browser instead.

Then from this folder:

run                 # Windows (cmd / PowerShell)
./run               # macOS / Linux / Git Bash

That opens the app in a native desktop window. Prefer your normal browser?

run --server              # local server + browser tab
run --server --no-open    # headless: just serve

The helpers use uv run --extra export --extra desktop, so figure export and the native window both work. Equivalent without them: uv run --extra export --extra desktop cellpy-simple-gui (same flags). Then click Load demo cells and explore.

Build the Windows installer

pwsh packaging/build_installer.ps1

Produces a ~178 MB per-user installer: no admin, Start-menu entry, clean uninstall that leaves your projects alone. It is unsigned, so SmartScreen will warn on first run — docs/windows-installer.md explains exactly what you will see, why, and what fixing it costs, along with where the app writes its logs when something goes wrong.

Cutting a release of any of the three artefacts: docs/releasing.md.

Run it as a server

docker compose up --build      # or: docker build -t cellpy-simple-gui .

One container serves one person — the library, the job manager and cellpy's config session are process-global, by design. The per-launch token is not authentication: put it behind a reverse proxy with TLS and real auth, or keep it on a network you already trust. docs/deployment.md has the full story, including the things that bite (volume ownership, cellpy's own directories, and why server-side figure export is not in the image).

Developer mode

run --dev                 # or set CSG_DEV_MODE=1

The regular UI shows a curated set of plot types, because cellpy registers far more than are useful on any one dataset. Developer mode adds every summary family cellpy registers, grouped by whether the loaded cells can actually plot them — the rest are listed but disabled, showing which summary columns are missing rather than rendering a blank chart. Availability comes from cellpy itself (family.summary_options()), so families whose columns the collector builds — CV splits, the full-cell standards — are offered rather than hidden. It also raises the glob/batch file cap (10 → 500) for stress-testing. A DEV badge marks the session.

Two extra Cell-explorer views appear alongside the regular ones:

View What it shows
Raw traces The raw time series (voltage/current/capacity, or all of it)
Raw + step/cycle info Raw traces annotated with step and cycle boundaries

Raw data is big — 155k rows for a single demo cell — so Max points caps what is drawn (cellpy does the downsampling itself since 2.1.2, #867) and the chart says when it applied: "showing 1,882 of 155,754 points". For raw data rather than a picture, use Export cells → csv.

Clicking the DEV badge opens Diagnostics:

  • Logs — a live tail of the last 2000 records, filterable by level. cellpy's own records arrive here too (they go through stdlib logging), so a failed load can be diagnosed without opening cellpy_debug.log.
  • Jobs — every job this session with how long it waited for a worker and how long it ran.

copy puts either view on the clipboard as plain text, ready for a bug report.

Off by default and not reachable from the UI: regular users get the curated set.

First run downloads a few small example cells from the cellpy example-data repository (then cached). Everything else is fully offline.

Projects

Plot types

Projects on disk

A project is a portable folder — move it, zip it, share it:

<project>/
├── project.json      # manifest: name, timestamps, versions, per-cell grouping/labels/selection
├── cellpy.toml       # optional: cellpy settings pinned to this project
└── data/
    ├── c1.cellpy     # every loaded cell saved as a self-contained cellpy file
    └── c2.cellpy

Projects live under ~/.cellpy_simple_gui/projects/ by default; Save writes the current set there and Open restores it (physical quantities come from the .cellpy files, organisational metadata from the manifest). Changes in the UI are not written until you Save.

That split is also why Save is quick. Re-saving reuses a .cellpy file whenever the cell it holds provably has not changed — nothing edited it since it was read, the file is still there, and its size and timestamp still match. Renaming or regrouping touches only project.json. Editing mass, area, nominal capacity or cycle mode rewrites the summary, so those cells are written out again; if anything is uncertain, the file is rewritten. Save-As always produces a complete project.

Moving the data directory

CSG_DATA_DIR decides where projects live. It defaults to ~/.cellpy_simple_gui, so desktop installs need nothing; a container points it at a volume:

CSG_DATA_DIR=/data cellpy-simple-gui --server --no-open

~ and relative paths are expanded and resolved, and reading the setting never creates anything — whoever writes creates what it needs.

cellpy keeps its own directories, separate from the app's, and they default under $HOME too. Anything running where $HOME is not persistent should place them deliberately. Every field under [paths] has an environment override, CELLPY_PATHS__<FIELD>:

CELLPY_PATHS__CELLPYDATADIR=/data/cellpy/cellpyfiles
CELLPY_PATHS__OUTDATADIR=/data/cellpy/out
CELLPY_PATHS__EXAMPLESDIR=/data/cellpy/examples   # bundled demo cells cache here
CELLPY_PATHS__FILELOGDIR=/data/cellpy/logs

Overrides apply per field, so setting one leaves the rest alone. The cellpy version badge in the app shows every setting and which layer it came from, which is the quickest way to confirm a deployment is reading what you think.

Local vs served: what the app may touch

Typing D:\data\*.res into the app is the point of a desktop tool. Answering requests over a network with that same freedom is arbitrary read and write on the host, behind nothing but a per-launch token — so the app has two modes, chosen by the bind address:

bound to paths
local loopback anywhere you can reach, as always
served anything else inside CSG_DATA_DIR only

In served mode, absolute paths, .., drive letters, UNC paths and symlinks that point outward are all refused — the resolved location is what gets checked, so a link that looks innocent does not get through. Globs are rooted at the data directory and their results filtered, because ** through a symlinked directory is the classic way out.

One case the bind address gets wrong: an instance on loopback published by a reverse proxy looks local from inside, because the proxy connects from loopback. Force it:

CSG_ALLOW_HOST_PATHS=0

/api/system/capabilities reports host_paths_allowed and sandbox_root, so you can confirm which rules an instance is running under.

A served instance currently reads files that are already inside its data directory — mount your data there. Browser upload is #133.

Per-project cellpy settings

Drop a cellpy.toml next to project.json and the app activates it as cellpy's project configuration layer whenever that project is open — so the project pins the settings its data was analysed with (cycle mode, units, defaults) instead of being silently re-interpreted under whatever your global config says today:

[reader]
cycle_mode = "cathode"

[units]
mass = "g"

A cellpy.toml chip appears next to the project name while those settings are active; closing the project restores your own configuration. Settings never leak from one project to the next. Click the chip (or the cellpy version badge) to see every setting and which layer it came from — defaults, your user cellpy.toml, the project file, or the environment.

Rather than writing the file by hand, open that panel with a project loaded and hit Pin settings to project: it captures the reader, units and defaults currently in effect. Only those sections are written — paths would bake your machine's directory layout into a folder meant to be portable, and leaving out instruments/db means the file structurally cannot contain a credential.

The app never writes your user cellpy.toml; that file belongs to you and is shared with your notebooks and the cellpy CLI.

How it is built

Three layers, each testable on its own. The UI never imports cellpy; the core never imports the web framework.

┌──────────────────────────────────────────────────────────────┐
│  desktop shell (pywebview window)                             │
│    └── web/  Alpine.js + Plotly.js  (served by the backend)   │
├──────────────────────────────────────────────────────────────┤
│  api/   FastAPI (127.0.0.1 + per-launch token)                │
│         routers · JobManager (threads + SSE progress)         │
├──────────────────────────────────────────────────────────────┤
│  core/  pure Python — no web imports                          │
│         models · library · plotting · export                  │
│         cellpy_adapter.py  ← the ONLY module that imports cellpy
└──────────────────────────────────────────────────────────────┘
                              │
                          cellpy ≥ 2.1
src/cellpy_simple_gui/
├── core/
│   ├── cellpy_adapter.py   # every cellpy call lives here (get, summary, get_cap, example_data)
│   ├── models.py           # Pydantic domain models (CellMeta, SummaryPlotSpec, …)
│   ├── library.py          # in-memory library of loaded cells = source of truth
│   ├── collect.py          # bridges the library into cellpy.collect / .plotting (from_cells)
│   ├── plotting.py         # thin: delegates figures to cellpy via collect.py
│   ├── projects.py         # save/open portable project folders (manifest + .cellpy files)
│   ├── files.py            # glob/path expansion with a max cap + messages
│   └── export.py           # csv / xlsx / parquet / json from cellpy collections
├── api/
│   ├── app.py              # FastAPI factory + index route
│   ├── jobs.py             # tiny thread-pool JobManager (progress + cancel)
│   └── routers/            # cells · plots · export · jobs · projects · ingest · system
├── web/                    # templates/ (Jinja) + static/ (css, js, vendored Plotly & Alpine)
├── logging_setup.py        # loguru terminal logging + stdlib bridge
├── server.py               # uvicorn-in-a-thread helper
├── desktop.py              # pywebview launcher
└── __main__.py             # entry point (desktop / --server)

Powered by cellpy, not around it. Summaries, grouping (incl. group averaging with spread), per-cell cycle curves, the plots and the multi-format export are all produced by cellpy's own collect / plotting subsystems. On cellpy ≥ 2.1.1 the app feeds them a real Batch via cellpy.collect.from_cells(...) built from the in-memory library (collect.py), and discovers instruments via cellpy.list_instruments(). The cellpy surface stays isolated to cellpy_adapter.py + collect.py.

Development

uv sync --extra dev
uv run pytest            # core unit tests + FastAPI integration tests
uv run pytest -m essential   # critical-path subset (also run in CI)

The suite passes without the desktop extra — that is the point of it being an extra, and one test skips accordingly. Add --extra desktop to run that one too.

Optional Playwright GUI smoke tests (server/browser mode, not pywebview):

uv sync --extra dev --extra e2e
uv run playwright install chromium
uv run pytest -m e2e

Without the e2e extra / Chromium browsers, those tests skip so a plain uv run pytest stays green.

GitHub Actions runs the essential marker. One workflow decides internally whether the change touched code, so every PR gets exactly one essential check run — and a green one means either "tests passed" or "no code changed", never "a second workflow reported first". If the base commit cannot be resolved, it runs the tests rather than guessing.

Optional static figure export (Export ▾ → Figure → PNG / SVG / PDF, via kaleido):

uv sync --extra export

Without that extra, data export still works; figure formats show a toast pointing at the install command above.

Why we develop this app

The app is meant to inspire other users of cellpy to make their own apps. We also use the development of this app to find pain-points in the cellpy library.

Already now, building this surfaced a number of cellpy rough edges, filed upstream as jepegit/cellpy#785–#791most were fixed in cellpy 2.1.1. The full write-up and per-item status is in CELLPY_PAINPOINTS.md.

Status

This is an MVP / reference implementation

Done: load .cellpy/.h5 files, raw-file ingestion (Arbin .res / Maccor / Neware / PEC → cellpy), cycle-summary & cell-explorer plotting, editable cell list + Manage cells modal, CSV/Excel/Parquet/JSON export with feedback, save/open/close portable projects, batch-journal loading with error toasts, loguru console logging, and essential-test CI.

Arbin .res loads on Windows through the Access ODBC driver (no separate mdbtools needed in this environment). Instruments needing extra engines will surface a clear error.

Future plans

  • packaging into a Windows installer (PyInstaller + InnoSetup, bundling WebView2).
  • show-case more of the plots

License

See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cellpy_simple_gui-0.1.1.tar.gz (2.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cellpy_simple_gui-0.1.1-py3-none-any.whl (1.5 MB view details)

Uploaded Python 3

File details

Details for the file cellpy_simple_gui-0.1.1.tar.gz.

File metadata

  • Download URL: cellpy_simple_gui-0.1.1.tar.gz
  • Upload date:
  • Size: 2.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cellpy_simple_gui-0.1.1.tar.gz
Algorithm Hash digest
SHA256 668f64d9de611cfece3e79d36ba503023b002c3da3413198d1c3108404a0748c
MD5 a1f6c73e1eeb46b2945706393a5a834e
BLAKE2b-256 5f1f837ab7fb61d6ef88e00788c200dd574eb1478234acaf664e221bb982169e

See more details on using hashes here.

Provenance

The following attestation bundles were made for cellpy_simple_gui-0.1.1.tar.gz:

Publisher: publish.yml on cellpy/cellpy-simple-gui

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cellpy_simple_gui-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for cellpy_simple_gui-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 52e529742e8edc42b9492731a509f64ecdaae672c9d11c46d5d27c9ecb7a8add
MD5 f2a0d5ee8f21dfa855d672d212352911
BLAKE2b-256 d5c4f15a7a76699231742cf47f9fd323b6762addd2217dbddf8c65ed7c125206

See more details on using hashes here.

Provenance

The following attestation bundles were made for cellpy_simple_gui-0.1.1-py3-none-any.whl:

Publisher: publish.yml on cellpy/cellpy-simple-gui

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

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