2024-01-06

QBuffer を利用する ~ PySide6

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。Linux/X11, macOS および Microsoft Windows をサポートしています。配布ライセンスは LGPL で公開されています。

本ブログの過去記事 [1] で、データベースに書き込んだ PDF を読み込んで表示する際に、一旦、テンポラリ領域にファイルとして保存したファイルを読み込んで QPdfView に表示するサンプルを紹介しました。その後、QBuffer を利用すればファイルに保存せずともデータベースから読み込んだバイナリを QPdfView へ渡せることを確認できました(追記済みです)。

今回は、matplotlib が保存する画像を、ファイルに保存せずに QBuffer を介してウィジェットに画像として表示するサンプルを紹介します。

QBuffer を使用すると、QIODevice インターフェイスを使用して QByteArray にアクセスできます。QIODevice は、ブロックデバイスのための抽象インターフェースを提供しています。

今回のテーマ
  • Matplotlib で描画したチャートを plt.savefig() あるいは figure.savefig() で画像ファイルに保存できますが、これを画像ファイルに、ではなく、QBuffer クラスのインスタンスを介して QWidget 上に画像として表示します。

なお、本サンプルで使用している 3D プロットのチャートは、下記のサイトから引用させていただいています。

下記の OS 環境で動作確認をしています。

Fedora Workstation 39 x86_64
Python 3.11.7
PySide6 6.6.1
matplotlib 3.8.2

サンプルを以下に示します。

qt_matplotlib_sombrero.py
#!/usr/bin/env python
# coding: utf-8
import matplotlib as mpl
import numpy as np
import sys
from PySide6.QtGui import QPixmap, QIcon
from matplotlib.backends.backend_qtagg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar
)
from matplotlib.figure import Figure
from PySide6.QtCore import (
QBuffer,
QByteArray,
QIODevice,
Qt,
)
from PySide6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QStyle,
QToolBar,
QToolButton,
QWidget,
)
class ImageViewer(QWidget):
def __init__(self, byte_array: QByteArray):
super().__init__()
self.init_ui(byte_array)
self.setWindowTitle('Matplotlib image viewer')
def init_ui(self, byte_array: QByteArray):
lab = QLabel(self)
pixmap = QPixmap()
pixmap.loadFromData(byte_array)
lab.setPixmap(pixmap)
lab.setFixedSize(
pixmap.size().width(),
pixmap.size().height()
)
class Sombrero(FigureCanvas):
def __init__(self):
self.fig = Figure()
super().__init__(self.fig)
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
ax = self.fig.add_subplot(111, projection='3d')
ax.plot_surface(
X, Y, Z,
rstride=1, cstride=1,
cmap=mpl.colormaps['coolwarm'],
linewidth=0, antialiased=False
)
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.viewer = None
self.init_ui()
self.setWindowTitle('QBuffer test')
def init_ui(self):
toolbar = QToolBar()
self.addToolBar(toolbar)
but_apply = QToolButton()
ico_apply = self.style().standardIcon(
QStyle.StandardPixmap.SP_DialogApplyButton
)
but_apply.setIcon(QIcon(ico_apply))
but_apply.clicked.connect(self.on_apply_button)
toolbar.addWidget(but_apply)
canvas = Sombrero()
self.setCentralWidget(canvas)
navtoolbar = NavigationToolbar(canvas, self)
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, navtoolbar)
def on_apply_button(self):
buffer = QBuffer()
buffer.open(QIODevice.OpenModeFlag.WriteOnly)
canvas: QWidget | FigureCanvas = self.centralWidget()
canvas.figure.savefig(buffer)
byte_array = buffer.data()
self.viewer = ImageViewer(byte_array)
self.viewer.show()
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

サンプルの説明

本サンプルを実行すると、下記のような matplotlib で描画したチャートのウィンドウが表示されます。

qt_matplotlib_sombrero.py の実行例 (1)

QMainWindow の下部のステータスバーの位置に配置した matplotlib の NavigationToolbar の右端のフロッピーディスクのアイコンをクリックすれば、画像としてチャートを保存することができます。しかし、今回はウィンドウ上部のツールバーの位置に Apply ボタンを用意して、これをクリックすれば、別ウィンドウ (QWidget) 上に matplotlib が出力した画像イメージを表示するようにしました。

その Apply ボタンをクリックすると下記のような QWidget のトップレベルのウィンドウが表示され、出力された画像が表示されます。

qt_matplotlib_sombrero.py の実行例 (2)

Apply ボタンをクリックすると下記の on_apply_button メソッドが実行されます。

Example クラスの on_apply_button メソッド
def on_apply_button(self):
    buffer = QBuffer()
    buffer.open(QIODevice.OpenModeFlag.WriteOnly)
 
    canvas: QWidget | FigureCanvas = self.centralWidget()
    canvas.figure.savefig(buffer)
    byte_array = buffer.data()
    self.viewer = ImageViewer(byte_array)
    self.viewer.show()

書き込み用のバッファ buffer を用意して、チャートを表示している FigureCanvas のインスタンス canvassavefig メソッドの引数にします。通常この引数には、出力するファイル名を指定しますが、QBuffer のインスタンスを指定しています。

savefig メソッドが出力したバイナリは、buffer に QByteArray 型で保持されます。これを bufferdata メソッドで取り出し、画像を表示する ImageViewer のインスタンスへ渡します。

ImageViewer クラス
class ImageViewer(QWidget):
    def __init__(self, byte_array: QByteArray):
        super().__init__()
        self.init_ui(byte_array)
        self.setWindowTitle('Matplotlib image viewer')
 
    def init_ui(self, byte_array: QByteArray):
        lab = QLabel(self)
        pixmap = QPixmap()
        pixmap.loadFromData(byte_array)
        lab.setPixmap(pixmap)
        lab.setFixedSize(
            pixmap.size().width(),
            pixmap.size().height()
        )

ImageViewer クラスでは、QPixmap の loadFromData メソッドで、渡された QByteArray のインスタンスを読み込んで QLabel 上に表示しています。

まとめ

今回紹介したサンプルは QBuffer の機能を検証しただけの、他になんの役にも立たないサンプルですが、QBuffer を入出力のバッファとして利用したい場面はいろいろとありそうです。QBuffer の使い方については、まだ不慣れな部分が多いので間違っているところがあるかもしれません。誤りが判り次第、訂正を加えていくようにします。

なお、QBuffer を利用することがメインのテーマだったので、PySide6 上で matplotlib を扱うことについての説明は割愛しました。

参考サイト

  1. bitWalk's: PDF をデータベースに格納 ~ PySide6 [2023-12-29]
  2. QBuffer - Qt for Python
  3. QIODevice - Qt for Python
  4. QByteArray - Qt for Python

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



0 件のコメント: