Skip to content

Commit e2dab06

Browse files
committed
Add imgui_fig: display matplotlib figures
1 parent 1ec50e6 commit e2dab06

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed

bindings/imgui_bundle/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from imgui_bundle._imgui_bundle import im_cool_bar as im_cool_bar
1818
from imgui_bundle import immapp as immapp
1919
from imgui_bundle.immapp import icons_fontawesome as icons_fontawesome
20+
from imgui_bundle import imgui_fig as imgui_fig
2021

2122
from imgui_bundle._imgui_bundle import __version__, compilation_time
2223

bindings/imgui_bundle/demos_cpp/demo_immapp_launcher.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ std::function<void()> makeGui()
3939
DemoApp{"haiku_implot_heart", "Share some love for ImGui and ImPlot"},
4040
DemoApp{"demo_drag_and_drop", "Drag and drop demo"},
4141
DemoApp{"demo_implot_markdown", "How to quickly run an app that uses implot and/or markdown with ImmApp"},
42+
DemoApp{
43+
"demo_matplotlib",
44+
"Python: display matplotlib figures in an ImGui window (animated or static)",
45+
},
4246
DemoApp{
4347
"imgui_example_glfw_opengl3",
4448
"Python: translation of the [GLFW+OpenGL3 example](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_opengl3/main.cpp) from Dear ImGui. "

bindings/imgui_bundle/demos_python/demo_immapp_launcher.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ def make_gui() -> GuiFunction:
5959
"demo_implot_markdown",
6060
"How to quickly run an app that uses implot and/or markdown with ImmApp",
6161
),
62+
DemoApp(
63+
"demo_matplotlib",
64+
"Python: display matplotlib figures in an ImGui window (animated or static)",
65+
),
6266
DemoApp(
6367
"imgui_example_glfw_opengl3",
6468
"Python: translation of the [GLFW+OpenGL3 example](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_opengl3/main.cpp) from Dear ImGui. "
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import matplotlib
2+
# Important: before importing pyplot, set the renderer to Tk,
3+
# so that the figure is not displayed on the screen before we can capture it.
4+
matplotlib.use('Agg') #
5+
import matplotlib.pyplot as plt
6+
from imgui_bundle import immapp, imgui, imgui_fig, imgui_ctx
7+
import numpy as np
8+
9+
10+
class AnimatedFigure:
11+
"""A class that encapsulates a Matplotlib figure, and provides a method to animate it."""
12+
x: np.ndarray
13+
y: np.ndarray
14+
amplitude: float = 1.0
15+
plotted_curve: matplotlib.lines.Line2D
16+
phase: float
17+
fig: matplotlib.figure.Figure
18+
ax: matplotlib.axes.Axes
19+
20+
def __init__(self):
21+
# Data for plotting
22+
self.phase = 0.0
23+
self.x = np.arange(0.0, 2.0, 0.01)
24+
self.y = 1 + np.sin(2 * np.pi * self.x + self.phase) * self.amplitude
25+
26+
# Create a figure and a set of subplots
27+
self.fig, self.ax = plt.subplots()
28+
29+
# Plot the data
30+
self.plotted_curve, = self.ax.plot(self.x, self.y)
31+
32+
# Add labels and title
33+
self.ax.set(xlabel='time (s)', ylabel='voltage (mV)',
34+
title='Simple Plot: Voltage vs. Time')
35+
36+
# Add a grid
37+
self.ax.grid()
38+
39+
def animate(self):
40+
self.phase += 0.1
41+
self.y = 1 + np.sin(2 * np.pi * self.x + self.phase) * self.amplitude
42+
self.plotted_curve.set_ydata(self.y)
43+
44+
45+
def main():
46+
# Create an animated figure
47+
animated_figure = AnimatedFigure()
48+
49+
# Create a static figure
50+
x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
51+
y = np.sin(x) * np.exp(-x ** 2 / 20)
52+
static_fig, static_ax = plt.subplots()
53+
static_ax.plot(x, y)
54+
55+
def gui():
56+
# Show an animated figure
57+
with imgui_ctx.begin_group():
58+
animated_figure.animate()
59+
imgui_fig.fig("Animated figure", animated_figure.fig, refresh_image=True, show_options_button=False)
60+
imgui.set_next_item_width(immapp.em_size(20))
61+
_, animated_figure.amplitude = imgui.slider_float("amplitude", animated_figure.amplitude, 0.1, 2.0)
62+
63+
imgui.same_line()
64+
65+
# Show a static figure
66+
imgui_fig.fig("Static figure", static_fig)
67+
68+
69+
runner_params = immapp.RunnerParams()
70+
runner_params.fps_idling.fps_idle = 0 # disable idling, so that the animation is fast
71+
runner_params.app_window_params.window_geometry.size = (1400, 600)
72+
runner_params.app_window_params.window_title = "imgui_fig demo"
73+
runner_params.callbacks.show_gui = gui
74+
immapp.run(runner_params)
75+
76+
77+
if __name__ == '__main__':
78+
main()

bindings/imgui_bundle/imgui_fig.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import matplotlib
2+
# Important: before importing pyplot, set the renderer to Tk,
3+
# so that the figure is not displayed on the screen before we can capture it.
4+
matplotlib.use('Agg') #
5+
import matplotlib.pyplot as plt
6+
7+
import numpy
8+
import cv2
9+
import matplotlib
10+
from imgui_bundle.immapp import static
11+
from imgui_bundle import immvision
12+
13+
from typing import Tuple
14+
# from numpy.typing import ArrayLike
15+
16+
17+
"""
18+
Display Matplotlib figures in an ImGui window.
19+
"""
20+
21+
22+
Size = Tuple[int, int]
23+
Point2d = Tuple[float, float]
24+
25+
26+
@static(fig_cache=dict())
27+
def _fig_to_image(figure: matplotlib.figure.Figure, refresh_image: bool = False) -> numpy.ndarray:
28+
"""
29+
Convert a Matplotlib figure to an RGB image.
30+
31+
Parameters:
32+
- figure (matplotlib.figure.Figure): The Matplotlib figure to convert.
33+
34+
Returns:
35+
- numpy.ndarray: An RGB image as a NumPy array with uint8 datatype.
36+
"""
37+
statics = _fig_to_image
38+
fig_id = id(figure)
39+
if refresh_image and fig_id in statics.fig_cache:
40+
del statics.fig_cache[fig_id]
41+
if fig_id not in statics.fig_cache:
42+
# draw the renderer
43+
figure.canvas.draw()
44+
# Get the RGBA buffer from the figure
45+
w, h = figure.canvas.get_width_height()
46+
buf = numpy.fromstring(figure.canvas.tostring_rgb(), dtype=numpy.uint8)
47+
buf.shape = (h, w, 3)
48+
img_rgb = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
49+
matplotlib.pyplot.close(figure)
50+
statics.fig_cache[fig_id] = img_rgb
51+
return statics.fig_cache[fig_id]
52+
53+
54+
def fig(label_id: str,
55+
figure: matplotlib.figure.Figure,
56+
image_display_size: Size = (0, 0),
57+
refresh_image: bool = False,
58+
show_options_button: bool = False) -> Point2d:
59+
"""
60+
Display a Matplotlib figure in an ImGui window.
61+
62+
Parameters:
63+
- label_id (str): An identifier for the ImGui image widget.
64+
- figure (matplotlib.figure.Figure): The Matplotlib figure to display.
65+
- image_display_size (Size): Size of the displayed image (width, height).
66+
- refresh_image (bool): Flag to refresh the image.
67+
- show_options_button (bool): Flag to show additional options.
68+
69+
Returns:
70+
- Point2d: The position of the mouse in the image display.
71+
72+
Important:
73+
before importing pyplot, set the renderer to Tk,
74+
so that the figure is not displayed on the screen before we can capture it.
75+
```python
76+
import matplotlib
77+
matplotlib.use('Agg')
78+
import matplotlib.pyplot as plt
79+
```
80+
"""
81+
image_rgb = _fig_to_image(figure, refresh_image)
82+
mouse_position = immvision.image_display(label_id, image_rgb, image_display_size, refresh_image, show_options_button)
83+
return mouse_position

0 commit comments

Comments
 (0)