Single source of truth for MEDUSA styling: one Style x Theme look for PySide6 QSS + Matplotlib, organized by rendering environment.
Project description
medusa-style
Single source of truth (SSOT) for MEDUSA styling. One look themes BOTH PySide6 desktop apps (via generated QSS) and Matplotlib scientific plots (built on Matplotlib's bundled seaborn baseline) — so a microvolt is the same color in the dark acquisition app and on a white-paper export, and an embedded plot is seamless with the panel that hosts it.
The look has two independent axes:
- a theme — the color scheme (
dark,light, …), aPalette. - a style — the visual language (
modern,presentation, …): fonts, density and plot line widths, aStyle.
Any style renders in any theme. The code is organized by rendering
environment (qt/, mpl/, …), so adding a backend never touches the others.
Install
pip install git+https://github.com/medusabci/medusa-style.git
That's all — PySide6 and Matplotlib are both core dependencies (every MEDUSA
component needs both). The only extra is [dev] for building/testing the
package itself. The seaborn library is not required: the seaborn base
styles ship inside Matplotlib.
The whole API
import medusa_style as ms
ms.apply(app) # paint a Qt application (widgets + seamless plots)
ms.apply(widget) # scope the theme to one Qt widget
ms.apply() # theme every Matplotlib figure you draw
ms.apply(fig) # restyle one figure in place
ms.apply(ax) # restyle one axes in place
ms.use_theme("light") # switch COLORS — restyles everything already styled
ms.use_style("presentation")# switch VISUAL LANGUAGE — restyles everything
ms.current_theme() # the active Palette
ms.current_style() # the active Style
ms.themes() # ['dark', 'light'] — for a theme picker
ms.styles() # ['modern', 'presentation'] — for a style picker
ms.customize(colors=..., style=...) # one-line house theme / style
ms.on_change(cb) # react to switches — cb(palette, style)
ms.categorical_color(7) # SSOT categorical color #7
apply() dispatches on what you give it: nothing → global Matplotlib, a
QApplication/QWidget → Qt, a Figure/Axes → Matplotlib. You never choose a
backend. Whatever you pass is remembered, so a later use_theme()/use_style()
repaints it with no loop on your side.
Desktop app (PySide6)
import sys
import medusa_style as ms
from medusa_style.qt import application, PlotPanel
from PySide6.QtWidgets import QMainWindow
app = application(sys.argv) # Hi-DPI handled before construction; themed
ms.apply() # theme Matplotlib figures too
win = QMainWindow()
panel = PlotPanel(toolbar=True) # seamless embed — no objectName, no restyle call
panel.figure.add_subplot(111).plot(range(10), color=ms.categorical_color(0))
win.setCentralWidget(panel)
win.show()
# View menu -> app AND embedded figure restyle, one line each:
view = win.menuBar().addMenu("View")
view.addAction("Light", lambda: ms.use_theme("light"))
view.addAction("Presentation", lambda: ms.use_style("presentation"))
sys.exit(app.exec())
PlotPanel is the borderless Matplotlib↔Qt seam as a drop-in widget: its
background equals the plot's, and it tracks itself for live theme/style switches.
For a frame you already own, use medusa_style.qt.embed_figure(frame, fig).
Notebook / headless (Qt never imported)
import medusa_style as ms
ms.apply() # theme Matplotlib globally
fig = make_figure()
ms.apply(fig) # ...or restyle one figure
ms.use_theme("dark") # restyles tracked figures in place
Importing medusa_style pulls in neither PySide6 nor Matplotlib until
you actually style something.
Themes and styles
Two built-in themes (color schemes), both tuned for long medical/scientific sessions:
- dark (default) — deep, cool, near-black canvas for dim-lab real-time use.
- light — a calm cool-gray elevation ramp for reports/paper export
(
plot_bgis pure white).
Two built-in styles (visual languages):
- modern (default) — the clean flat MEDUSA look (
assets/qss/base.qss). - presentation — larger type, thicker plot lines and roomier controls for
projectors and slides (token-only; reuses
base.qss).
The data-visualization encodings — a 16-color colorblind-aware categorical cycle
and the medusa_sequential (PSD) / medusa_diverging (topomap) colormaps — are
theme- and style-independent, so results stay comparable across every look.
Customize
Zero config by default; override in one line. colors derive a new theme,
style derives a new style:
ms.customize(
colors={"accent_primary": "#FF6600"}, # -> a registered theme
style={"ui_size_body": 15, "plot_line_width": 1.4}, # -> a registered style
) # activates + live-restyles; returns the created value(s)
Architecture & extensibility
medusa_style/
api.py the public verbs
dispatch.py target detection + environment routing (stdlib-only)
appearance.py the active (theme, style) + live switching + observer
palette.py the color SSOT (Palette, DARK, LIGHT) + encodings
style.py the visual languages (Style, MODERN, PRESENTATION)
resources.py zip-safe asset loading
qt/ the Qt environment (QSS render, PlotPanel, icons, fonts)
mpl/ the Matplotlib environment (rcParams, colormaps, figure styling)
assets/ qss/ (structural templates), icons/, fonts/, brand/
- Add a theme — one
Palette(...)literal inpalette.py(+PALETTESentry) orms.register_theme(...). Works in every environment immediately. - Add a style — one
Style(...)literal instyle.py(+STYLESentry) orms.register_style(...). A token-only style is pure data; only a structurally different widget language needs its ownassets/qss/<name>.qss. - Add an environment (Plotly, VisPy, Bokeh, …) — a new package with a
matches/_apply/reapplytrio and onems.register_environment(...). No existing file changes.
Power-user namespaces
The tiny top-level surface is enough for almost everything; reach into these only when you need the knobs:
| Namespace | What's there |
|---|---|
medusa_style.qt |
application(), PlotPanel, embed_figure(), style_app(), stylesheet(), prepare_high_dpi(), icon(), pixmap(), load_fonts() |
medusa_style.mpl |
style_figure(), style_axes(), rcparams(), build_rcparams(), mplstyle_text(), register_colormaps(), sequential_cmap(), diverging_cmap() |
medusa_style.palette |
the raw SSOT colors: Palette, DARK, LIGHT, CATEGORICAL_CYCLE, WCAG utils |
medusa_style.style |
the visual languages: Style, MODERN, PRESENTATION |
medusa_style.qt imports PySide6 (never Matplotlib); medusa_style.mpl imports
Matplotlib (never Qt). import medusa_style loads neither until you style
something, so a headless figure-export job never pulls in Qt.
Icons, fonts & brand assets
from medusa_style import qt
button.setIcon(qt.icon("save_as")) # theme-agnostic action icon
button.setIcon(qt.icon("delete_forever", color="error")) # ...or a semantic color
splash = qt.pixmap("medusa_splash") # brand images (splash/login/about)
win.setWindowIcon(qt.app_icon()) # the MEDUSA app icon
path = qt.asset_path("fonts/Roboto-Regular.ttf") # any bundled file, real path
qt.icon(name) icons are theme-agnostic. The color is not baked in: each
icon recolors to the active theme on every repaint and changes with Qt's state
automatically — a disabled widget dims its icon, and a theme switch repaints it
live, with no re-fetch. Pass color= (a palette role like "error"/"accent_primary",
or a "#RRGGBB" literal) to force a specific color; it still dims when disabled.
qt.application() does the common setup for you: it registers the bundled fonts
(Roboto family, Abel, Dense, Womby) and sets the MEDUSA window icon app-wide
(both opt-out via application(set_icon=False, load_bundled_fonts=False)).
Packaging
All runtime data lives under medusa_style/assets/ — qss/ (the structural QSS
templates), icons/, fonts/ and brand/. They are declared in pyproject.toml
so the wheel always includes them, while the editable .psd design sources stay
in the repo-level design_source/ directory (outside the package, never shipped):
[tool.hatch.build.targets.wheel]
packages = ["medusa_style"]
artifacts = [
"medusa_style/assets/**/*.qss",
"medusa_style/assets/**/*.svg",
"medusa_style/assets/**/*.png",
"medusa_style/assets/**/*.ico",
"medusa_style/assets/**/*.ttf",
"medusa_style/assets/**/*.otf",
"medusa_style/py.typed",
]
At runtime the assets are loaded zip-safely via medusa_style.resources (which
wraps importlib.resources), so it works even from inside a zipped wheel.
Matplotlib .mplstyle files are generated on demand
(medusa_style.mpl.write_mplstyle(...)) rather than shipped, so they can never
drift from the SSOT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file medusa_style-0.3.1.tar.gz.
File metadata
- Download URL: medusa_style-0.3.1.tar.gz
- Upload date:
- Size: 4.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91eca0fa6f42fb07229f30e1dc42d72fddc666014d586fc5f6feb0e893f3d277
|
|
| MD5 |
f0fa57ff46d49694062f979b59ff9b96
|
|
| BLAKE2b-256 |
fb93ff198c067ebb8dca18dc3e181eeeb6d019ba8c5054688b3847ae901a2c59
|
Provenance
The following attestation bundles were made for medusa_style-0.3.1.tar.gz:
Publisher:
publish.yml on medusabci/medusa-style
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medusa_style-0.3.1.tar.gz -
Subject digest:
91eca0fa6f42fb07229f30e1dc42d72fddc666014d586fc5f6feb0e893f3d277 - Sigstore transparency entry: 2036278695
- Sigstore integration time:
-
Permalink:
medusabci/medusa-style@1943a52ad9d2dd49ee11a281f9d1d937fd3c5431 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/medusabci
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1943a52ad9d2dd49ee11a281f9d1d937fd3c5431 -
Trigger Event:
release
-
Statement type:
File details
Details for the file medusa_style-0.3.1-py3-none-any.whl.
File metadata
- Download URL: medusa_style-0.3.1-py3-none-any.whl
- Upload date:
- Size: 4.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6940625fa3ac84aff4a60739f063c248967ddb631d358572e23466d54774241d
|
|
| MD5 |
a75726932d73adc31a4102b233614e40
|
|
| BLAKE2b-256 |
2bba3297461ef6b467be847306cb6ea55a35efe5b90037b542c5bf32a2ada3a7
|
Provenance
The following attestation bundles were made for medusa_style-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on medusabci/medusa-style
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medusa_style-0.3.1-py3-none-any.whl -
Subject digest:
6940625fa3ac84aff4a60739f063c248967ddb631d358572e23466d54774241d - Sigstore transparency entry: 2036278864
- Sigstore integration time:
-
Permalink:
medusabci/medusa-style@1943a52ad9d2dd49ee11a281f9d1d937fd3c5431 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/medusabci
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1943a52ad9d2dd49ee11a281f9d1d937fd3c5431 -
Trigger Event:
release
-
Statement type: