Skip to content

Commit

Permalink
Add imgui_fig: display matplotlib figures
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed Jan 8, 2024
1 parent 1ec50e6 commit e2dab06
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 0 deletions.
1 change: 1 addition & 0 deletions bindings/imgui_bundle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from imgui_bundle._imgui_bundle import im_cool_bar as im_cool_bar
from imgui_bundle import immapp as immapp
from imgui_bundle.immapp import icons_fontawesome as icons_fontawesome
from imgui_bundle import imgui_fig as imgui_fig

from imgui_bundle._imgui_bundle import __version__, compilation_time

Expand Down
4 changes: 4 additions & 0 deletions bindings/imgui_bundle/demos_cpp/demo_immapp_launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ std::function<void()> makeGui()
DemoApp{"haiku_implot_heart", "Share some love for ImGui and ImPlot"},
DemoApp{"demo_drag_and_drop", "Drag and drop demo"},
DemoApp{"demo_implot_markdown", "How to quickly run an app that uses implot and/or markdown with ImmApp"},
DemoApp{
"demo_matplotlib",
"Python: display matplotlib figures in an ImGui window (animated or static)",
},
DemoApp{
"imgui_example_glfw_opengl3",
"Python: translation of the [GLFW+OpenGL3 example](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_opengl3/main.cpp) from Dear ImGui. "
Expand Down
4 changes: 4 additions & 0 deletions bindings/imgui_bundle/demos_python/demo_immapp_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ def make_gui() -> GuiFunction:
"demo_implot_markdown",
"How to quickly run an app that uses implot and/or markdown with ImmApp",
),
DemoApp(
"demo_matplotlib",
"Python: display matplotlib figures in an ImGui window (animated or static)",
),
DemoApp(
"imgui_example_glfw_opengl3",
"Python: translation of the [GLFW+OpenGL3 example](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_opengl3/main.cpp) from Dear ImGui. "
Expand Down
78 changes: 78 additions & 0 deletions bindings/imgui_bundle/demos_python/demos_immapp/demo_matplotlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import matplotlib
# Important: before importing pyplot, set the renderer to Tk,
# so that the figure is not displayed on the screen before we can capture it.
matplotlib.use('Agg') #
import matplotlib.pyplot as plt
from imgui_bundle import immapp, imgui, imgui_fig, imgui_ctx
import numpy as np


class AnimatedFigure:
"""A class that encapsulates a Matplotlib figure, and provides a method to animate it."""
x: np.ndarray
y: np.ndarray
amplitude: float = 1.0
plotted_curve: matplotlib.lines.Line2D
phase: float
fig: matplotlib.figure.Figure
ax: matplotlib.axes.Axes

def __init__(self):
# Data for plotting
self.phase = 0.0
self.x = np.arange(0.0, 2.0, 0.01)
self.y = 1 + np.sin(2 * np.pi * self.x + self.phase) * self.amplitude

# Create a figure and a set of subplots
self.fig, self.ax = plt.subplots()

# Plot the data
self.plotted_curve, = self.ax.plot(self.x, self.y)

# Add labels and title
self.ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Simple Plot: Voltage vs. Time')

# Add a grid
self.ax.grid()

def animate(self):
self.phase += 0.1
self.y = 1 + np.sin(2 * np.pi * self.x + self.phase) * self.amplitude
self.plotted_curve.set_ydata(self.y)


def main():
# Create an animated figure
animated_figure = AnimatedFigure()

# Create a static figure
x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
y = np.sin(x) * np.exp(-x ** 2 / 20)
static_fig, static_ax = plt.subplots()
static_ax.plot(x, y)

def gui():
# Show an animated figure
with imgui_ctx.begin_group():
animated_figure.animate()
imgui_fig.fig("Animated figure", animated_figure.fig, refresh_image=True, show_options_button=False)
imgui.set_next_item_width(immapp.em_size(20))
_, animated_figure.amplitude = imgui.slider_float("amplitude", animated_figure.amplitude, 0.1, 2.0)

imgui.same_line()

# Show a static figure
imgui_fig.fig("Static figure", static_fig)


runner_params = immapp.RunnerParams()
runner_params.fps_idling.fps_idle = 0 # disable idling, so that the animation is fast
runner_params.app_window_params.window_geometry.size = (1400, 600)
runner_params.app_window_params.window_title = "imgui_fig demo"
runner_params.callbacks.show_gui = gui
immapp.run(runner_params)


if __name__ == '__main__':
main()
83 changes: 83 additions & 0 deletions bindings/imgui_bundle/imgui_fig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import matplotlib
# Important: before importing pyplot, set the renderer to Tk,
# so that the figure is not displayed on the screen before we can capture it.
matplotlib.use('Agg') #
import matplotlib.pyplot as plt

import numpy
import cv2
import matplotlib
from imgui_bundle.immapp import static
from imgui_bundle import immvision

from typing import Tuple
# from numpy.typing import ArrayLike


"""
Display Matplotlib figures in an ImGui window.
"""


Size = Tuple[int, int]
Point2d = Tuple[float, float]


@static(fig_cache=dict())
def _fig_to_image(figure: matplotlib.figure.Figure, refresh_image: bool = False) -> numpy.ndarray:
"""
Convert a Matplotlib figure to an RGB image.
Parameters:
- figure (matplotlib.figure.Figure): The Matplotlib figure to convert.
Returns:
- numpy.ndarray: An RGB image as a NumPy array with uint8 datatype.
"""
statics = _fig_to_image
fig_id = id(figure)
if refresh_image and fig_id in statics.fig_cache:
del statics.fig_cache[fig_id]
if fig_id not in statics.fig_cache:
# draw the renderer
figure.canvas.draw()
# Get the RGBA buffer from the figure
w, h = figure.canvas.get_width_height()
buf = numpy.fromstring(figure.canvas.tostring_rgb(), dtype=numpy.uint8)
buf.shape = (h, w, 3)
img_rgb = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
matplotlib.pyplot.close(figure)
statics.fig_cache[fig_id] = img_rgb
return statics.fig_cache[fig_id]


def fig(label_id: str,
figure: matplotlib.figure.Figure,
image_display_size: Size = (0, 0),
refresh_image: bool = False,
show_options_button: bool = False) -> Point2d:
"""
Display a Matplotlib figure in an ImGui window.
Parameters:
- label_id (str): An identifier for the ImGui image widget.
- figure (matplotlib.figure.Figure): The Matplotlib figure to display.
- image_display_size (Size): Size of the displayed image (width, height).
- refresh_image (bool): Flag to refresh the image.
- show_options_button (bool): Flag to show additional options.
Returns:
- Point2d: The position of the mouse in the image display.
Important:
before importing pyplot, set the renderer to Tk,
so that the figure is not displayed on the screen before we can capture it.
```python
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
```
"""
image_rgb = _fig_to_image(figure, refresh_image)
mouse_position = immvision.image_display(label_id, image_rgb, image_display_size, refresh_image, show_options_button)
return mouse_position

0 comments on commit e2dab06

Please sign in to comment.