Skip to main content

imgui_debugger

PyPI version Python 3.10+ License: MIT

Standalone, configurable debug panels for imgui-bundle apps

install · panels · one menu · inspector · style editor · examples · configuration · issues

About

Every debugging tool imgui offers normally hangs off the demo window's Tools menu. This package unbundles them: each tool is an independent, configurable panel you can drop into your own app, on your own menu, with your own shortcuts — plus a live variable inspector and a style editor whose Save and Load are yours.

Nothing here needs the demo window, and nothing here depends on anything else here: import one panel, or take the whole set.

panel what it shows comes from
Debugger every variable your widget can see, by scope, live this package
StyleEditor sizes, colors, rendering, with pluggable Save / Load this package
MetricsPanel windows, draw lists, viewports, internal state imgui
DebugLogPanel focus, nav, docking and IO events as they happen imgui
IdStackPanel what an item's id is built from, for id collisions imgui
AboutPanel version and the build's enabled features imgui
UserGuidePanel imgui's built-in control reference imgui
DemoPanel the demo window, if you ever want it — opt-in only imgui

Built for widgets like the ones in mbo_utilities and masknmf-toolbox, but it knows nothing about them — a target is any Python object.

Install

pip install imgui_debugger

The only dependency is imgui-bundle (which provides imgui, hello_imgui, immapp and the FontAwesome icon font).

The panels

Every panel — including ones you write — has the same four-part surface:

panel.visible                  # bool you can set, bind, or persist
panel.menu_item()              # a checked menu entry bound to visible
panel.render()                 # the body, at the current cursor, no window
panel.render_window()          # the body in its own window; polls the hotkey

so wiring one into a host app is two lines:

def draw_menu(self):
    if imgui.begin_menu("View"):
        self.editor.menu_item()       # or menu_item("Theme", "Ctrl+T")
        imgui.end_menu()

def draw(self):
    ...
    self.editor.render_window()

render() is there for when you would rather host the body yourself — in a tab, a dock node, or a sidebar you already own. imgui's own windows (MetricsPanel and friends) are window-only and say so by raising from render().

Configuration is a dataclass per panel, all sharing PanelConfig:

from imgui_bundle import imgui
from imgui_debugger import Hotkey, StyleEditor, StyleEditorConfig

editor = StyleEditor(StyleEditorConfig(
    title="Theme",                          # window title + menu label
    visible=False,                          # starts closed
    hotkey=Hotkey(imgui.Key.t, ctrl=True),  # Ctrl+T toggles it; the menu says so
    window_size=(520, 700),
    show_sizes=False,                       # StyleEditor's own knobs
    on_save=my_app.save_style,
))

To show one on its own, with no host app at all:

from imgui_debugger import run_panel
run_panel(StyleEditor())
run_panel(MetricsPanel())

Writing your own panel is a subclass and a render:

from imgui_debugger import Panel, PanelConfig

class Timings(Panel):
    config_class = PanelConfig

    def render(self):
        imgui.text(f"{imgui.get_io().framerate:.0f} fps")

One menu for everything

DebugTools is an ordered set of panels with one menu and one per-frame call:

from imgui_debugger import DebugTools, Hotkey

self.tools = DebugTools.default(self, menu_label="Debug")
self.tools["Debugger"].config.hotkey = Hotkey(imgui.Key.f12)

def draw_menu(self):
    self.tools.draw_menu()        # or draw_menu_items() inside your own menu

def draw(self):
    ...
    self.tools.render()           # every open panel, every hotkey

DebugTools.default() gives the inspector, the style editor, metrics, the debug log and the ID stack tool, all starting closed. The demo window is deliberately left out. The set is editable — add, remove, tools["title"], show_all, hide_all, visible() — so you can swap the style editor for one configured your way, or register a panel of your own beside them.

Variable inspector

Point it at a widget and it draws every variable that widget can see, grouped by scope, in a collapsible tree that re-reads its values every frame:

  • instance — the object's own __dict__ and __slots__
  • properties — property descriptors, evaluated live; one that raises shows the exception instead of blanking the panel
  • class — class attributes from the whole MRO
  • locals / globals — the call frame you captured, so you can follow a draw method's own variables
  • imgui — io, mouse, keyboard, the window rect and the style metrics that explain most layout bugs
  • watches — anything else you promote to a top-level scope

Leaves that are a bool, number, string or color tuple get an inline editor that writes straight back onto the object.

from imgui_debugger import attach

class RoiWidget:
    def __init__(self):
        self.threshold = 0.4
        self.debugger = attach(self, title="ROIs widget")

    def update(self):          # your per-frame draw
        ...
        self.debugger.capture()        # follow this method's locals
        self.debugger.render_window()

Or inspect one object with no host app:

from imgui_debugger import run_debugger
run_debugger({"fs": 9.6, "dz": 5.0, "planes": [1, 2, 3]})

Scopes

Debugger.scopes() returns them in display order: the target's instance / properties / class, then your watches, then locals / globals, then imgui.

scope source writable
instance vars(obj) + __slots__ yes
properties property / cached_property on the MRO only with an fset
class class attributes, no methods or descriptors yes
locals the captured frame's f_locals no (writes do not stick)
globals the captured frame's f_globals yes
imgui io, mouse, keyboard, window, style no
watches watch(name, value_or_callable) depends on the value

A watch takes a value or a zero-argument callable; the callable is re-read every frame, so it survives the attribute being reassigned:

dbg.watch("metadata", lambda: self.metadata, role="prop")
dbg.watch("fps", lambda: {"now": imgui.get_io().framerate}, role="runtime")

watch_all(obj, ["metadata", "indices"]) does the same for several attributes in one call.

Toolbar

control what it does
filter case-insensitive match over names and leaf values, recursing into children (bounded to 6 levels and 64 items per container, memoized per filter string)
expand / collapse force every node open or shut for one frame
private include _name attributes
edit turn the inline editors off and read only

Style editor

StyleEditor is the imgui demo's style editor with its Save Ref / Revert Ref / Export buttons replaced by two of yours. imgui.show_style_editor() cannot be drawn without those buttons, and they only manage an in-memory reference style — nothing they do touches disk — so this draws the tabs itself.

from imgui_debugger import StyleEditor, StyleEditorConfig

editor = StyleEditor(StyleEditorConfig(
    title="Theme",
    visible=False,
    save_label="Save to settings",
    load_label="Load from settings",
    on_save=my_app.save_style,   # on_save(data: dict)
    on_load=my_app.load_style,   # on_load() -> dict | None
))

With no on_save / on_load, the buttons fall back to reading and writing config.path as JSON (~/.imgui_debugger/style.json when that is unset too). on_load returning None is a cancel — that is how a native file dialog the user dismissed reports back. Errors from either hook land on the editor's status line instead of raising inside a frame.

The body is configurable: show_sizes, show_colors and show_rendering pick the tabs (one tab alone is drawn without a tab bar), show_font_selector and show_style_selector add imgui's own pickers, size_groups replaces the Sizes tab's fields entirely, and extra_draw(editor) slots your own controls under the toolbar. Presets apply style_colors_dark / _light / _classic; Revert restores the style as it was when the editor was constructed.

The serialization is usable on its own:

function does
style_to_dict(style=None) {"sizes": {...}, "colors": {name: [r,g,b,a]}}
apply_style_dict(data, style=None) writes it back, returns how many fields landed
save_style(path=None, style=None) style_to_dict to JSON
load_style(path=None, style=None) JSON to apply_style_dict

Colors are keyed by imgui's own names (Text, FrameBg, ...), and unknown keys are ignored, so a file written against an older imgui still loads. Two fields are deliberately not serialized: font_scale_dpi and font_size_base are derived from the screen at runtime, and restoring another machine's values resizes every font for the wrong display.

Examples

See all examples in examples/.

name file what it shows
debug_minimal debug_minimal.py one-shot window over a settings dataclass
debug_widget debug_widget.py a widget that owns its inspector, captures its own locals, and toggles it with F12
debug_edge_window debug_edge_window.py a fastplotlib EdgeWindow, the widget shape pml_utilities and masknmf-toolbox use
style_editor_menu style_editor_menu.py the style editor alone, on a menu, saving into the app's own settings file
debug_tools_menu debug_tools_menu.py the whole set behind one menu, with hotkeys and a replaced style editor

Configuration reference

PanelConfig — every panel takes these

field default purpose
title per panel window title and default menu label
visible True whether it starts open
window_id "" stable imgui id suffix; the class name when empty, so renaming the title keeps the saved layout
window_size (0, 0) first-use size; (0, 0) sizes to content
window_pos None first-use position
window_flags 0 imgui.WindowFlags_ bits
closable True draw the close button and clear visible with it
shortcut "" menu shortcut text; the hotkey's text when empty
hotkey None Hotkey(key, ctrl=, shift=, alt=) that toggles the panel
theme Theme.dark() palette for what the panel draws itself

DebuggerConfig — plus the above

field default purpose
target None the object whose scopes come first
private False show _name attributes
properties True show the properties scope and expand nested properties
class_attrs True show the class scope
editable True inline editors for writable leaves
show_frame True show locals / globals
show_runtime True show the live imgui scope
max_depth 8 deepest level the tree expands
max_items 200 rows per container before "+N more"
value_col 0.0 pixel column values align at; 0 packs them after the name
show_toolbar True draw the filter box and toggles
show_title True draw the title line inside the body
os_window_title, resizable, ini_path, assets_folder — one-shot run_debugger only

StyleEditorConfig — plus PanelConfig

field default purpose
on_save None on_save(data); falls back to writing path
on_load None on_load() -> dict | None; falls back to reading path
path None file used by the fallback Save / Load
save_label, load_label "Save", "Load" button text
show_presets, show_revert True the preset buttons and Revert
show_sizes, show_colors, show_rendering True which tabs to draw
show_font_selector, show_style_selector False imgui's own pickers above the tabs
size_groups None replace the Sizes tab's field groups
extra_draw None extra_draw(editor) under the toolbar

attach(target, **kwargs) and run_debugger(target, **kwargs) take any DebuggerConfig field as a keyword argument.

Files on disk

Everything the library writes lives under ~/.imgui_debugger/ (override with the IMGUI_DEBUGGER_HOME env var):

path written by purpose
~/.imgui_debugger/debugger.ini, panel.ini run_debugger, run_panel hello_imgui window layout. Override per call; an embedding app's own ini_filename always wins.
~/.imgui_debugger/style.json save_style with no path the fallback style file
~/.imgui_debugger/assets/ you (optional) user assets folder, added to hello_imgui's search path when the icon font cannot be resolved

Embedded use writes nothing: render() and render_window() only draw.

Notes

  • Reads are guarded. A property that raises, a __repr__ that raises, and a container that changes size mid-frame all render as a row rather than crashing the frame.
  • Big containers are capped, not truncated silently: a "+N more" line says how many rows were left out.
  • Editing writes through the same setter the row was built from — setattr for an attribute, __setitem__ for a dict or list entry, the property's fset for a property.

Acknowledgements

The tree rendering, the bounded memoized filter and the value formatting come from the metadata inspector in mbo_utilities at the Miller Brain Observatory. The packaging, theming and one-shot harness follow imgui_data_loader.

License

MIT

Metadata

Release files for imgui_debugger 0.2.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 imgui_debugger 0.2.0
File Size Uploaded
imgui_debugger-0.2.0.tar.gz 52.0 kB Details

Built distribution (wheel)

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

Total release size: 94.7 kB

Release files / imgui_debugger-0.2.0.tar.gz

Download URL imgui_debugger-0.2.0.tar.gz
Size 52.0 kB
Tags Source
SHA-256 checksum
How to use checksums
ec2995546c5fb8bd981a693cfa698ec34735094bc8930bab3c4c3eb378ea6182
BLAKE2b-256 checksum
How to use checksums
bb0b32c7de70010c61433149184ac67574105c78871b9e6639bdaecd7610324a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / imgui_debugger-0.2.0-py3-none-any.whl

Download URL imgui_debugger-0.2.0-py3-none-any.whl
Size 42.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d4c723d2e2db36caa2d95dd01cc9b22ad3552ceaca91657467c4a38df60b119
BLAKE2b-256 checksum
How to use checksums
42463239d1b1208245444f866b64c7ed3886ec0c996238dc5af9d09d2a9a2591
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release 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